<commit_before>import formCache from '../../public/js/src/module/form-cache';
import connection from '../../public/js/src/module/connection';
import store from '../../public/js/src/module/store';
import { getLastSavedRecord } from '../../public/js/src/module/last-saved';

/**
 * @typedef { import('sinon').SinonSandbox } SinonSandbox
 */

/**
 * @typedef { import('sinon').SinonFakeTimers } SinonFakeTimers
 */

/**
 * @typedef { import('sinon').SinonStub } SinonStub
 */

/**
 * @typedef {import('../../app/models/record-model').EnketoRecord} EnketoRecord
 */

/**
 * @typedef {import('../../app/models/survey-model').SurveyObject} Survey
 */

/**
 * @typedef {import('../../../../app/models/survey-model').SurveyExternalData} SurveyExternalData
 */

/**
 * @typedef GetFormPartsStubResult
 * @property { string } enketoId
 * @property { string } form
 * @property { string } model
 * @property { string } hash
 */

const parser = new DOMParser();

const url1 = '/path/to/source.png';
const form1 = `<form class="or"><img src="${url1}"/></form>`;
const defaultInstanceData =
    '<data id="modelA"><item>initial</item><meta><instanceID/></meta></data>';
const model1 = `<model><instance>${defaultInstanceData}</instance><instance id="last-saved" src="jr://instance/last-saved"/></model>`;
const hash1 = '12345';

describe('Client Form Cache', () => {
    /** @type {Survey} */
    let survey;

    /** @type {SurveyExternalData} */
    let lastSavedExternalData;

    /** @type {SinonSandbox} */
    let sandbox;

    /** @type {SinonStub} */
    let getFormPartsSpy;

    /** @type {GetFormPartsStubResult} */
    let getFormPartsStubResult;

    /** @type {SinonStub} */
    let getFileSpy;

    /** @type {SinonFakeTimers} */
    let timers;

    beforeEach((done) => {
        const formElement = document.createElement('form');

        formElement.className = 'or';
        document.body.appendChild(formElement);

        survey = {};
        sandbox = sinon.createSandbox();

        // Prevent calls to `_updateCache` after tests complete/stubs are restored
        timers = sandbox.useFakeTimers();

        lastSavedExternalData = {
            id: 'last-saved',
            src: 'jr://instance/last-saved',
            xml: parser.parseFromString(defaultInstanceData, 'text/xml'),
        };

        getFormPartsStubResult = {
            externalData: [lastSavedExternalData],
            form: form1,
            model: model1,
            hash: hash1,
        };

        getFormPartsSpy = sandbox
            .stub(connection, 'getFormParts')
            .callsFake((survey) =>
                Promise.resolve(survey.enketoId)
                    .then((enketoId) => {
                        if (enketoId != null) {
                            return getLastSavedRecord(survey.enketoId);
                        }
                    })
                    .then((lastSavedRecord) => {
                        if (lastSavedRecord != null) {
                            return { lastSavedRecord };
                        }

                        return {};
                    })
                    .then((lastSavedData) => {
                        const formParts = {
                            enketoId: survey.enketoId,
                            ...getFormPartsStubResult,
                            ...lastSavedData,
                        };

                        return formParts;
                    })
            );

        getFileSpy = sandbox.stub(connection, 'getMediaFile').callsFake((url) =>
            Promise.resolve({
                url,
                item: new Blob(['babdf'], {
                    type: 'image/png',
                }),
            })
        );

        store.init().then(done, done);
    });

    afterEach((done) => {
        timers.clearTimeout();
        timers.clearInterval();
        timers.restore();
        sandbox.restore();

        document.body.removeChild(document.querySelector('form.or'));

        store.survey.removeAll().then(done, done);
    });

    it('is loaded', () => {
        expect(formCache).to.be.an('object');
    });

    describe('in empty state', () => {
        it('will call connection.getFormParts to obtain the form parts', (done) => {
            survey.enketoId = '10';
            formCache
                .init(survey)
                .then(() => {
                    expect(getFormPartsSpy).to.have.been.calledWith(survey);
                })
                .then(done, done);
        });

        it('will call connection.getMediaFile to obtain form resources', (done) => {
            survey.enketoId = '20';
            formCache
                .init(survey)
                .then((result) => {
                    const currentForm = document.querySelector('form.or');
                    const form = document
                        .createRange()
                        .createContextualFragment(result.form);

                    currentForm.parentNode.replaceChild(form, currentForm);

                    return formCache.updateMedia(result);
                })
                .then(() => {
                    expect(getFileSpy).to.have.been.calledWith(url1);
                })
                .then(done, done);
        });

        it('will populate the cache upon initialization', (done) => {
            survey.enketoId = '30';
            formCache
                .get(survey)
                .then((result) => {
                    expect(result).to.equal(undefined);

                    return formCache.init(survey);
                })
                .then(() =>
                    // we could also leave this out as formCache.init will return the survey object
                    formCache.get(survey)
                )
                .then((result) => {
                    expect(result.model).to.equal(model1);
                    expect(result.hash).to.equal(hash1);
                    expect(result.enketoId).to.equal(survey.enketoId);
                })
                .then(done, done);
        });

        it('will empty src attributes and copy the original value to a data-offline-src attribute ', (done) => {
            survey.enketoId = '40';
            formCache
                .init(survey)
                .then((result) => {
                    expect(result.form)
                        .to.contain('src=""')
                        .and.to.contain(`data-offline-src="${url1}"`);
                })
                .then(done, done);
        });
    });

    describe('form cache updates', () => {
        /**
         * @param {Partial<GetFormPartsStubResult>} updates
         */
        const updateSurvey = (updates) => {
            // Ensure `_updateCache` receives a new hash indicating it should perform an update
            sandbox
                .stub(connection, 'getFormPartsHash')
                .callsFake(() => Promise.resolve(updates.hash));

            const updatePromise = new Promise((resolve) => {
                setTimeout(resolve, formCache.CACHE_UPDATE_INITIAL_DELAY + 1);
            });

            const originalStoreUpdate = store.survey.update.bind(store.survey);

            sandbox.stub(store.survey, 'update').callsFake((update) =>
                originalStoreUpdate(update).then((result) => {
                    if (update.model === updates.model) {
                        timers.tick(1);
                    }

                    return result;
                })
            );

            timers.tick(formCache.CACHE_UPDATE_INITIAL_DELAY);

            getFormPartsStubResult = { ...getFormPartsStubResult, ...updates };

            // Wait for `_updateCache` to resolve
            return updatePromise.then(() => formCache.get(survey));
        };

        it('updates the survey when the form cache is out of date', (done) => {
            Object.assign(survey, {
                enketoId: '60',
                hash: '1234',
                model: model1,
            });

            const originalSurvey = { ...survey };
            const update = {
                ...survey,
                hash: '123456',
                model: `${model1}<!-- updated -->`,
            };

            formCache
                .init(survey)
                .then(() => updateSurvey(update))
                .then((result) => {
                    Object.entries(originalSurvey).forEach(([key, value]) => {
                        if (key in update) {
                            expect(result[key]).to.equal(update[key]);
                        } else {
                            expect(result[key]).to.equal(value);
                        }
                    });

                    expect(result.hash).to.equal(update.hash);
                    expect(result.model).to.equal(update.model);
                })
                .then(done, done);
        });
    });
});
<commit_msg>added tests<commit_after>import formCache from '../../public/js/src/module/form-cache';
import connection from '../../public/js/src/module/connection';
import store from '../../public/js/src/module/store';
import { getLastSavedRecord } from '../../public/js/src/module/last-saved';

/**
 * @typedef { import('sinon').SinonSandbox } SinonSandbox
 */

/**
 * @typedef { import('sinon').SinonFakeTimers } SinonFakeTimers
 */

/**
 * @typedef { import('sinon').SinonStub } SinonStub
 */

/**
 * @typedef {import('../../app/models/record-model').EnketoRecord} EnketoRecord
 */

/**
 * @typedef {import('../../app/models/survey-model').SurveyObject} Survey
 */

/**
 * @typedef {import('../../../../app/models/survey-model').SurveyExternalData} SurveyExternalData
 */

/**
 * @typedef GetFormPartsStubResult
 * @property { string } enketoId
 * @property { string } form
 * @property { string } model
 * @property { string } hash
 */

const parser = new DOMParser();

const url1 = '/path/to/source.png';
const form1 = `<form class="or"><img src="${url1}"/></form>`;
const defaultInstanceData =
    '<data id="modelA"><item>initial</item><meta><instanceID/></meta></data>';
const model1 = `<model><instance>${defaultInstanceData}</instance><instance id="last-saved" src="jr://instance/last-saved"/></model>`;
const hash1 = '12345';

describe('Client Form Cache', () => {
    /** @type {Survey} */
    let survey;

    /** @type {SurveyExternalData} */
    let lastSavedExternalData;

    /** @type {SinonSandbox} */
    let sandbox;

    /** @type {SinonStub} */
    let getFormPartsSpy;

    /** @type {GetFormPartsStubResult} */
    let getFormPartsStubResult;

    /** @type {SinonStub} */
    let getFileSpy;

    /** @type {SinonFakeTimers} */
    let timers;

    beforeEach((done) => {
        const formElement = document.createElement('form');

        formElement.className = 'or';
        document.body.appendChild(formElement);

        survey = {};
        sandbox = sinon.createSandbox();

        // Prevent calls to `_updateCache` after tests complete/stubs are restored
        timers = sandbox.useFakeTimers();

        lastSavedExternalData = {
            id: 'last-saved',
            src: 'jr://instance/last-saved',
            xml: parser.parseFromString(defaultInstanceData, 'text/xml'),
        };

        getFormPartsStubResult = {
            externalData: [lastSavedExternalData],
            form: form1,
            model: model1,
            hash: hash1,
        };

        getFormPartsSpy = sandbox
            .stub(connection, 'getFormParts')
            .callsFake((survey) =>
                Promise.resolve(survey.enketoId)
                    .then((enketoId) => {
                        if (enketoId != null) {
                            return getLastSavedRecord(survey.enketoId);
                        }
                    })
                    .then((lastSavedRecord) => {
                        if (lastSavedRecord != null) {
                            return { lastSavedRecord };
                        }

                        return {};
                    })
                    .then((lastSavedData) => {
                        const formParts = {
                            enketoId: survey.enketoId,
                            ...getFormPartsStubResult,
                            ...lastSavedData,
                        };

                        return formParts;
                    })
            );

        getFileSpy = sandbox.stub(connection, 'getMediaFile').callsFake((url) =>
            Promise.resolve({
                url,
                item: new Blob(['babdf'], {
                    type: 'image/png',
                }),
            })
        );

        store.init().then(done, done);
    });

    afterEach((done) => {
        timers.clearTimeout();
        timers.clearInterval();
        timers.restore();
        sandbox.restore();

        document.body.removeChild(document.querySelector('form.or'));

        store.survey.removeAll().then(done, done);
    });

    it('is loaded', () => {
        expect(formCache).to.be.an('object');
    });

    describe('in empty state', () => {
        it('will call connection.getFormParts to obtain the form parts', (done) => {
            survey.enketoId = '10';
            formCache
                .init(survey)
                .then(() => {
                    expect(getFormPartsSpy).to.have.been.calledWith(survey);
                })
                .then(done, done);
        });

        it('will call connection.getMediaFile to obtain form resources', (done) => {
            survey.enketoId = '20';
            formCache
                .init(survey)
                .then((result) => {
                    const currentForm = document.querySelector('form.or');
                    const form = document
                        .createRange()
                        .createContextualFragment(result.form);

                    currentForm.parentNode.replaceChild(form, currentForm);

                    return formCache.updateMedia(result);
                })
                .then(() => {
                    expect(getFileSpy).to.have.been.calledWith(url1);
                })
                .then(done, done);
        });

        it('will populate the cache upon initialization', (done) => {
            survey.enketoId = '30';
            formCache
                .get(survey)
                .then((result) => {
                    expect(result).to.equal(undefined);

                    return formCache.init(survey);
                })
                .then(() =>
                    // we could also leave this out as formCache.init will return the survey object
                    formCache.get(survey)
                )
                .then((result) => {
                    expect(result.model).to.equal(model1);
                    expect(result.hash).to.equal(hash1);
                    expect(result.enketoId).to.equal(survey.enketoId);
                })
                .then(done, done);
        });

        it('will empty src attributes and copy the original value to a data-offline-src attribute ', (done) => {
            survey.enketoId = '40';
            formCache
                .init(survey)
                .then((result) => {
                    expect(result.form)
                        .to.contain('src=""')
                        .and.to.contain(`data-offline-src="${url1}"`);
                })
                .then(done, done);
        });
    });

    describe('form cache updates', () => {
        /**
         * @param {Partial<GetFormPartsStubResult>} updates
         */
        const updateSurvey = (updates) => {
            // Ensure `_updateCache` receives a new hash indicating it should perform an update
            sandbox
                .stub(connection, 'getFormPartsHash')
                .callsFake(() => Promise.resolve(updates.hash));

            const updatePromise = new Promise((resolve) => {
                setTimeout(resolve, formCache.CACHE_UPDATE_INITIAL_DELAY + 1);
            });

            const originalStoreUpdate = store.survey.update.bind(store.survey);

            sandbox.stub(store.survey, 'update').callsFake((update) =>
                originalStoreUpdate(update).then((result) => {
                    if (update.model === updates.model) {
                        timers.tick(1);
                    }

                    return result;
                })
            );

            timers.tick(formCache.CACHE_UPDATE_INITIAL_DELAY);

            getFormPartsStubResult = { ...getFormPartsStubResult, ...updates };

            // Wait for `_updateCache` to resolve
            return updatePromise.then(() => formCache.get(survey));
        };

        it('updates the survey when the form cache is out of date', (done) => {
            Object.assign(survey, {
                enketoId: '60',
                hash: '1234',
                model: model1,
            });

            const originalSurvey = { ...survey };
            const update = {
                ...survey,
                hash: '123456',
                model: `${model1}<!-- updated -->`,
            };

            formCache
                .init(survey)
                .then(() => updateSurvey(update))
                .then((result) => {
                    Object.entries(originalSurvey).forEach(([key, value]) => {
                        if (key in update) {
                            expect(result[key]).to.equal(update[key]);
                        } else {
                            expect(result[key]).to.equal(value);
                        }
                    });

                    expect(result.hash).to.equal(update.hash);
                    expect(result.model).to.equal(update.model);
                })
                .then(done, done);
        });

        describe('form media (only) cache updates', () => {
            let resultSurvey;

            /** @type {SinonSpy} */
            let storeSurveyUpdateSpy;

            beforeEach((done) => {
                getFileSpy.restore();
                getFileSpy = sandbox.stub(connection, 'getMediaFile').throws();

                storeSurveyUpdateSpy = sandbox.spy(store.survey, 'update');

                survey.enketoId = '200';
                formCache
                    .init(survey)
                    .then((result) => {
                        const currentForm = document.querySelector('form.or');
                        const form = document
                            .createRange()
                            .createContextualFragment(result.form);

                        currentForm.parentNode.replaceChild(form, currentForm);

                        resultSurvey = result;
                        return formCache.updateMedia(result);
                    })
                    .then(() => {
                        getFileSpy.restore();
                        getFileSpy = sandbox
                            .stub(connection, 'getMediaFile')
                            .callsFake((url) =>
                                Promise.resolve({
                                    url,
                                    item: new Blob(['babdf'], {
                                        type: 'image/png',
                                    }),
                                })
                            );
                    })
                    .then(done, done);
            });

            afterEach(() => {
                storeSurveyUpdateSpy.restore();
                getFileSpy.restore();
            });

            it('will re-attempt to download failed media files (at next load) and update the cache', (done) => {
                expect(getFileSpy).to.not.have.been.called;
                expect(storeSurveyUpdateSpy).to.not.have.been.called;

                // simulate re-opening a cached form by calling updateMedia again
                formCache
                    .updateMedia(resultSurvey)
                    .then(() => {
                        // another attempt is made to download the previously-failed media file
                        expect(getFileSpy).to.have.been.calledOnce;
                        expect(getFileSpy).to.have.been.calledWith(url1);
                        // and to cache it when successful
                        expect(storeSurveyUpdateSpy).to.have.been.calledOnce;
                    })
                    .then(done, done);
            });

            it('will not re-attempt to download and update again after the cache is complete', (done) => {
                // simulate re-opening a cached form by calling updateMedia again
                formCache
                    .updateMedia(resultSurvey)
                    .then(formCache.updateMedia)
                    .then(formCache.updateMedia)
                    .then(() => {
                        // Despite 3 calls the media file was only downloaded once,
                        // and the cache was updated only once.
                        expect(getFileSpy).to.have.been.calledOnce;
                        expect(storeSurveyUpdateSpy).to.have.been.calledOnce;
                    })
                    .then(done, done);
            });
        });
    });
});
<|endoftext|>


<commit_before>const { info, error } = require('../src/logger');

const { connect, close } = require('../src/utils/database');
const { schoolModel: School } = require('../src/services/school/model');

const disableStudentTeamCreation = 'disableStudentTeamCreation';

module.exports = {
	up: async function up() {
		await connect();

		const schools = await School.find({}).exec();
		info(`Got ${schools.length} schools.`);
		const result = [];
		for (const school of schools) {
			info(`Migrating ${school.name} (${school._id})...`);
			if (school.features.includes(disableStudentTeamCreation)) {
				school.enableStudentTeamCreation = false;
				school.features.splice(school.features.indexOf(disableStudentTeamCreation), 1);
				result.push(school.save());
			}
		}
		await Promise.all(result);
		info('Done.');
		await close();
	},

	down: async function down() {
		await connect();

		const schools = await School.find({}).exec();
		info(`Got ${schools.length} schools.`);
		const result = [];
		for (const school of schools) {
			info(`Migrating ${school.name} (${school._id})...`);
			if (school.enableStudentTeamCreation === false && !school.features.includes(disableStudentTeamCreation)) {
				school.features.push(disableStudentTeamCreation);
				result.push(school.save());
			}
		}
		await Promise.all(result);
		info('Done.');
		await close();
	},
};
<commit_msg>SC-4075 fixed codacy warning<commit_after>const { info } = require('../src/logger');

const { connect, close } = require('../src/utils/database');
const { schoolModel: School } = require('../src/services/school/model');

const disableStudentTeamCreation = 'disableStudentTeamCreation';

module.exports = {
	up: async function up() {
		await connect();

		const schools = await School.find({}).exec();
		info(`Got ${schools.length} schools.`);
		const result = [];
		for (const school of schools) {
			info(`Migrating ${school.name} (${school._id})...`);
			if (school.features.includes(disableStudentTeamCreation)) {
				school.enableStudentTeamCreation = false;
				school.features.splice(school.features.indexOf(disableStudentTeamCreation), 1);
				result.push(school.save());
			}
		}
		await Promise.all(result);
		info('Done.');
		await close();
	},

	down: async function down() {
		await connect();

		const schools = await School.find({}).exec();
		info(`Got ${schools.length} schools.`);
		const result = [];
		for (const school of schools) {
			info(`Migrating ${school.name} (${school._id})...`);
			if (school.enableStudentTeamCreation === false && !school.features.includes(disableStudentTeamCreation)) {
				school.features.push(disableStudentTeamCreation);
				result.push(school.save());
			}
		}
		await Promise.all(result);
		info('Done.');
		await close();
	},
};
<|endoftext|>


<commit_before>// util.js
// This contains various utilities used throughout the code.

import dnt from '/imports/lib/dnt';
import rx from '/imports/common/rx';
import select2 from '/imports/lib/select2';
import userMsg from '/imports/common/userMsg';

exports.get_username = function (callback) {

    // Callback will be called with one parameter: the username or undefined
    Meteor.call('get_username', function (error, results) {
        if (error || !results) {
            callback(undefined);
        } else {
            callback(results);
        }
    });
}

exports.isValidFileName = function (name) {
    var index = name.search(/[^A-Za-z0-9_\-\.]/g);
    return (index < 0);
}

exports.clean_file_name = function (dirty) {

    // Make a directory or file name out of some string
    // Valid characters:
    //     a-z, A-Z, 0-9, dash (-), dot (.), underscore (_)
    // All other characters are replaced with underscores.

    if (!dirty) { return undefined; }

    return dirty.replace(/[^A-Za-z0-9_\-\.]/g, "_");
}

exports.getDataType = function (layerName) {
    let dataType = layers[layerName.dataType];
    if (dataType === undefined) {
        if (exports.is_binary(layerName)){
            dataType = "binary"
        } else if (exports.is_continuous(layerName)){
            dataType = "continuous"
        } else if (exports.is_categorical(layerName)){
            dataType = "categorical"
        } else {
            throw 'No dataType for attribute: ' + layerName;
        }
    }
    return dataType;
}

exports.is_continuous = function (layer_name) {
    return (ctx.cont_layers.indexOf(layer_name.toString()) > -1);
}

exports.is_categorical = function (layer_name) {
        var is_cat;
        if ( _.isUndefined(layer_name) ){
            is_cat = false
        } else {
            is_cat = (ctx.cat_layers.indexOf(layer_name.toString()) > -1)
}
        return is_cat;
    }

exports.is_cat_or_bin = function (layer_name){
        return (exports.is_categorical(layer_name) ||
            exports.is_binary(layer_name));
    }

exports.is_binary = function (layer_name) {
        var is_bin;
        if ( _.isUndefined(layer_name) ){
            is_bin = false
        } else {
            is_bin = (ctx.bin_layers.indexOf(layer_name.toString()) > -1)
}
        return is_bin;
    }

exports.round = function (x, n) {
    if (!n) {
        n = 0;
    }
    var m = Math.pow(10, n);
    return Math.round(x * m) / m;
}

exports.getHumanProject = function (project) {

    // Transform a project from dir structure to display for humans
    return project.slice(0, -1);
}

export function hideAllSnakes () {
    rx.set('snake.map.hide');
    rx.set('snake.project.hide');
    rx.set('snake.shortlist.hide');
}

exports.mapNotFoundNotify = function (more, stackTrace) {
    if (!ctx.mapNotFoundNotified) {
        ctx.mapNotFoundNotified = true;

        var name = exports.getHumanProject(ctx.project),
            msg = 'Map "' + name + '" not found.\nPlease select another';
        if (Meteor.user() === null) {
            msg += ' or sign in'
        }
        msg += '. ' + more;
        userMsg.error(msg, {logStr: stackTrace});
        hideAllSnakes();
    }
}

exports.parseTsv = function (data) {

    if (data.length < 1) {
        return [];
    }

    // Separate the data into an array of rows
    var rows = data.split('\n'),

    // Separate each row into an array of values
    parsed = _.map(rows, function(row) {
        return row.split('\t');
    });

    // Remove any empty row left from the new-line split
    if (parsed[parsed.length-1].length === 1 &&
            parsed[parsed.length-1][0] === '') {
        parsed.pop();
    }
    return parsed;
}

exports.removeFromDataTypeList = function (layer_name) {

    // Remove this layer from the appropriate data type list
    var index = ctx.bin_layers.indexOf(layer_name);
    if (index > -1) {
        ctx.bin_layers.splice(index, 1);
    } else {
        index = ctx.cat_layers.indexOf(layer_name);
        if (index > -1) {
            ctx.cat_layers.splice(index, 1);
        } else {
            index = ctx.cont_layers.indexOf(layer_name);
            if (index > -1) {
                ctx.cont_layers.splice(index, 1);
            }
        }
    }
}

function setHeightSelect2 ($el) {

    // Make the bottom of the list no longer than the main window
    $el.parent().on('select2-open', function () {
        var results = $('#select2-drop .select2-results');
        results.css(
            'max-height', $(window).height() - results.offset().top - 15);
    });
}

exports.createOurSelect2 = function ($el, optsIn, defaultSelection) {

    // Create a select2 drop-down.

    // Including our favorite options
    var opts = {
        dropdownAutoWidth: true,
        minimumResultsForSearch: -1,
    };

    // The caller's options override our favorite options
    for (var key in optsIn) {
        if (optsIn.hasOwnProperty(key)) {
            opts[key] = optsIn[key];
        }
    }

    // Create the select2 object
    $el.select2(opts);

    // Set the default selection
    if (defaultSelection) {
        $el.select2('val', defaultSelection);
    }

    setHeightSelect2($el);
}

exports.addToDataTypeList = function (layer_name, dataType) {
    if (dataType === 'binary') {
        ctx.bin_layers.push(layer_name);
    } else if (dataType === 'categorical') {
        ctx.cat_layers.push(layer_name);
    } else {
        ctx.cont_layers.push(layer_name);
    }
}

exports.googleAnalytics = function () {

    // Before including google analytics, respect the user's wish not to be
    // tracked if she set this in her browser preferences.
    if (!dnt._dntEnabled()) {
        /* eslint-disable */
        window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
        ga('create', 'UA-76132249-2', 'auto');
        ga('send', 'pageview');
        /* eslint-enable */
    }
}

<commit_msg>If google charts is slow, don't hold up the app<commit_after>// util.js
// This contains various utilities used throughout the code.

import dnt from '/imports/lib/dnt';
import rx from '/imports/common/rx';
import select2 from '/imports/lib/select2';
import userMsg from '/imports/common/userMsg';

exports.get_username = function (callback) {

    // Callback will be called with one parameter: the username or undefined
    Meteor.call('get_username', function (error, results) {
        if (error || !results) {
            callback(undefined);
        } else {
            callback(results);
        }
    });
}

exports.isValidFileName = function (name) {
    var index = name.search(/[^A-Za-z0-9_\-\.]/g);
    return (index < 0);
}

exports.clean_file_name = function (dirty) {

    // Make a directory or file name out of some string
    // Valid characters:
    //     a-z, A-Z, 0-9, dash (-), dot (.), underscore (_)
    // All other characters are replaced with underscores.

    if (!dirty) { return undefined; }

    return dirty.replace(/[^A-Za-z0-9_\-\.]/g, "_");
}

exports.getDataType = function (layerName) {
    let dataType = layers[layerName.dataType];
    if (dataType === undefined) {
        if (exports.is_binary(layerName)){
            dataType = "binary"
        } else if (exports.is_continuous(layerName)){
            dataType = "continuous"
        } else if (exports.is_categorical(layerName)){
            dataType = "categorical"
        } else {
            throw 'No dataType for attribute: ' + layerName;
        }
    }
    return dataType;
}

exports.is_continuous = function (layer_name) {
    return (ctx.cont_layers.indexOf(layer_name.toString()) > -1);
}

exports.is_categorical = function (layer_name) {
        var is_cat;
        if ( _.isUndefined(layer_name) ){
            is_cat = false
        } else {
            is_cat = (ctx.cat_layers.indexOf(layer_name.toString()) > -1)
}
        return is_cat;
    }

exports.is_cat_or_bin = function (layer_name){
        return (exports.is_categorical(layer_name) ||
            exports.is_binary(layer_name));
    }

exports.is_binary = function (layer_name) {
        var is_bin;
        if ( _.isUndefined(layer_name) ){
            is_bin = false
        } else {
            is_bin = (ctx.bin_layers.indexOf(layer_name.toString()) > -1)
}
        return is_bin;
    }

exports.round = function (x, n) {
    if (!n) {
        n = 0;
    }
    var m = Math.pow(10, n);
    return Math.round(x * m) / m;
}

exports.getHumanProject = function (project) {

    // Transform a project from dir structure to display for humans
    return project.slice(0, -1);
}

export function hideAllSnakes () {
    rx.set('snake.map.hide');
    rx.set('snake.project.hide');
    rx.set('snake.shortlist.hide');
}

exports.mapNotFoundNotify = function (more, stackTrace) {
    if (!ctx.mapNotFoundNotified) {
        ctx.mapNotFoundNotified = true;

        var name = exports.getHumanProject(ctx.project),
            msg = 'Map "' + name + '" not found.\nPlease select another';
        if (Meteor.user() === null) {
            msg += ' or sign in'
        }
        msg += '. ' + more;
        userMsg.error(msg, {logStr: stackTrace});
        hideAllSnakes();
    }
}

exports.parseTsv = function (data) {

    if (data.length < 1) {
        return [];
    }

    // Separate the data into an array of rows
    var rows = data.split('\n'),

    // Separate each row into an array of values
    parsed = _.map(rows, function(row) {
        return row.split('\t');
    });

    // Remove any empty row left from the new-line split
    if (parsed[parsed.length-1].length === 1 &&
            parsed[parsed.length-1][0] === '') {
        parsed.pop();
    }
    return parsed;
}

exports.removeFromDataTypeList = function (layer_name) {

    // Remove this layer from the appropriate data type list
    var index = ctx.bin_layers.indexOf(layer_name);
    if (index > -1) {
        ctx.bin_layers.splice(index, 1);
    } else {
        index = ctx.cat_layers.indexOf(layer_name);
        if (index > -1) {
            ctx.cat_layers.splice(index, 1);
        } else {
            index = ctx.cont_layers.indexOf(layer_name);
            if (index > -1) {
                ctx.cont_layers.splice(index, 1);
            }
        }
    }
}

function setHeightSelect2 ($el) {

    // Make the bottom of the list no longer than the main window
    $el.parent().on('select2-open', function () {
        var results = $('#select2-drop .select2-results');
        results.css(
            'max-height', $(window).height() - results.offset().top - 15);
    });
}

exports.createOurSelect2 = function ($el, optsIn, defaultSelection) {

    // Create a select2 drop-down.

    // Including our favorite options
    var opts = {
        dropdownAutoWidth: true,
        minimumResultsForSearch: -1,
    };

    // The caller's options override our favorite options
    for (var key in optsIn) {
        if (optsIn.hasOwnProperty(key)) {
            opts[key] = optsIn[key];
        }
    }

    // Create the select2 object
    $el.select2(opts);

    // Set the default selection
    if (defaultSelection) {
        $el.select2('val', defaultSelection);
    }

    setHeightSelect2($el);
}

exports.addToDataTypeList = function (layer_name, dataType) {
    if (dataType === 'binary') {
        ctx.bin_layers.push(layer_name);
    } else if (dataType === 'categorical') {
        ctx.cat_layers.push(layer_name);
    } else {
        ctx.cont_layers.push(layer_name);
    }
}

exports.googleAnalytics = function () {

    // Before including google analytics, respect the user's wish not to be
    // tracked if she set this in her browser preferences.
    if (!dnt._dntEnabled() || (window.ga && ga.loaded)) {
        /* eslint-disable */
        window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
        ga('create', 'UA-76132249-2', 'auto');
        ga('send', 'pageview');
        /* eslint-enable */
    } else {
        document.location = url;
    }
}

<|endoftext|>


<commit_before>var theme = null;

if(window.sessionStorage){
	theme = window.sessionStorage['jslet-theme'];
}
if(!theme) {
	theme = 'default';
}
requirejs.config({
    paths: {
        requirejs: 'requirejs',
		//jquery
        jquery: '../lib/jquery/jquery-2.2.3.min',
        //Jslet
        "jslet-locale": '../dist/locale/zh-cn/jslet-locale.min',
        "jslet-data": '../dist/jslet-data.min',
        "jslet-ui": '../dist/jslet-ui',
        datasetmetastore: 'common/datasetmetastore',
		//echart
		'echarts': '../lib/chart/echarts',

        //xlxs
        'jszip': '../lib/xlsx/jszip',
        'xlsx': '../lib/xlsx/xlsx.min',
        'filesaver': '../lib/xlsx/FileSaver.min'
    },
    map: {
        '*': {
            'css': 'lib/requirejs/css.min',
        }
    },
    shim: {
    	'xlsx': ['filesaver', 'jszip-wrap'],

    	'jslet-data': ['jquery', 'xlsx'],
    	'jslet-ui': ['jslet-data', 'bootstrapcss', 'fontawesome', 'css!../dist/asset/jslet.css', 'css!../dist/asset/' + theme + '/jslet-theme.css']
    },
	waitSeconds: 0
});

define('jszip-wrap', ['jszip'], function(jszip) {
	window.JSZip = jszip;
});

define('bootstrapcss', ['css!../lib/bootstrap-3.2.0-dist/css/bootstrap.min.css',
                        'css!../dist/asset/' + theme + '/bootstrap-theme.css']);

define('fontawesome', ['css!../lib/fontawesome/css/font-awesome.min.css']);

define('jslet', ['echarts', 'jslet-data', 'jslet-ui'], function(ec) {
	jslet.global.echarts = ec;
});

require(['bootstrapcss', 'fontawesome', 'css!../dist/asset/jslet.css',
         'css!../dist/asset/' + theme + '/jslet-theme.css',
         'jslet']);
<commit_msg>Initial Commit<commit_after>var theme = null;

if(window.sessionStorage){
	theme = window.sessionStorage['jslet-theme'];
}
if(!theme) {
	theme = 'default';
}
requirejs.config({
    paths: {
        requirejs: 'requirejs',
		//jquery
        jquery: '../lib/jquery/jquery-2.2.3.min',
        //Jslet
        "jslet-locale": '../dist/locale/zh-cn/jslet-locale.min',
        "jslet-data": '../dist/jslet-data.min',
        "jslet-ui": '../dist/jslet-ui.min',
        datasetmetastore: 'common/datasetmetastore',
		//echart
		'echarts': '../lib/chart/echarts',

        //xlxs
        'jszip': '../lib/xlsx/jszip',
        'xlsx': '../lib/xlsx/xlsx.min',
        'filesaver': '../lib/xlsx/FileSaver.min'
    },
    map: {
        '*': {
            'css': 'lib/requirejs/css.min',
        }
    },
    shim: {
    	'xlsx': ['filesaver', 'jszip-wrap'],

    	'jslet-data': ['jquery', 'xlsx'],
    	'jslet-ui': ['jslet-data', 'bootstrapcss', 'fontawesome', 'css!../dist/asset/jslet.css', 'css!../dist/asset/' + theme + '/jslet-theme.css']
    },
	waitSeconds: 0
});

define('jszip-wrap', ['jszip'], function(jszip) {
	window.JSZip = jszip;
});

define('bootstrapcss', ['css!../lib/bootstrap-3.2.0-dist/css/bootstrap.min.css',
                        'css!../dist/asset/' + theme + '/bootstrap-theme.css']);

define('fontawesome', ['css!../lib/fontawesome/css/font-awesome.min.css']);

define('jslet', ['echarts', 'jslet-data', 'jslet-ui'], function(ec) {
	jslet.global.echarts = ec;
});

require(['bootstrapcss', 'fontawesome', 'css!../dist/asset/jslet.css',
         'css!../dist/asset/' + theme + '/jslet-theme.css',
         'jslet']);
<|endoftext|>


<commit_before>function example () {

  // Configuraiton
  var
    H = humblevis,
    D = financeData,
    jsonData = D.summaryTicks,

    container = document.getElementById('demo'),

    priceOptions = {
      name    : 'price',
      data    : D.price,
      flotr   : {
        'lite-lines' : {
          lineWidth : 1,
          show : true,
          fill : true,
          fillOpacity : .2
        },
        mouse : {
          track: true,
          trackY: false,
          trackAll: true,
          sensibility: 1,
          trackDecimals: 4,
          trackFormatter: function (o) {
            var data = jsonData[o.nearest.x];
            return data.date + " Price: " + data.close + " Vol: " + data.volume;
          },
          position: 'ne'
        },
        yaxis : {
          noTicks : 3,
          showLabels : true,
          min : 0,
          tickFormatter : function (n) {
            return (n == this.max ? false : '$'+n);
          }
        }
      },
      processData : processData
    },

    volumeOptions = {
      name    : 'volume',
      data    : D.volume,
      flotr   : {
        whiskers : {
          show : true
        },
        mouse: {
          track: true,
          trackY: false,
          trackAll: true,
          position: 'ne',
          trackDecimals: 0
        }
      },
      processData : processData
    },

    summaryOptions = {
      name    : 'summary',
      data    : D.price,
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2,
          fillBorder : true
        },
        xaxis : {
          noTicks: 5,
          showLabels : true,
          tickFormatter : function (n) {
            return jsonData[n].date.split(' ')[2];
            return (parseInt(n) === 0 ? false : jsonData[n].date.split(' ')[2]);
          }
        },
        yaxis : {
          autoscale : true,
          autoscaleMargin : .1
        },
        handles   : { show : true },
        selection : { mode : 'x'},
        grid : {
          verticalLines : false
        }
      }
    },

    connectionOptions = {
      name : 'connection',
      drawing : humblevis.QuadraticDrawing
    },

    vis, price, volume, summary, selection, hit, connection;

  function processData (options) {
    options.preprocessor
      .bound(options.min, options.max)
      .subsampleMinMax(800)
  }

  // Application

  vis = new H.Visualization();
  price = new H.Child(priceOptions);
  volume = new H.Child(volumeOptions);
  connection = new H.Child(connectionOptions);
  summary = new H.Child(summaryOptions);
  selection = new H.Interaction({leader : summary});
  hit = new H.Interaction();

  vis
    .add(price)
    .add(volume)
    .add(connection)
    .add(summary)
    .render(container);

  selection
    .follower(price)
    .follower(volume)
    .follower(connection)
    .add(H.action.selection);

  summary.api.flotr.selection.setSelection({ y1 : 0, y2 : 0, x1 : 50, x2 : 150});

  hit
    .group([price, volume])
    .add(H.action.hit);
}

<commit_msg>Switched to trigger select through adapter.<commit_after>function example () {

  // Configuraiton
  var
    H = humblevis,
    D = financeData,
    jsonData = D.summaryTicks,

    container = document.getElementById('demo'),

    priceOptions = {
      name    : 'price',
      data    : D.price,
      flotr   : {
        'lite-lines' : {
          lineWidth : 1,
          show : true,
          fill : true,
          fillOpacity : .2
        },
        mouse : {
          track: true,
          trackY: false,
          trackAll: true,
          sensibility: 1,
          trackDecimals: 4,
          trackFormatter: function (o) {
            var data = jsonData[o.nearest.x];
            return data.date + " Price: " + data.close + " Vol: " + data.volume;
          },
          position: 'ne'
        },
        yaxis : {
          noTicks : 3,
          showLabels : true,
          min : 0,
          tickFormatter : function (n) {
            return (n == this.max ? false : '$'+n);
          }
        }
      },
      processData : processData
    },

    volumeOptions = {
      name    : 'volume',
      data    : D.volume,
      flotr   : {
        whiskers : {
          show : true
        },
        mouse: {
          track: true,
          trackY: false,
          trackAll: true,
          position: 'ne',
          trackDecimals: 0
        }
      },
      processData : processData
    },

    summaryOptions = {
      name    : 'summary',
      data    : D.price,
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2,
          fillBorder : true
        },
        xaxis : {
          noTicks: 5,
          showLabels : true,
          tickFormatter : function (n) {
            return jsonData[n].date.split(' ')[2];
            return (parseInt(n) === 0 ? false : jsonData[n].date.split(' ')[2]);
          }
        },
        yaxis : {
          autoscale : true,
          autoscaleMargin : .1
        },
        handles   : { show : true },
        selection : { mode : 'x'},
        grid : {
          verticalLines : false
        }
      }
    },

    connectionOptions = {
      name : 'connection',
      drawing : humblevis.QuadraticDrawing
    },

    vis, price, volume, summary, selection, hit, connection;

  function processData (options) {
    options.preprocessor
      .bound(options.min, options.max)
      .subsampleMinMax(800)
  }

  // Application

  vis = new H.Visualization();
  price = new H.Child(priceOptions);
  volume = new H.Child(volumeOptions);
  connection = new H.Child(connectionOptions);
  summary = new H.Child(summaryOptions);
  selection = new H.Interaction({leader : summary});
  hit = new H.Interaction();

  vis
    .add(price)
    .add(volume)
    .add(connection)
    .add(summary)
    .render(container);

  selection
    .follower(price)
    .follower(volume)
    .follower(connection)
    .add(H.action.selection);

  summary.api.adapter.trigger(summary, 'select', {
    data : {
      x : {
        min : 0,
        max : 100
      },
      y : {
        min : 0,
        max : 0
      }
    }
  });

  hit
    .group([price, volume])
    .add(H.action.hit);
}

<|endoftext|>


<commit_before>angular.module('profile.controllers', [])

.controller('ProfileCtrl', function($scope, $stateParams, $state, $ionicHistory, User, CandidatesFactory, MatchesFactory, SkippedFactory, PlaceFactory, RoommateFactory, ProfileFactory) {
  $scope.User = User;
  $scope.fbId = User.fbid;

  //console.log(CandidatesFactory.all());
  $scope.candidates = CandidatesFactory.all();
  $scope.currentCandidate = angular.copy($scope.candidates[0]);

  switch($stateParams.type){
    case 'swipe':
      $scope.profile = CandidatesFactory.getFirst();
      break;
    case 'matches':
      console.log("state.params ", $stateParams);
      $scope.profile = MatchesFactory.get($stateParams.id);
      console.log('scope profile in matches profile ', $scope.profile)
      break;
    default:
      $scope.profile = User;
      // $scope.profile.profile = PlaceFactory.all();
      // $scope.profile.people = RoommateFactory.all();
      // $scope.profile.userInfo = ProfileFactory.all();
      break;
  }
  // get stored location info
  // ??? is this being used anymore? (Daniel: Apr 12)
  $scope.myGoBack = function() {
      $ionicHistory.goBack();
  };
  if ($scope.profile.profile) {
   $scope.keywords = $scope.profile.profile.keywords.join(", ");
  }
  $scope.profile.type = $stateParams.type;
  $scope.profile.match = true;

  $scope.candidateSwipe =  function (match){
    //console.log(match,'ALL',CandidatesFactory.all());
    CandidatesFactory.removeFirst();
    if (match) {
      //Once server is up, this will be a POST request to the server
      User = MatchesFactory.add($scope.currentCandidate, $scope.User);
      MatchesFactory.saveAllMatches(User);
      MatchesFactory.updateMatchedUsers();
    } else {
      //Perhaps we just need to do a PUT request to the server here?
      SkippedFactory.add($scope.currentCandidate);
    }
    $scope.currentCandidate = angular.copy($scope.candidates[0]);
    //console.log('ALL',CandidatesFactory.all(),'FIRST',CandidatesFactory.getFirst());
    $state.go('tab.swipe');

  };
});

<commit_msg>(feat) Update current user's matched/liked arrays from profile view<commit_after>angular.module('profile.controllers', [])

.controller('ProfileCtrl', function($scope, $stateParams, $state, $ionicHistory, User, CandidatesFactory, MatchesFactory, SkippedFactory, PlaceFactory, RoommateFactory, ProfileFactory) {
  $scope.User = User;
  $scope.fbId = User.fbid;

  //console.log(CandidatesFactory.all());
  $scope.candidates = CandidatesFactory.all();
  $scope.currentCandidate = angular.copy($scope.candidates[0]);

  switch($stateParams.type){
    case 'swipe':
      $scope.profile = CandidatesFactory.getFirst();
      break;
    case 'matches':
      console.log("state.params ", $stateParams);
      $scope.profile = MatchesFactory.get($stateParams.id);
      console.log('scope profile in matches profile ', $scope.profile)
      break;
    default:
      $scope.profile = User;
      // $scope.profile.profile = PlaceFactory.all();
      // $scope.profile.people = RoommateFactory.all();
      // $scope.profile.userInfo = ProfileFactory.all();
      break;
  }
  // get stored location info
  // ??? is this being used anymore? (Daniel: Apr 12)
  $scope.myGoBack = function() {
      $ionicHistory.goBack();
  };
  if ($scope.profile.profile) {
   $scope.keywords = $scope.profile.profile.keywords.join(", ");
  }
  $scope.profile.type = $stateParams.type;
  $scope.profile.match = true;

  $scope.candidateSwipe =  function (match){
    //console.log(match,'ALL',CandidatesFactory.all());
    CandidatesFactory.removeFirst();
    if (match) {
      //Once server is up, this will be a POST request to the server
      User = MatchesFactory.add($scope.currentCandidate, $scope.User);
      MatchesFactory.saveAllMatches(User)
        .then(function(res){
          console.log("current user res from saveAllMatches", res);
          $scope.User = User = res;
        });
      MatchesFactory.updateMatchedUsers()
        .then(function(res) {
          console.log("last candidate from updateMatchedUsers ", res)
          // $scope.currentCandidate = res.data;
        })
    } else {
      //Perhaps we just need to do a PUT request to the server here?
      SkippedFactory.add($scope.currentCandidate);
    }
    $scope.currentCandidate = angular.copy($scope.candidates[0]);
    //console.log('ALL',CandidatesFactory.all(),'FIRST',CandidatesFactory.getFirst());
    $state.go('tab.swipe');

  };
});

<|endoftext|>


<commit_before>/**
 * jQuery DOMEC (DOM Elements Creator) 1.0.1
 * http://code.google.com/p/jquery-domec
 *
 * Copyright (c) 2008-2009 Lukasz Rajchel (lukasz@rajchel.pl | http://lukasz.rajchel.pl)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Documentation	:	http://code.google.com/p/jquery-domec/wiki/Documentation
 * Changelog		:	http://code.google.com/p/jquery-domec/wiki/Changelog
 */

/*jslint bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true,
plusplus: true, regexp: true, undef: true, white: true, indent: 4 */
/*global document, jQuery*/
/*members DOMEC, addAttributes, addChildren, append, attr, create, createElement , extend,
length, propertyIsEnumerable, splice, text, toString*/
(function ($) {

	// DOMEC Core class
	$.DOMEC = (function () {
		// variables declaration
		var typeOf, Element;

		// returns type of value
		typeOf = function (value) {
			var type = typeof value;
			if (type === 'object') {
				if (value === null) {
					type = 'null';
				} else if (typeof value.length === 'number' &&
					typeof value.splice === 'function' &&
					!(value.propertyIsEnumerable('length'))) {
					type = 'array';
				}

			}
			return type;
		};

		// DOM element
		Element = (function () {
			return {
				// create element
				create: function (name, root) {
					// set default root if undefined
					if (root === undefined || root === null) {
						root = document;
					}

					if (typeOf(root) === 'object' && typeOf(name) === 'string') {
						return $(root.createElement(name));
					}

					return undefined;
				},
				// add attributes
				addAttributes: function (elem, attributes) {
					if (typeOf(attributes) === 'object') {
						for (var key in attributes) {
							if (typeOf(attributes[key]) === 'string' ||
								typeOf(attributes[key]) === 'number') {
								elem.attr(key, attributes[key]);
							}
						}
					}
				},
				// add child elements
				addChildren: function (elem, children) {
					if (children !== undefined && children !== null) {
						if (typeOf(children) === 'array') {
							for (var i = 0; i < children.length; i += 1) {
								elem.append(children[i]);
							}
						} else if (typeOf(children) === 'object') {
							elem.append(children);
						} else {
							elem.text(children.toString());
						}
					}
				}
			};
		}());

		// DOMEC public members
		return {
			create: function (name, attributes, children, root) {
				var elem = Element.create(name, root);

				if (elem !== undefined) {
					Element.addAttributes(elem, attributes);
					Element.addChildren(elem, children);
				}

				return elem;
			}
		};
	}());

 	// register jQuery extension
	$.extend({
		create: $.DOMEC.create
	});

}(jQuery));<commit_msg>performance improvements<commit_after>/**
 * jQuery DOMEC (DOM Elements Creator) 1.0.1
 * http://code.google.com/p/jquery-domec
 *
 * Copyright (c) 2008-2009 Lukasz Rajchel (lukasz@rajchel.pl | http://lukasz.rajchel.pl)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Documentation	:	http://code.google.com/p/jquery-domec/wiki/Documentation
 * Changelog		:	http://code.google.com/p/jquery-domec/wiki/Changelog
 */

/*jslint bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true,
plusplus: true, regexp: true, undef: true, white: true, indent: 4 */
/*global document, jQuery*/
/*members DOMEC, addAttributes, addChildren, append, attr, create, createElement, extend,
hasOwnProperty, indexOf, length, propertyIsEnumerable, prototype, registerIndexOf, splice, text,
toString*/
(function ($) {

	$.DOMEC = (function () {
		var typeOf, Element, Hacks;

		typeOf = function (value) {
			var type = typeof value;
			if (type === 'object') {
				if (value === null) {
					type = 'null';
				} else if (typeof value.length === 'number' &&
					typeof value.splice === 'function' &&
					!(value.propertyIsEnumerable('length'))) {
					type = 'array';
				}

			}
			return type;
		};

		Hacks = (function () {
			return {
				registerIndexOf: function () {
					if (Array.prototype.indexOf === undefined) {
						Array.prototype.indexOf = function (object) {
							for (var i = 0; i < this.length; i += 1) {
								if (this[i] === object) {
									return i;
								}
							}
							return -1;
						};
					}
				}
			};
		}());

		Element = (function () {
			return {
				create: function (name, root) {
					if (root === undefined || root === null) {
						root = document;
					}

					if (typeOf(root) === 'object' && typeOf(name) === 'string') {
						return $(root.createElement(name));
					}

					return undefined;
				},

				addAttributes: function (elem, attributes) {
					var key, item;
					if (typeOf(attributes) === 'object') {
						for (key in attributes) {
							if (attributes.hasOwnProperty(key)) {
								item = attributes[key];
								if (['string', 'number'].indexOf(typeOf(item)) !== -1) {
									elem.attr(key, item);
								}
							}
						}
					}
				},

				addChildren: function (elem, children) {
					if (children !== undefined && children !== null) {
						var type = typeOf(children), length, i;
						if (type === 'array') {
							length = children.length;
							for (i = 0; i < length; i += 1) {
								elem.append(children[i]);
							}
						} else if (type === 'object') {
							elem.append(children);
						} else {
							elem.text(children.toString());
						}
					}
				}
			};
		}());

		return {
			create: function (name, attributes, children, root) {
				Hacks.registerIndexOf();

				var elem = Element.create(name, root);

				if (elem !== undefined) {
					Element.addAttributes(elem, attributes);
					Element.addChildren(elem, children);
				}

				return elem;
			}
		};
	}());

 	$.extend({
		create: $.DOMEC.create
	});

}(jQuery));<|endoftext|>


<commit_before>// Peity jQuery plugin version 1.1.2
// (c) 2013 Ben Pickles
//
// http://benpickles.github.com/peity
//
// Released under MIT license.
(function($, document, Math, devicePixelRatio) {
  var canvasSupported = document.createElement("canvas").getContext

  var peity = $.fn.peity = function(type, options) {
    if (canvasSupported) {
      this.each(function() {
        var $this = $(this)
        var chart = $this.data("peity")

        if (chart) {
          if (type) chart.type = type
          $.extend(chart.opts, options)
          chart.draw()
        } else {
          var defaults = peity.defaults[type]
          var data = {}

          $.each($this.data(), function(name, value) {
            if (name in defaults) data[name] = value
          })

          var opts = $.extend({}, defaults, data, options)
          var chart = new Peity($this, type, opts)
          chart.draw()

          $this
            .change(function() { chart.draw() })
            .data("peity", chart)
        }
      });
    }

    return this;
  };

  var Peity = function($el, type, opts) {
    this.$el = $el
    this.type = type
    this.opts = opts
  }

  var PeityPrototype = Peity.prototype

  PeityPrototype.colours = function() {
    var colours = this.opts.colours
    var func = colours

    if (!$.isFunction(func)) {
      func = function(_, i) {
        return colours[i % colours.length]
      }
    }

    return func
  }

  PeityPrototype.draw = function() {
    peity.graphers[this.type].call(this, this.opts)
  }

  PeityPrototype.prepareCanvas = function(width, height) {
    var canvas = this.canvas

    if (canvas) {
      this.context.clearRect(0, 0, canvas.width, canvas.height)
    } else {
      var $canvas = $("<canvas>").css({
        height: height,
        width: width
      }).data("peity", this)

      this.canvas = canvas = $canvas[0]
      this.context = canvas.getContext("2d")
      this.$el.hide().before(canvas)

      $canvas.attr({
        height: $canvas.height() * devicePixelRatio,
        width: $canvas.width() * devicePixelRatio
      })
    }

    return canvas
  }

  PeityPrototype.values = function() {
    return $.map(this.$el.text().split(this.opts.delimiter), function(value) {
      return parseFloat(value)
    })
  }

  peity.defaults = {}
  peity.graphers = {}

  peity.register = function(type, defaults, grapher) {
    this.defaults[type] = defaults
    this.graphers[type] = grapher
  }

  peity.register(
    'pie',
    {
      colours: ["#ff9900", "#fff4dd", "#ffc66e"],
      delimiter: null,
      diameter: 16
    },
    function(opts) {
      if (!opts.delimiter) {
        var delimiter = this.$el.text().match(/[^0-9\.]/)
        opts.delimiter = delimiter ? delimiter[0] : ","
      }

      var values = this.values()

      if (opts.delimiter == "/") {
        var v1 = values[0]
        var v2 = values[1]
        values = [v1, v2 - v1]
      }

      var i = 0
      var length = values.length
      var sum = 0

      for (; i < length; i++) {
        sum += values[i]
      }

      var canvas = this.prepareCanvas(opts.width || opts.diameter, opts.height || opts.diameter)
      var context = this.context
      var width = canvas.width
      var height = canvas.height
      var radius = Math.min(width, height) / 2
      var pi = Math.PI
      var colours = this.colours()

      context.save()
      context.translate(width / 2, height / 2)
      context.rotate(-pi / 2)

      for (i = 0; i < length; i++) {
        var value = values[i]
        var slice = (value / sum) * pi * 2

        context.beginPath()
        context.moveTo(0, 0)
        context.arc(0, 0, radius, 0, slice, false)
        context.fillStyle = colours.call(this, value, i, values)
        context.fill()
        context.rotate(slice)
      }

      context.restore()
    }
  )

  peity.register(
    "line",
    {
      colour: "#c6d9fd",
      strokeColour: "#4d89f9",
      strokeWidth: 1,
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var values = this.values()
      if (values.length == 1) values.push(values[0])
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = this.prepareCanvas(opts.width, opts.height)
      var context = this.context
      var width = canvas.width
      var height = canvas.height
      var xQuotient = width / (values.length - 1)
      var yQuotient = height / (max - min)

      var coords = [];
      var i;

      context.beginPath();
      context.moveTo(0, height + (min * yQuotient))

      for (i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        coords.push({ x: x, y: y });
        context.lineTo(x, y);
      }

      context.lineTo(width, height + (min * yQuotient))
      context.fillStyle = opts.colour;
      context.fill();

      if (opts.strokeWidth) {
        context.beginPath();
        context.moveTo(0, coords[0].y);
        for (i = 0; i < coords.length; i++) {
          context.lineTo(coords[i].x, coords[i].y);
        }
        context.lineWidth = opts.strokeWidth * devicePixelRatio;
        context.strokeStyle = opts.strokeColour;
        context.stroke();
      }
    }
  );

  peity.register(
    'bar',
    {
      colours: ["#4D89F9"],
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      spacing: devicePixelRatio,
      width: 32
    },
    function(opts) {
      var values = this.values()
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = this.prepareCanvas(opts.width, opts.height)
      var context = this.context

      var width = canvas.width
      var height = canvas.height
      var yQuotient = height / (max - min)
      var space = opts.spacing
      var xQuotient = (width + space) / values.length
      var colours = this.colours()

      for (var i = 0; i < values.length; i++) {
        var value = values[i]
        var y = height - (yQuotient * (value - min))
        var h

        if (value == 0) {
          if (min >= 0 || max > 0) y -= 1
          h = 1
        } else {
          h = yQuotient * values[i]
        }

        context.fillStyle = colours.call(this, value, i, values)
        context.fillRect(i * xQuotient, y, xQuotient - space, h)
      }
    }
  );
})(jQuery, document, Math, window.devicePixelRatio || 1);
<commit_msg>Recalculate and apply canvas dimensions when redrawing.<commit_after>// Peity jQuery plugin version 1.1.2
// (c) 2013 Ben Pickles
//
// http://benpickles.github.com/peity
//
// Released under MIT license.
(function($, document, Math, devicePixelRatio) {
  var canvasSupported = document.createElement("canvas").getContext

  var peity = $.fn.peity = function(type, options) {
    if (canvasSupported) {
      this.each(function() {
        var $this = $(this)
        var chart = $this.data("peity")

        if (chart) {
          if (type) chart.type = type
          $.extend(chart.opts, options)
          chart.draw()
        } else {
          var defaults = peity.defaults[type]
          var data = {}

          $.each($this.data(), function(name, value) {
            if (name in defaults) data[name] = value
          })

          var opts = $.extend({}, defaults, data, options)
          var chart = new Peity($this, type, opts)
          chart.draw()

          $this
            .change(function() { chart.draw() })
            .data("peity", chart)
        }
      });
    }

    return this;
  };

  var Peity = function($el, type, opts) {
    this.$el = $el
    this.type = type
    this.opts = opts
  }

  var PeityPrototype = Peity.prototype

  PeityPrototype.colours = function() {
    var colours = this.opts.colours
    var func = colours

    if (!$.isFunction(func)) {
      func = function(_, i) {
        return colours[i % colours.length]
      }
    }

    return func
  }

  PeityPrototype.draw = function() {
    peity.graphers[this.type].call(this, this.opts)
  }

  PeityPrototype.prepareCanvas = function(width, height) {
    var canvas = this.canvas
    var $canvas

    if (canvas) {
      this.context.clearRect(0, 0, canvas.width, canvas.height)
      $canvas = $(canvas)
    } else {
      $canvas = $("<canvas>").css({
        height: height,
        width: width
      }).data("peity", this)

      this.canvas = canvas = $canvas[0]
      this.context = canvas.getContext("2d")
      this.$el.hide().before(canvas)
    }

    $canvas.attr({
      height: $canvas.height() * devicePixelRatio,
      width: $canvas.width() * devicePixelRatio
    })

    return canvas
  }

  PeityPrototype.values = function() {
    return $.map(this.$el.text().split(this.opts.delimiter), function(value) {
      return parseFloat(value)
    })
  }

  peity.defaults = {}
  peity.graphers = {}

  peity.register = function(type, defaults, grapher) {
    this.defaults[type] = defaults
    this.graphers[type] = grapher
  }

  peity.register(
    'pie',
    {
      colours: ["#ff9900", "#fff4dd", "#ffc66e"],
      delimiter: null,
      diameter: 16
    },
    function(opts) {
      if (!opts.delimiter) {
        var delimiter = this.$el.text().match(/[^0-9\.]/)
        opts.delimiter = delimiter ? delimiter[0] : ","
      }

      var values = this.values()

      if (opts.delimiter == "/") {
        var v1 = values[0]
        var v2 = values[1]
        values = [v1, v2 - v1]
      }

      var i = 0
      var length = values.length
      var sum = 0

      for (; i < length; i++) {
        sum += values[i]
      }

      var canvas = this.prepareCanvas(opts.width || opts.diameter, opts.height || opts.diameter)
      var context = this.context
      var width = canvas.width
      var height = canvas.height
      var radius = Math.min(width, height) / 2
      var pi = Math.PI
      var colours = this.colours()

      context.save()
      context.translate(width / 2, height / 2)
      context.rotate(-pi / 2)

      for (i = 0; i < length; i++) {
        var value = values[i]
        var slice = (value / sum) * pi * 2

        context.beginPath()
        context.moveTo(0, 0)
        context.arc(0, 0, radius, 0, slice, false)
        context.fillStyle = colours.call(this, value, i, values)
        context.fill()
        context.rotate(slice)
      }

      context.restore()
    }
  )

  peity.register(
    "line",
    {
      colour: "#c6d9fd",
      strokeColour: "#4d89f9",
      strokeWidth: 1,
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      width: 32
    },
    function(opts) {
      var values = this.values()
      if (values.length == 1) values.push(values[0])
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = this.prepareCanvas(opts.width, opts.height)
      var context = this.context
      var width = canvas.width
      var height = canvas.height
      var xQuotient = width / (values.length - 1)
      var yQuotient = height / (max - min)

      var coords = [];
      var i;

      context.beginPath();
      context.moveTo(0, height + (min * yQuotient))

      for (i = 0; i < values.length; i++) {
        var x = i * xQuotient
        var y = height - (yQuotient * (values[i] - min))

        coords.push({ x: x, y: y });
        context.lineTo(x, y);
      }

      context.lineTo(width, height + (min * yQuotient))
      context.fillStyle = opts.colour;
      context.fill();

      if (opts.strokeWidth) {
        context.beginPath();
        context.moveTo(0, coords[0].y);
        for (i = 0; i < coords.length; i++) {
          context.lineTo(coords[i].x, coords[i].y);
        }
        context.lineWidth = opts.strokeWidth * devicePixelRatio;
        context.strokeStyle = opts.strokeColour;
        context.stroke();
      }
    }
  );

  peity.register(
    'bar',
    {
      colours: ["#4D89F9"],
      delimiter: ",",
      height: 16,
      max: null,
      min: 0,
      spacing: devicePixelRatio,
      width: 32
    },
    function(opts) {
      var values = this.values()
      var max = Math.max.apply(Math, values.concat([opts.max]));
      var min = Math.min.apply(Math, values.concat([opts.min]))

      var canvas = this.prepareCanvas(opts.width, opts.height)
      var context = this.context

      var width = canvas.width
      var height = canvas.height
      var yQuotient = height / (max - min)
      var space = opts.spacing
      var xQuotient = (width + space) / values.length
      var colours = this.colours()

      for (var i = 0; i < values.length; i++) {
        var value = values[i]
        var y = height - (yQuotient * (value - min))
        var h

        if (value == 0) {
          if (min >= 0 || max > 0) y -= 1
          h = 1
        } else {
          h = yQuotient * values[i]
        }

        context.fillStyle = colours.call(this, value, i, values)
        context.fillRect(i * xQuotient, y, xQuotient - space, h)
      }
    }
  );
})(jQuery, document, Math, window.devicePixelRatio || 1);
<|endoftext|>


<commit_before>(function(){
	var B={};

	var setAttribute='setAttribute';
	var getAttribute='getAttribute';

	var ex=function(v){return v!=null && v!=undefined;};
	B.exists=ex;

	var $id=function(id){
		if(typeof id=="string" && id!='')
			return document.getElementById(id);
		else if(id==='' || !ex(id))
			return null;
		else
			return id;
	};
	B.$id=$id;

	var cattr=document.all?'className':'class';
	function classRegex(c){return new RegExp('(?:\\s|^)'+c+'(?:\\s|$)');}

	B.hasClass=function(i,c){
		i=$id(i);
		if(!ex(i) || !i[getAttribute](cattr))
			return false;
		return i[getAttribute](cattr).match(classRegex(c));
	};
	B.addClass=function(i,c){
		i=$id(i);
		if(!B.hasClass(i,c)){
			var cn=i[getAttribute](cattr)||'';
			i[setAttribute](cattr,cn+' '+c);
		}
	};
	B.removeClass=function(i,c){
		i=$id(i);
		if(ex(i) && B.hasClass(i,c)){
			var cn=i[getAttribute](cattr).replace(classRegex(c),' ');
			i[setAttribute](cattr,cn);
		}
	};
	B.replaceClass=function(i,cO,cN,app){
		i=$id(i);
		if(ex(i)){
			var cn=i[getAttribute](cattr);
			if(B.hasClass(i,cO)) {
				cn=cn.replace(classRegex(cO),' '+cN+' ');
				i[setAttribute](cattr,cn);
			}else if(app)
				B.addClass(i, cN);
		}
	};
	B.setClass = function(i,c){
		i=$id(i);
		ex(i) && i[setAttribute](cattr,c);
	};

	B.create=function(n,att,p){
		var e;
		var appendChild = 'appendChild';
		if(n=='text')
			e=document.createTextNode(att['value']);
		else{
			e=document.createElement(n);
			for(var k in att){
				switch(k){
				default:
					e[setAttribute](k, att[k]);
					break;
				case 'id':
					if($id(att[k])==null || $id(att[k])==undefined)
						e[setAttribute]('id',att[k]);
					break;
				case 'style':
				case 'cssText':
					// doesn't seem to work with IE
					e[setAttribute]('cssText',att[k]);
					e[setAttribute]('style',att[k]);
					break;
				case 'class':
				case 'className':
					B.setClass(e,att[k]);
					break;
				case 'text':
					e[appendChild](document.createTextNode(att[k]));
					break;
				}
			}
		}
		//if the parent is given, the created node will be put in it
		if(ex($id(p)) || p && ex($id(p.element))){
			if(!ex(p.element)) $id(p)[appendChild](e);
			else{
				//if the elem has to be inserted before an element
				if(ex($id(p.before))) $id(p.element).insertBefore(e,$id(p.before));
				//else if it has to be inserted after an element
				else if(ex($id(p.after)) && ex($id(p.after).nextSibling)) $id(p.element).insertBefore(e,$id(p.after).nextSibling);
				//else append
				else $id(p.element)[appendChild](e);
			}
		}
		return e;
	};

	B.addEvent=function(item,event,action,opt,args){
		opt=opt||{};
		args=args||new Array();
		item=$id(item);
		item[event+action]=function(evt){
			var scope=$id('scope' in opt?opt['scope']:window);
			if(!opt['skipEvent']) action.apply(scope,args.concat([evt]));
			else action.apply(scope,args);
		};

		if(item.attachEvent) item.attachEvent('on'+event,item[event+action]);
		else if(item.addEventListener) item.addEventListener(event,item[event+action],opt['propagate'] == true);
	};

	B.removeEvent=function(item,event,action,opt,args){
		opt=opt||{};
		args=args||new Array();
		item=$id(item);
		if(item.detachEvent) item.detachEvent('on'+event,item[event+action]);
		else if(item.removeEventListener) item.removeEventListener(event,item[event+action],opt['propagate'] == true);
		delete item[event+action];
	};

	B.appendChildren=function(i,c){
		var lC=c.length;
		i=$id(i);
		for(var k=0;k<lC;k++)
			i[appendChild](c[i]);
	};

	B.getStyle=function(i,s){
		i=$id(i);
		if (!ex(i)) return;
		if(i.currentStyle){
			s=s.replace(/\-(\w)/g,function(m,p1){return p1.toUpperCase();});
			var y=i.currentStyle[s];
		}else if(window.getComputedStyle){
			s=s.replace(/([A-Z])/g,function(p1){return "-"+p1.toLowerCase();});
			var y=document.defaultView.getComputedStyle(i,null).getPropertyValue(s);
		}
		return y;
	};

	B.getStyleValue=function(i,s){
		var v=B.getStyle(i, s);
		return parseFloat(v);
	};

	// Finally, set B visible to everybody
	window['B']=B;

	/******** MISC NON-BUTTERFLY FEATURES **********/

	function indexOf(v){
		for(var k in this)
			if(this[k]==v) return k;
		return -1;
	}

	Object.prototype.indexOf || (Object.prototype.indexOf = indexOf);
	Array.prototype.indexOf || (Array.prototype.indexOf = indexOf);

	function bind(obj){
		var s=Array.prototype.slice;
		args=s.call(arguments, 1);
		var self=this;
		var n=function(){};
		bound=function(){
			return self.apply(
				this instanceof n ? this : ( obj || {} ),
				args.concat(s.call(arguments))
			);
		};
		n.prototype=self.prototype;
		bound.prototype=new n();
		return bound;
	}
	Function.prototype.bind || (Function.prototype.bind = bind);

})();
<commit_msg>variable moved cause used elsewhere<commit_after>(function(){
	var B={};

	var setAttribute='setAttribute';
	var getAttribute='getAttribute';
	var appendChild = 'appendChild';

	var ex=function(v){return v!=null && v!=undefined;};
	B.exists=ex;

	var $id=function(id){
		if(typeof id=="string" && id!='')
			return document.getElementById(id);
		else if(id==='' || !ex(id))
			return null;
		else
			return id;
	};
	B.$id=$id;

	var cattr=document.all?'className':'class';
	function classRegex(c){return new RegExp('(?:\\s|^)'+c+'(?:\\s|$)');}

	B.hasClass=function(i,c){
		i=$id(i);
		if(!ex(i) || !i[getAttribute](cattr))
			return false;
		return i[getAttribute](cattr).match(classRegex(c));
	};
	B.addClass=function(i,c){
		i=$id(i);
		if(!B.hasClass(i,c)){
			var cn=i[getAttribute](cattr)||'';
			i[setAttribute](cattr,cn+' '+c);
		}
	};
	B.removeClass=function(i,c){
		i=$id(i);
		if(ex(i) && B.hasClass(i,c)){
			var cn=i[getAttribute](cattr).replace(classRegex(c),' ');
			i[setAttribute](cattr,cn);
		}
	};
	B.replaceClass=function(i,cO,cN,app){
		i=$id(i);
		if(ex(i)){
			var cn=i[getAttribute](cattr);
			if(B.hasClass(i,cO)) {
				cn=cn.replace(classRegex(cO),' '+cN+' ');
				i[setAttribute](cattr,cn);
			}else if(app)
				B.addClass(i, cN);
		}
	};
	B.setClass = function(i,c){
		i=$id(i);
		ex(i) && i[setAttribute](cattr,c);
	};

	B.create=function(n,att,p){
		var e;
		if(n=='text')
			e=document.createTextNode(att['value']);
		else{
			e=document.createElement(n);
			for(var k in att){
				switch(k){
				default:
					e[setAttribute](k, att[k]);
					break;
				case 'id':
					if($id(att[k])==null || $id(att[k])==undefined)
						e[setAttribute]('id',att[k]);
					break;
				case 'style':
				case 'cssText':
					// doesn't seem to work with IE
					e[setAttribute]('cssText',att[k]);
					e[setAttribute]('style',att[k]);
					break;
				case 'class':
				case 'className':
					B.setClass(e,att[k]);
					break;
				case 'text':
					e[appendChild](document.createTextNode(att[k]));
					break;
				}
			}
		}
		//if the parent is given, the created node will be put in it
		if(ex($id(p)) || p && ex($id(p.element))){
			if(!ex(p.element)) $id(p)[appendChild](e);
			else{
				//if the elem has to be inserted before an element
				if(ex($id(p.before))) $id(p.element).insertBefore(e,$id(p.before));
				//else if it has to be inserted after an element
				else if(ex($id(p.after)) && ex($id(p.after).nextSibling)) $id(p.element).insertBefore(e,$id(p.after).nextSibling);
				//else append
				else $id(p.element)[appendChild](e);
			}
		}
		return e;
	};

	B.addEvent=function(item,event,action,opt,args){
		opt=opt||{};
		args=args||new Array();
		item=$id(item);
		item[event+action]=function(evt){
			var scope=$id('scope' in opt?opt['scope']:window);
			if(!opt['skipEvent']) action.apply(scope,args.concat([evt]));
			else action.apply(scope,args);
		};

		if(item.attachEvent) item.attachEvent('on'+event,item[event+action]);
		else if(item.addEventListener) item.addEventListener(event,item[event+action],opt['propagate'] == true);
	};

	B.removeEvent=function(item,event,action,opt,args){
		opt=opt||{};
		args=args||new Array();
		item=$id(item);
		if(item.detachEvent) item.detachEvent('on'+event,item[event+action]);
		else if(item.removeEventListener) item.removeEventListener(event,item[event+action],opt['propagate'] == true);
		delete item[event+action];
	};

	B.appendChildren=function(i,c){
		var lC=c.length;
		i=$id(i);
		for(var k=0;k<lC;k++)
			i[appendChild](c[i]);
	};

	B.getStyle=function(i,s){
		i=$id(i);
		if (!ex(i)) return;
		if(i.currentStyle){
			s=s.replace(/\-(\w)/g,function(m,p1){return p1.toUpperCase();});
			var y=i.currentStyle[s];
		}else if(window.getComputedStyle){
			s=s.replace(/([A-Z])/g,function(p1){return "-"+p1.toLowerCase();});
			var y=document.defaultView.getComputedStyle(i,null).getPropertyValue(s);
		}
		return y;
	};

	B.getStyleValue=function(i,s){
		var v=B.getStyle(i, s);
		return parseFloat(v);
	};

	// Finally, set B visible to everybody
	window['B']=B;

	/******** MISC NON-BUTTERFLY FEATURES **********/

	function indexOf(v){
		for(var k in this)
			if(this[k]==v) return k;
		return -1;
	}

	Object.prototype.indexOf || (Object.prototype.indexOf = indexOf);
	Array.prototype.indexOf || (Array.prototype.indexOf = indexOf);

	function bind(obj){
		var s=Array.prototype.slice;
		args=s.call(arguments, 1);
		var self=this;
		var n=function(){};
		bound=function(){
			return self.apply(
				this instanceof n ? this : ( obj || {} ),
				args.concat(s.call(arguments))
			);
		};
		n.prototype=self.prototype;
		bound.prototype=new n();
		return bound;
	}
	Function.prototype.bind || (Function.prototype.bind = bind);

})();
<|endoftext|>