<commit_before>/* ************************************************************************

    License: MIT Licence

    Description:
        Provide a model based system to create and manage models threw
        application lifetime

************************************************************************ */

/*
 * Property available element :
 *   - nullable {Boolean}   if the property can be set to null or not
 *   - init {Mixed}         the initial value
 *   - primary {Boolean}    Indicate if property is a primary type or not,
 *                          it's used internally to find models who match...
 *   - needed {Boolean}     Indicate if the property should ALWAYS be
 *                          included when performing a save to server
 *   - check {String}       the typeof check (like String, Object, ...)
 *   - pattern {String}     the regex pattern to check
 *   - validate {Function}  the function to use for validate input.
 *                          Must return true and false value to validate or not
 *                          Validate can also act like pattern (string regex)
 *                          but it's more recommanded to use pattern instead
 *   - many {Boolean}       Indicate if check should expect an array instead
 *                          of a single value.
 *   - transform {Function} the transformation to apply before setting data
 *   - event {String}       the event to raise on any change
 *   - apply {Function}     the apply element
*/

/**
 * A model creator to manage your model type.
 *
 * @function model
 * @namespace a
 *
 * @param name {String}                     The model name to create
 * @param properties {Object}               The properties associated to the
 *                                          model.
*/
a.model = function(name, properties) {
    // Only allow new name (already existing name just give already existing
    // model definition)
    if(a.isString(name)) {
        if(!a.model.pooler.get(name)) {
            // Remove functions from object
            var functions = {};
            for (var el in properties) {
                if (properties.hasOwnProperty(el) && a.isFunction(properties[el])) {
                    functions[el] = properties[el];
                    delete properties[el];
                }
            }
            // Register model into pooler
            a.model.pooler.set(name, {
                properties: properties,
                functions: functions
            });

            // Register model into ajax
            // We auto-add the type 'json' as for now AppStorm
            // is only able to parse JSON elements
            a.setTemplateAjaxOptions('model:' + name, {
                model: name,
                type: 'json'
            });

            // We return a function embed to create new instance
            // from variable
            return function() {
                return a.model.pooler.createInstance(name);
            };
        } else {
            // We directly create a new model
            return a.model.pooler.createInstance(name);
        }

    // Name is a search query system
    } else if(a.isTrueObject(name)) {
        if('destroy' in name) {
            var instances = name['instances'];
            if(instances && a.isArray(instances)) {
                var i = instances.length;
                while(i--) {
                    a.model.pooler.deleteInstance(instances[i]);
                }
            }
        } else {
            return a.model.pooler.searchInstance(name);
        }
    }
};

/**
 * A model instance generator to manage multiple instance from a main model.
 * NEVER USE BY ITSELF, you should always go threw a.model before.
 *
 * @constructor
 *
 * @param name {String}                     The model name to create
 * @param properties {Object}               The properties associated to the
 *                                          model
 * @param functions {Object}                The functions associated to the
 *                                          model
*/
a.modelInstance = function(name, properties, functions) {
    this.modelName  = name || '';
    this.properties = {};
    this.snapshot   = {};
    // List properties originally found in the model
    // by default which cannot be changed by user
    this.originalContent = [];

    // Internal unique id tracer
    this.uid = a.uniqueId();
    this.nid = name + '-' + this.uid;

    if (a.isTrueObject(properties)) {
        this.properties = a.deepClone(properties);
    }

    for (var key in this) {
        this.originalContent.push(key);
    }

    // Applying functions
    if (a.isTrueObject(functions)) {
        for (var key in functions) {
            if(!a.contains(this.originalContent, key)) {
                this[key] = functions[key];
                // We also add it to original content
                this.originalContent.push(key);
            }
        }
    }
};

a.modelInstance.prototype = {
    /**
     * Get a single property value.
     *
     * @method get
     *
     * @param key {String}                  The property key
     * @return {Object}                     The property value or null if not
     *                                      existing
    */
    get: function(key) {
        var p = this.properties[key];
        return p ? p.value : null;
    },

    /**
     * Get a single property type.
     *
     * @method type
     *
     * @param key {String}                  The property key
     * @return {Object}                     The property type found, or null
    */
    type: function(key) {
        var p = this.properties[key];
        if(!p) {
            return 'text';
        }

        if(p.type) {
            return p.type;

        // Now we try to guess
        } else if(p.primary === true) {
            return 'hidden';
        } else if(a.isArray(p.check)) {
            return 'select';
        } else if(p.check) {
            var content = p.check.toLowerCase();
            if(content === 'boolean') {
                return 'checkbox';
            } else if(content === 'number' || content === 'float' ||
                content === 'double' || content === 'integer') {
                return 'number';
            }
            // TODO: add the lastest HTML like date, phone...
        }

        return 'text';
    },

    /**
     * Get the property list stored in the model.
     *
     * @method list
     *
     * @return {Object}                     The property list currently setted
    */
    list: function() {
        var properties = [];
        for(var i in this.properties) {
            properties.push(i);
        }
        return properties;
    },

    /**
     * Set the given property value.
     *
     * @method set
     *
     * @param key {String}                  The property key
     * @param value {Object}                The property value
    */
    set: function(key, value) {
        var property = this.properties[key];

        // If the property is setted, we can use it
        if(property) {
            var check     = property.check,
                apply     = property.apply,
                eventName = property.event,
                pattern   = property.pattern,
                transform = property.transform,
                validate  = property.validate,
                many      = property.many || false,
                old       = property.value;

            // TRANSFORM
            value = a.isFunction(transform) ? transform(value, old) : value;

            // NULLABLE TEST
            if(property['nullable'] === false && a.isNone(value)) {
                return;
            }

            // TODO: one of the solution here is to convert value into
            // an array (except in case of many = true) and then
            // try to check it
            // CHECK TEST - basic typeof test
            // CHECK TEST - model check error (we do allow complex sub type)
            if(a.isString(check)) {

                // Little hack to prevent wrong typeof check
                check = check.toLowerCase();
                if(check === 'integer' || check === 'float'
                    || check === 'double') {
                    check = 'number';
                }

                var instance = value instanceof a.modelInstance;
                if(instance && check !== value.modelName) {
                    return;
                } else if(!instance && check !== typeof(value)) {
                    return;
                }

            // CHECK TEST - array of values
            // Note: don't mix if...
            } else if(a.isArray(check)) {
                if(!a.contains(check, value)) {
                    return;
                }

            // CHECK TEST - key in object
            // Note: don't mix if...
            } else if(a.isTrueObject(check)) {
                if(!a.has(check, value)) {
                    return;
                }
            }

            // PATTERN TEST
            if(!a.isNone(value) && a.isString(pattern) && pattern) {
                if(many === true && a.isArray(value)) {
                    for(var i=0, l=value.length; i<l; ++i) {
                        var reg = new RegExp(pattern, 'g');
                        if(!reg.test(value[i])) {
                            return;
                        }
                    }
                } else {
                    var reg = new RegExp(pattern, 'g');
                    if(!reg.test(value)) {
                        return;
                    }
                }
            }

            // VALIDATE TEST - function
            if(a.isFunction(validate) && validate(value, old) !== true) {
                return;

            // VALIDATE TEST - regex
            } else if(a.isString(validate)
                        && !(new RegExp(validate, 'gi').test(value))) {
                return;
            }

            // We can apply property value now
            property.value = value;

            // If it's possible, we also update the 'direct' value
            if(!a.contains(this.originalContent, key)) {
                this[key] = value;
            }

            // APPLY TEST
            if(a.isFunction(apply)) {
                apply(value, old);
            }

            if(eventName) {
                this.dispatch(eventName, {
                    value: value,
                    old: old
                });
            }
        }
    },

    /**
     * Watch a model property for changes
     *
     * @method watch
     *
     * @param key {String}                  The model key to watch
     * @param fct {Function}                The function to bind
    */
    watch: function(key, fct) {
        if(a.isString(key) && a.isFunction(fct)) {
            a.watch.call(this, this.properties[key].value, fct);
        } else {
            a.console.storm('error', 'a.model.watch', 'Unable to watch the ' +
                'property ```' + key + '``` for model ```' + this.modelName +
                '```', 1);
        }
    },

    /**
     * Unwatch a mdoel property changes
     *
     * @method unwatch
     *
     * @param key {String}                  The model key to stop watching
     * @param fct {Function}                The function to unbind
    */
    unwatch: function(key, fct) {
        if(a.isString(key) && a.isFunction(fct)) {
            a.unwatch.call(this, this.properties[key].value, fct);
        } else {
            a.console.storm('error', 'a.model.unwatch', 'Unable to unwatch ' +
                'the property ```' + key + '``` for model ```' +
                this.modelName + '```', 1);
        }
    },

    /**
     * Check if a given key exist or not in model.
     *
     * @method has
     *
     * @param key {String}                  The key to test
    */
    has: function(key) {
        return key in this.properties;
    },

    /**
     * Clear model (rollback to default values for all properties)
     *
     * @method init
    */
    init: function() {
        for(var property in this.properties) {
            if (this.properties[property].hasOwnProperty('init')) {
                this.properties[property].value =
                        this.properties[property].init;
            } else {
                this.properties[property].value = null;
            }

            // Now we push data into directly the model itself
            if(!a.contains(this.originalContent, property)) {
                this[property] = this.get(property);
            } else {
                a.console.storm('error', 'a.model', 'The model ```' +
                        this.modelName + '``` has the property ```' + key +
                        '``` which is in conflict with internal model system.'+
                        ' Please change the property name...', 1);
            }
        }

        // Save current setted data
        this.takeSnapshot();

        this.dispatch('init', {});
    },

    /**
     * Get a fresh copy of the model, another instance with same data
     *
     * @method clone
     *
     * @return {a.modelInstance}            A new instance with exactly same
     *                                      data
    */
    clone: function() {
        var data = a.deepClone(this.toObject()),
            instance = a.model.pooler.createInstance(this.modelName);

        instance.fromObject(data);
        return instance;
    },

    /**
     * Convert model to a simple json object like
     *
     * @method toObject
     *
     * @return {Object}                     The result object
    */
    toObject: function() {
        var obj = {};
        for(var property in this.properties) {
            var result = this.get(property);
            if(result instanceof a.modelInstance) {
                obj[property] = result.toObject();
            } else if(a.isArray(result)) {
                var content = [];
                for(var i=0, l=result.length; i<l; ++i) {
                    var element = result[i];
                    if(element instanceof a.modelInstance) {
                        content.push(element.toObject());
                    } else {
                        content.push(element);
                    }
                }
                obj[property] = content;
            } else {
                obj[property] = result;
            }
        }
        return obj;
    },

    /**
     * From a JSON object like, fill this model with element found
     *
     * @method fromObject
     *
     * @param data {Object}                 The input data
    */
    fromObject: function(data) {
        for(var property in this.properties) {
            if(property in data) {
                this.properties[property].value = data[property];
                // If it's possible, we also update the 'direct' value
                if(!a.contains(this.originalContent, property)) {
                    this[property] = data[property];
                }
            }
        }
    },

    /**
     * Convert model to JSON data
     *
     * @method toJSON
     *
     * @return {String}                     The serialized JSON model
    */
    toJSON: function() {
        return a.parser.json.stringify(this.toObject());
    },

    /**
     * From a JSON, fill a JSON instance
     *
     * @method fromJSON
     *
     * @param data {Object}                 The input JSON data
    */
    fromJSON: function(data) {
        if(a.isString(data) && data.length > 0) {
            data = a.parser.json.parse(data);
        }
        this.fromObject(data);
    },

    /**
     * Take a model snapshot.
     *
     * @method takeSnapshot
     *
     * @return {Object}                     The snapshot created
    */
    takeSnapshot: function() {
        this.snapshot = {};
        for(var property in this.properties) {
            this.snapshot[property] = this.get(property);
        }
        return this.getSnapshot();
    },

    /**
     * Get the current stored snapshot.
     *
     * @method getSnapshot
     *
     * @return {Object}                     The snapshot currently stored
    */
    getSnapshot: function() {
        return this.snapshot;
    },

    /**
     * From the latest takeSnapshot used, retrieve the properties value
     * difference.
     * It helps to send to server only modified informations since last
     * snapshot.
     *
     * @method differenceSnapshot
     *
     * @param onlyCurrentValues {Boolean}   By default every properties found
     *                                      got a couple {value/old} object.
     *                                      But sometimes you may prefer to get
     *                                      only the current value and not old
     *                                      one, this parameter is for that.
     *                                      (default: false)
     * @return {Object}                     The difference between old and
     *                                      current model state
    */
    differenceSnapshot: function(onlyCurrentValues) {
        var snapshot   = this.snapshot,
            properties = this.properties,
            difference = {};

        for(var key in snapshot) {
            var snapValue    = snapshot[key],
                currentValue = properties[key].value;

            // Validate on value change, or needed stuff
            if(
                    currentValue !== snapValue
                ||  properties[key].needed === true
            ) {
                if(onlyCurrentValues) {
                    difference[key] = currentValue;
                } else {
                    difference[key] = {
                        value: currentValue,
                        old:   snapValue
                    };
                }
            }
        }

        return difference;
    }
};

/*
------------------------------
  HANDLEBARS HELPERS
------------------------------
*/
(function() {
    // From a given uid, get the linked model
    a.parameter.addParameterType('model',  function(uid) {
        return a.model.manager.get(uid);
    });

    // This helps to get model uid from a given model
    // The idea behind this is to recieve a model in parameter and lets
    // get the uid for form plugin
    Handlebars.registerHelper('model', function(object) {
        if(a.isString(object) || a.isNumber(object)) {
            return object;
        } else if(a.isTrueObject(object) && object.uid) {
            return object.uid;
        }
        return null;
    });
})();<commit_msg>Switch fromObject to use internal set method.<commit_after>/* ************************************************************************

    License: MIT Licence

    Description:
        Provide a model based system to create and manage models threw
        application lifetime

************************************************************************ */

/*
 * Property available element :
 *   - nullable {Boolean}   if the property can be set to null or not
 *   - init {Mixed}         the initial value
 *   - primary {Boolean}    Indicate if property is a primary type or not,
 *                          it's used internally to find models who match...
 *   - needed {Boolean}     Indicate if the property should ALWAYS be
 *                          included when performing a save to server
 *   - check {String}       the typeof check (like String, Object, ...)
 *   - pattern {String}     the regex pattern to check
 *   - validate {Function}  the function to use for validate input.
 *                          Must return true and false value to validate or not
 *                          Validate can also act like pattern (string regex)
 *                          but it's more recommanded to use pattern instead
 *   - many {Boolean}       Indicate if check should expect an array instead
 *                          of a single value.
 *   - transform {Function} the transformation to apply before setting data
 *   - event {String}       the event to raise on any change
 *   - apply {Function}     the apply element
*/

/**
 * A model creator to manage your model type.
 *
 * @function model
 * @namespace a
 *
 * @param name {String}                     The model name to create
 * @param properties {Object}               The properties associated to the
 *                                          model.
*/
a.model = function(name, properties) {
    // Only allow new name (already existing name just give already existing
    // model definition)
    if(a.isString(name)) {
        if(!a.model.pooler.get(name)) {
            // Remove functions from object
            var functions = {};
            for (var el in properties) {
                if (properties.hasOwnProperty(el) && a.isFunction(properties[el])) {
                    functions[el] = properties[el];
                    delete properties[el];
                }
            }
            // Register model into pooler
            a.model.pooler.set(name, {
                properties: properties,
                functions: functions
            });

            // Register model into ajax
            // We auto-add the type 'json' as for now AppStorm
            // is only able to parse JSON elements
            a.setTemplateAjaxOptions('model:' + name, {
                model: name,
                type: 'json'
            });

            // We return a function embed to create new instance
            // from variable
            return function() {
                return a.model.pooler.createInstance(name);
            };
        } else {
            // We directly create a new model
            return a.model.pooler.createInstance(name);
        }

    // Name is a search query system
    } else if(a.isTrueObject(name)) {
        if('destroy' in name) {
            var instances = name['instances'];
            if(instances && a.isArray(instances)) {
                var i = instances.length;
                while(i--) {
                    a.model.pooler.deleteInstance(instances[i]);
                }
            }
        } else {
            return a.model.pooler.searchInstance(name);
        }
    }
};

/**
 * A model instance generator to manage multiple instance from a main model.
 * NEVER USE BY ITSELF, you should always go threw a.model before.
 *
 * @constructor
 *
 * @param name {String}                     The model name to create
 * @param properties {Object}               The properties associated to the
 *                                          model
 * @param functions {Object}                The functions associated to the
 *                                          model
*/
a.modelInstance = function(name, properties, functions) {
    this.modelName  = name || '';
    this.properties = {};
    this.snapshot   = {};
    // List properties originally found in the model
    // by default which cannot be changed by user
    this.originalContent = [];

    // Internal unique id tracer
    this.uid = a.uniqueId();
    this.nid = name + '-' + this.uid;

    if (a.isTrueObject(properties)) {
        this.properties = a.deepClone(properties);
    }

    for (var key in this) {
        this.originalContent.push(key);
    }

    // Applying functions
    if (a.isTrueObject(functions)) {
        for (var key in functions) {
            if(!a.contains(this.originalContent, key)) {
                this[key] = functions[key];
                // We also add it to original content
                this.originalContent.push(key);
            }
        }
    }
};

a.modelInstance.prototype = {
    /**
     * Get a single property value.
     *
     * @method get
     *
     * @param key {String}                  The property key
     * @return {Object}                     The property value or null if not
     *                                      existing
    */
    get: function(key) {
        var p = this.properties[key];
        return p ? p.value : null;
    },

    /**
     * Get a single property type.
     *
     * @method type
     *
     * @param key {String}                  The property key
     * @return {Object}                     The property type found, or null
    */
    type: function(key) {
        var p = this.properties[key];
        if(!p) {
            return 'text';
        }

        if(p.type) {
            return p.type;

        // Now we try to guess
        } else if(p.primary === true) {
            return 'hidden';
        } else if(a.isArray(p.check)) {
            return 'select';
        } else if(p.check) {
            var content = p.check.toLowerCase();
            if(content === 'boolean') {
                return 'checkbox';
            } else if(content === 'number' || content === 'float' ||
                content === 'double' || content === 'integer') {
                return 'number';
            }
            // TODO: add the lastest HTML like date, phone...
        }

        return 'text';
    },

    /**
     * Get the property list stored in the model.
     *
     * @method list
     *
     * @return {Object}                     The property list currently setted
    */
    list: function() {
        var properties = [];
        for(var i in this.properties) {
            properties.push(i);
        }
        return properties;
    },

    /**
     * Set the given property value.
     *
     * @method set
     *
     * @param key {String}                  The property key
     * @param value {Object}                The property value
    */
    set: function(key, value) {
        var property = this.properties[key];

        // If the property is setted, we can use it
        if(property) {
            var check     = property.check,
                apply     = property.apply,
                eventName = property.event,
                pattern   = property.pattern,
                transform = property.transform,
                validate  = property.validate,
                many      = property.many || false,
                old       = property.value;

            // TRANSFORM
            value = a.isFunction(transform) ? transform(value, old) : value;

            // NULLABLE TEST
            if(property['nullable'] === false && a.isNone(value)) {
                return;
            }

            // TODO: one of the solution here is to convert value into
            // an array (except in case of many = true) and then
            // try to check it
            // CHECK TEST - basic typeof test
            // CHECK TEST - model check error (we do allow complex sub type)
            if(a.isString(check)) {

                // Little hack to prevent wrong typeof check
                check = check.toLowerCase();
                if(check === 'integer' || check === 'float'
                    || check === 'double') {
                    check = 'number';
                }

                var instance = value instanceof a.modelInstance;
                if(instance && check !== value.modelName) {
                    return;
                } else if(!instance && check !== typeof(value)) {
                    return;
                }

            // CHECK TEST - array of values
            // Note: don't mix if...
            } else if(a.isArray(check)) {
                if(!a.contains(check, value)) {
                    return;
                }

            // CHECK TEST - key in object
            // Note: don't mix if...
            } else if(a.isTrueObject(check)) {
                if(!a.has(check, value)) {
                    return;
                }
            }

            // PATTERN TEST
            if(!a.isNone(value) && a.isString(pattern) && pattern) {
                if(many === true && a.isArray(value)) {
                    for(var i=0, l=value.length; i<l; ++i) {
                        var reg = new RegExp(pattern, 'g');
                        if(!reg.test(value[i])) {
                            return;
                        }
                    }
                } else {
                    var reg = new RegExp(pattern, 'g');
                    if(!reg.test(value)) {
                        return;
                    }
                }
            }

            // VALIDATE TEST - function
            if(a.isFunction(validate) && validate(value, old) !== true) {
                return;

            // VALIDATE TEST - regex
            } else if(a.isString(validate)
                        && !(new RegExp(validate, 'gi').test(value))) {
                return;
            }

            // We can apply property value now
            property.value = value;

            // If it's possible, we also update the 'direct' value
            if(!a.contains(this.originalContent, key)) {
                this[key] = value;
            }

            // APPLY TEST
            if(a.isFunction(apply)) {
                apply(value, old);
            }

            if(eventName) {
                this.dispatch(eventName, {
                    value: value,
                    old: old
                });
            }
        }
    },

    /**
     * Watch a model property for changes
     *
     * @method watch
     *
     * @param key {String}                  The model key to watch
     * @param fct {Function}                The function to bind
    */
    watch: function(key, fct) {
        if(a.isString(key) && a.isFunction(fct)) {
            a.watch.call(this, this.properties[key].value, fct);
        } else {
            a.console.storm('error', 'a.model.watch', 'Unable to watch the ' +
                'property ```' + key + '``` for model ```' + this.modelName +
                '```', 1);
        }
    },

    /**
     * Unwatch a mdoel property changes
     *
     * @method unwatch
     *
     * @param key {String}                  The model key to stop watching
     * @param fct {Function}                The function to unbind
    */
    unwatch: function(key, fct) {
        if(a.isString(key) && a.isFunction(fct)) {
            a.unwatch.call(this, this.properties[key].value, fct);
        } else {
            a.console.storm('error', 'a.model.unwatch', 'Unable to unwatch ' +
                'the property ```' + key + '``` for model ```' +
                this.modelName + '```', 1);
        }
    },

    /**
     * Check if a given key exist or not in model.
     *
     * @method has
     *
     * @param key {String}                  The key to test
    */
    has: function(key) {
        return key in this.properties;
    },

    /**
     * Clear model (rollback to default values for all properties)
     *
     * @method init
    */
    init: function() {
        for(var property in this.properties) {
            if (this.properties[property].hasOwnProperty('init')) {
                this.properties[property].value =
                        this.properties[property].init;
            } else {
                this.properties[property].value = null;
            }

            // Now we push data into directly the model itself
            if(!a.contains(this.originalContent, property)) {
                this[property] = this.get(property);
            } else {
                a.console.storm('error', 'a.model', 'The model ```' +
                        this.modelName + '``` has the property ```' + key +
                        '``` which is in conflict with internal model system.'+
                        ' Please change the property name...', 1);
            }
        }

        // Save current setted data
        this.takeSnapshot();

        this.dispatch('init', {});
    },

    /**
     * Get a fresh copy of the model, another instance with same data
     *
     * @method clone
     *
     * @return {a.modelInstance}            A new instance with exactly same
     *                                      data
    */
    clone: function() {
        var data = a.deepClone(this.toObject()),
            instance = a.model.pooler.createInstance(this.modelName);

        instance.fromObject(data);
        return instance;
    },

    /**
     * Convert model to a simple json object like
     *
     * @method toObject
     *
     * @return {Object}                     The result object
    */
    toObject: function() {
        var obj = {};
        for(var property in this.properties) {
            var result = this.get(property);
            if(result instanceof a.modelInstance) {
                obj[property] = result.toObject();
            } else if(a.isArray(result)) {
                var content = [];
                for(var i=0, l=result.length; i<l; ++i) {
                    var element = result[i];
                    if(element instanceof a.modelInstance) {
                        content.push(element.toObject());
                    } else {
                        content.push(element);
                    }
                }
                obj[property] = content;
            } else {
                obj[property] = result;
            }
        }
        return obj;
    },

    /**
     * From a JSON object like, fill this model with element found
     *
     * @method fromObject
     *
     * @param data {Object}                 The input data
    */
    fromObject: function(data) {
        for(var property in this.properties) {
            if(property in data) {
                // We update everything
                this.set(property, data[property]);
            }
        }
    },

    /**
     * Convert model to JSON data
     *
     * @method toJSON
     *
     * @return {String}                     The serialized JSON model
    */
    toJSON: function() {
        return a.parser.json.stringify(this.toObject());
    },

    /**
     * From a JSON, fill a JSON instance
     *
     * @method fromJSON
     *
     * @param data {Object}                 The input JSON data
    */
    fromJSON: function(data) {
        if(a.isString(data) && data.length > 0) {
            data = a.parser.json.parse(data);
        }
        this.fromObject(data);
    },

    /**
     * Take a model snapshot.
     *
     * @method takeSnapshot
     *
     * @return {Object}                     The snapshot created
    */
    takeSnapshot: function() {
        this.snapshot = {};
        for(var property in this.properties) {
            this.snapshot[property] = this.get(property);
        }
        return this.getSnapshot();
    },

    /**
     * Get the current stored snapshot.
     *
     * @method getSnapshot
     *
     * @return {Object}                     The snapshot currently stored
    */
    getSnapshot: function() {
        return this.snapshot;
    },

    /**
     * From the latest takeSnapshot used, retrieve the properties value
     * difference.
     * It helps to send to server only modified informations since last
     * snapshot.
     *
     * @method differenceSnapshot
     *
     * @param onlyCurrentValues {Boolean}   By default every properties found
     *                                      got a couple {value/old} object.
     *                                      But sometimes you may prefer to get
     *                                      only the current value and not old
     *                                      one, this parameter is for that.
     *                                      (default: false)
     * @return {Object}                     The difference between old and
     *                                      current model state
    */
    differenceSnapshot: function(onlyCurrentValues) {
        var snapshot   = this.snapshot,
            properties = this.properties,
            difference = {};

        for(var key in snapshot) {
            var snapValue    = snapshot[key],
                currentValue = properties[key].value;

            // Validate on value change, or needed stuff
            if(
                    currentValue !== snapValue
                ||  properties[key].needed === true
            ) {
                if(onlyCurrentValues) {
                    difference[key] = currentValue;
                } else {
                    difference[key] = {
                        value: currentValue,
                        old:   snapValue
                    };
                }
            }
        }

        return difference;
    }
};

/*
------------------------------
  HANDLEBARS HELPERS
------------------------------
*/
(function() {
    // From a given uid, get the linked model
    a.parameter.addParameterType('model',  function(uid) {
        return a.model.manager.get(uid);
    });

    // This helps to get model uid from a given model
    // The idea behind this is to recieve a model in parameter and lets
    // get the uid for form plugin
    Handlebars.registerHelper('model', function(object) {
        if(a.isString(object) || a.isNumber(object)) {
            return object;
        } else if(a.isTrueObject(object) && object.uid) {
            return object.uid;
        }
        return null;
    });
})();<|endoftext|>


<commit_before>/**
 * @file <%= moduleDescription %>
 * @author <%= userName %>
 * @module <%= moduleName %>
**/
(function(root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([<%= depList %>], function(<% if (use.jquery) { %>$<% } %><% if (use.jquery && depList.length > 1) { %>, <% } %><% if (use.underscore) { %>_<% } %><% if (use.backbone) { %>, Backbone<% } %>) {
            return (root.<%= moduleName %> = factory(root<% if (use.jquery) { %>, $<% } %><% if (use.underscore) { %>, _<% } %><% if (use.backbone) { %>, Backbone<% } %>));
        });
    } else if (typeof exports === 'object') {<% if (use.jquery) { %>
        var $ = require('jquery');<% } %><%     if (use.underscore) { %>
        var _ = require('underscore');<% } %><% if (use.backbone) { %>
        var Backbone = require('backbone');<% } %>
        module.exports = factory(root<% if(use.jquery) { %>, $<% } %><% if(use.underscore) { %>, _<% } %><% if(use.backbone) { %>, Backbone<% } %>);
    } else {
        root.<%= moduleName %> = factory(root<% if (use.jquery) { %>, jQuery<% } %><% if (use.underscore) { %>, _<% } %><% if (use.backbone) { %>, Backbone<% } %>);
    }
}(this, function(root<% if (use.jquery) { %>, $<% } %><% if (use.underscore) { %>, _<% } %><% if (use.backbone) { %>, Backbone<% } %>) {
    'use strict';

    //Write constructors, functions, objects, variables, and other stuff here...
    function <%= moduleName %>() {
        this._foo = 'bar';
    }

    <%= moduleName %>.prototype.foo = function() {
        console.log(this._foo);
    }

    function privateFunction() {
        console.log('"Well begun is half done"');
        console.info('--Aristotle');
    }

    return {

        //Return the public module API here..
        publicFunction: privateFunction

    };
}));
<commit_msg>Changed _ to underscore to resolve lint warning<commit_after>/**
 * @file <%= moduleDescription %>
 * @author <%= userName %>
 * @module <%= moduleName %>
**/
(function(root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([<%= depList %>], function(<% if (use.jquery) { %>$<% } %><% if (use.jquery && depList.length > 1) { %>, <% } %><% if (use.underscore) { %>_<% } %><% if (use.backbone) { %>, Backbone<% } %>) {
            return (root.<%= moduleName %> = factory(root<% if (use.jquery) { %>, $<% } %><% if (use.underscore) { %>, _<% } %><% if (use.backbone) { %>, Backbone<% } %>));
        });
    } else if (typeof exports === 'object') {<% if (use.jquery) { %>
        var $ = require('jquery');<% } %><%     if (use.underscore) { %>
        var _ = require('underscore');<% } %><% if (use.backbone) { %>
        var Backbone = require('backbone');<% } %>
        module.exports = factory(root<% if(use.jquery) { %>, $<% } %><% if(use.underscore) { %>, _<% } %><% if(use.backbone) { %>, Backbone<% } %>);
    } else {
        root.<%= moduleName %> = factory(root<% if (use.jquery) { %>, jQuery<% } %><% if (use.underscore) { %>, underscore<% } %><% if (use.backbone) { %>, Backbone<% } %>);
    }
}(this, function(root<% if (use.jquery) { %>, $<% } %><% if (use.underscore) { %>, _<% } %><% if (use.backbone) { %>, Backbone<% } %>) {
    'use strict';

    //Write constructors, functions, objects, variables, and other stuff here...
    function <%= moduleName %>() {
        this._foo = 'bar';
    }

    <%= moduleName %>.prototype.foo = function() {
        console.log(this._foo);
    }

    function privateFunction() {
        console.log('"Well begun is half done"');
        console.info('--Aristotle');
    }

    return {

        //Return the public module API here..
        publicFunction: privateFunction

    };
}));
<|endoftext|>


<commit_before>Physics.Rod = function(config) {
  Physics.Constraint.call(this, config);
};
Physics.Rod.prototype = Object.create(Physics.Constraint.prototype);
Physics.Rod.prototype.update = function() {
  this.springEffect();
  this.forceCompensate();
};
<commit_msg>Update rod.js<commit_after>if(!Physics.Rod) {
  Physics.Rod = function(config) {
    Physics.Constraint.call(this, config);
  };
  Physics.Rod.prototype = Object.create(Physics.Constraint.prototype);
  Physics.Rod.prototype.update = function() {
    this.springEffect();
    this.forceCompensate();
  };
}
<|endoftext|>


<commit_before>'use strict';

var
  q = require('q'),
  Resource = require('./resource'),
  createDependencyStrategy = require('./dependency-strategy');

/**
 * @constructor
 */
function DM() {
  this._dependencies = {};
  this._subscribers = [];
  this._resources = {};

  this._timeout = null;
}

DM.TIMEOUT = 3000;

/**
 * @param {string} name
 * @param {*} value
 */
DM.prototype.set = function (name, value) {
  if (typeof this._dependencies[name] !== 'undefined') {
    throw new Error('Dependency "' + name + '" has already been provided');
  }

  this._dependencies[name] = value;

  this._resolve();
};

/**
 * @param {string|string[]} dependencies
 * @param {function} [callback]
 * @returns {Promise}
 */
DM.prototype.get = function (dependencies, callback) {
  var deferred = q.defer();

  this._subscribers.push({
    dependencyStrategy: createDependencyStrategy(dependencies),
    callback: callback,
    deferred: deferred
  });

  this._resolve();

  return deferred.promise;
};

/**
 * @param {string} dependency
 * @returns {boolean}
 */
DM.prototype.isResolved = function (dependency) {
  return (typeof this._dependencies[dependency] !== 'undefined');
};

/**
 * @private
 */
DM.prototype._areAllResolved = function (dependencies) {
  // Implemented in the fastest possible way.
  var length = dependencies.length;
  for (var i = 0; i < length; i++) {
    if (!this.isResolved(dependencies[i])) {
      return false;
    }
  }
  return true;
};

/**
 * @param {string} name
 * @param callback
 * @returns {Resource}
 */
DM.prototype.resource = function (name, callback) {
  if (typeof this._resources[name] === 'undefined') {
    this._resources[name] = new Resource(this, name);
  }

  if (typeof callback === 'function') {
    this._resources[name].provide(callback());
  }

  return this._resources[name];
};

/**
 * @param {string} name
 */
DM.prototype.getResource = function (name) {
  return this._resources[name];
};

/**
 * @private
 */
DM.prototype._resolve = function () {
  var
    self = this,
    remainingSubscribers = [],
    toCall = [];

  this._subscribers.forEach(function (subscriber) {
    if (!self._areAllResolved(subscriber.dependencyStrategy.requiredDependencies)) {
      remainingSubscribers.push(subscriber);
    }
    else {
      var returnedValue = subscriber.dependencyStrategy.buildReturningValue(self._dependencies);

      toCall.push(function () {
        if (subscriber.callback) {
          subscriber.callback.apply(null, Array.isArray(returnedValue) ? returnedValue : [returnedValue]);
        }
        subscriber.deferred.resolve(returnedValue);
      });
    }
  });

  this._subscribers = remainingSubscribers;

  toCall.forEach(function (callback) {
    callback();
  });

  this._delayReporting();
};

DM.prototype._delayReporting = function () {
  if (this._timeout !== null) {
    clearTimeout(this._timeout);
  }

  this._timeout = setTimeout(this._reportMissing.bind(this), DM.TIMEOUT);
};

DM.prototype._printLog = console.log;

/**
 * Build an object with missing resources.
 * @returns {{}} Object. Keys are missing resources. Values are arrays of missing deps.
 * @private
 */
DM.prototype._getMissingByResource = function () {
  var resource, key, missingByResource = {};

  var checkResourceDependencies = function checkResourceDependencies(resourceName, dependency) {
    if (!this.isResolved(dependency)) {
      missingByResource[resourceName] = missingByResource[resourceName] || [];
      missingByResource[resourceName].push(dependency);
    }
  };

  for (key in this._resources) {
    if (this._resources.hasOwnProperty(key)) {
      resource = this._resources[key];
      resource._dependencies.forEach(checkResourceDependencies.bind(this, resource._name));
    }
  }

  return missingByResource;
};

DM.prototype._getMainMissingDependencies = function () {
  var
    key,
    missingByResource = this._getMissingByResource(),
    causeOfTheProblem = {},
    stringMessages = [];

  // Trying to find the main problematic dependency.
  for (key in missingByResource) {
    if (missingByResource.hasOwnProperty(key)) {
      missingByResource[key].forEach(function (resourceAndState) {
        resourceAndState = resourceAndState.split(':');
        var res = resourceAndState[0];
        var state = resourceAndState[1];
        if (typeof missingByResource[res] === 'undefined') {
          causeOfTheProblem[res] = state || true;
        }
      });
    }
  }

  for (key in causeOfTheProblem) {
    if (causeOfTheProblem.hasOwnProperty(key)) {
      var str = key;
      if (causeOfTheProblem[key] !== true) {
        str += ':' + causeOfTheProblem[key];
      }
      stringMessages.push(str);
    }
  }

  return stringMessages.join(', ');
};

DM.prototype._getGeneralMissingDependencies = function () {  var
  missingGeneral = {},
  isResolved = this.isResolved.bind(this);

  this._subscribers.forEach(function (subscriber) {
    subscriber.dependencyStrategy.requiredDependencies.forEach(function (dependency) {
      if (!isResolved(dependency)) {
        missingGeneral[dependency] = true;
      }
    });
  });

  return Object.keys(missingGeneral).join(', ');
};

DM.prototype._reportMissing = function () {
  function isEmpty(obj) {
    if (obj == null) return true;
    if (obj.length > 0) return false;
    if (obj.length === 0) return true;
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) return false;
    }

    return true;
  }

  var
    missingGeneral = this._getGeneralMissingDependencies(),
    causeOfTheProblem = this._getMainMissingDependencies();

  if (!isEmpty(causeOfTheProblem)) {
    this._printLog('Main dependency missing: ' + causeOfTheProblem);
  }

  if (!isEmpty(missingGeneral)) {
    this._printLog('Final list of missing dependencies: [ ' + missingGeneral + ' ]');
  }
};

module.exports = DM;
<commit_msg>#16 - fix the last complicated bug.<commit_after>'use strict';

var
  q = require('q'),
  Resource = require('./resource'),
  createDependencyStrategy = require('./dependency-strategy');

/**
 * @constructor
 */
function DM() {
  this._dependencies = {};
  this._subscribers = [];
  this._resources = {};

  this._timeout = null;
}

DM.TIMEOUT = 3000;

/**
 * @param {string} name
 * @param {*} value
 */
DM.prototype.set = function (name, value) {
  if (typeof this._dependencies[name] !== 'undefined') {
    throw new Error('Dependency "' + name + '" has already been provided');
  }

  this._dependencies[name] = value;

  this._resolve();
};

/**
 * @param {string|string[]} dependencies
 * @param {function} [callback]
 * @returns {Promise}
 */
DM.prototype.get = function (dependencies, callback) {
  var deferred = q.defer();

  this._subscribers.push({
    dependencyStrategy: createDependencyStrategy(dependencies),
    callback: callback,
    deferred: deferred
  });

  this._resolve();

  return deferred.promise;
};

/**
 * @param {string} dependency
 * @returns {boolean}
 */
DM.prototype.isResolved = function (dependency) {
  return (typeof this._dependencies[dependency] !== 'undefined');
};

/**
 * @private
 */
DM.prototype._areAllResolved = function (dependencies) {
  // Implemented in the fastest possible way.
  var length = dependencies.length;
  for (var i = 0; i < length; i++) {
    if (!this.isResolved(dependencies[i])) {
      return false;
    }
  }
  return true;
};

/**
 * @param {string} name
 * @param callback
 * @returns {Resource}
 */
DM.prototype.resource = function (name, callback) {
  if (typeof this._resources[name] === 'undefined') {
    this._resources[name] = new Resource(this, name);
  }

  if (typeof callback === 'function') {
    this._resources[name].provide(callback());
  }

  return this._resources[name];
};

/**
 * @param {string} name
 */
DM.prototype.getResource = function (name) {
  return this._resources[name];
};

/**
 * @private
 */
DM.prototype._resolve = function () {
  var
    self = this,
    remainingSubscribers = [],
    toCall = [];

  // Loop through all non resolved subscribers.
  this._subscribers.forEach(function (subscriber) {
    var
      isResolved = true,
      resolvedDependencies = [];

    subscriber.dependencyStrategy.requiredDependencies.forEach(function (dependencyName) {
      if (!self.isResolved(dependencyName)) {
        isResolved = false;
      }
      else {
        resolvedDependencies.push(self._dependencies[dependencyName]);
      }
    });

    if (isResolved) {
      toCall.push(function () {
        if (subscriber.callback) {
          subscriber.callback.apply(null, resolvedDependencies);
        }

        subscriber.deferred.resolve(subscriber.dependencyStrategy.buildReturningValue(self._dependencies));
      });
    }
    else {
      remainingSubscribers.push(subscriber);
    }
  });

  this._subscribers = remainingSubscribers;

  toCall.forEach(function (callback) {
    callback();
  });

  this._delayReporting();
};

DM.prototype._delayReporting = function () {
  if (this._timeout !== null) {
    clearTimeout(this._timeout);
  }

  this._timeout = setTimeout(this._reportMissing.bind(this), DM.TIMEOUT);
};

DM.prototype._printLog = console.log;

/**
 * Build an object with missing resources.
 * @returns {{}} Object. Keys are missing resources. Values are arrays of missing deps.
 * @private
 */
DM.prototype._getMissingByResource = function () {
  var resource, key, missingByResource = {};

  var checkResourceDependencies = function checkResourceDependencies(resourceName, dependency) {
    if (!this.isResolved(dependency)) {
      missingByResource[resourceName] = missingByResource[resourceName] || [];
      missingByResource[resourceName].push(dependency);
    }
  };

  for (key in this._resources) {
    if (this._resources.hasOwnProperty(key)) {
      resource = this._resources[key];
      resource._dependencies.forEach(checkResourceDependencies.bind(this, resource._name));
    }
  }

  return missingByResource;
};

DM.prototype._getMainMissingDependencies = function () {
  var
    key,
    missingByResource = this._getMissingByResource(),
    causeOfTheProblem = {},
    stringMessages = [];

  // Trying to find the main problematic dependency.
  for (key in missingByResource) {
    if (missingByResource.hasOwnProperty(key)) {
      missingByResource[key].forEach(function (resourceAndState) {
        resourceAndState = resourceAndState.split(':');
        var res = resourceAndState[0];
        var state = resourceAndState[1];
        if (typeof missingByResource[res] === 'undefined') {
          causeOfTheProblem[res] = state || true;
        }
      });
    }
  }

  for (key in causeOfTheProblem) {
    if (causeOfTheProblem.hasOwnProperty(key)) {
      var str = key;
      if (causeOfTheProblem[key] !== true) {
        str += ':' + causeOfTheProblem[key];
      }
      stringMessages.push(str);
    }
  }

  return stringMessages.join(', ');
};

DM.prototype._getGeneralMissingDependencies = function () {  var
  missingGeneral = {},
  isResolved = this.isResolved.bind(this);

  this._subscribers.forEach(function (subscriber) {
    subscriber.dependencyStrategy.requiredDependencies.forEach(function (dependency) {
      if (!isResolved(dependency)) {
        missingGeneral[dependency] = true;
      }
    });
  });

  return Object.keys(missingGeneral).join(', ');
};

DM.prototype._reportMissing = function () {
  function isEmpty(obj) {
    if (obj == null) return true;
    if (obj.length > 0) return false;
    if (obj.length === 0) return true;
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) return false;
    }

    return true;
  }

  var
    missingGeneral = this._getGeneralMissingDependencies(),
    causeOfTheProblem = this._getMainMissingDependencies();

  if (!isEmpty(causeOfTheProblem)) {
    this._printLog('Main dependency missing: ' + causeOfTheProblem);
  }

  if (!isEmpty(missingGeneral)) {
    this._printLog('Final list of missing dependencies: [ ' + missingGeneral + ' ]');
  }
};

module.exports = DM;
<|endoftext|>


<commit_before>/**
 * @license Copyright 2013 Logentries.
 * Please view license at https://raw.github.com/logentries/le_js/master/LICENSE
 */

/*jslint browser:true*/
/*global define, module, exports, console, global */

/** @param {Object} window */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(function() {
            return factory(root);
        });
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        if (typeof global === 'object') {
            // Browserify. The calling object `this` does not reference window.
            // `global` and `this` are equivalent in Node, preferring global
            // adds support for Browserify.
            root = global;
        }
        module.exports = factory(root);
    } else {
        // Browser globals (root is window)
        root.LE = factory(root);
    }
}(this, function(window) {
    "use strict";

    /**
     * A single log event stream.
     * @constructor
     * @param {Object} options
     */
    function LogStream(options) {
        /**
         * @const
         * @type {string} */
        var _traceCode = (Math.random() + Math.PI).toString(36).substring(2, 10);
        /** @type {boolean} */
        var _doTrace = options.trace;
        /** @type {string} */
        var _pageInfo = options.page_info;
        /** @type {string} */
        var _token = options.token;
        /** @type {boolean} */
        var _print = options.print;
        /**
         * @type {string} */
        var _endpoint;
        if (window.LEENDPOINT) {
            _endpoint = window.LEENDPOINT;
        } else {
            _endpoint = "localhost:8080/v1";
        }

        /**
         * Flag to prevent further invocations on network err
         ** @type {boolean} */
        var _shouldCall = true;
        /** @type {boolean} */
        var _SSL = function() {
            if (typeof XDomainRequest === "undefined") {
                return options.ssl;
            }
            // If we're relying on XDomainRequest, we
            // must adhere to the page's encryption scheme.
            return window.location.protocol === "https:" ? true : false;
        }();
        /** @type {Array.<string>} */
        var _backlog = [];
        /** @type {boolean} */
        var _active = false;
        /** @type {boolean} */
        var _sentPageInfo = false;

        if (options.catchall) {
            var oldHandler = window.onerror;
            var newHandler = function(msg, url, line) {
                _rawLog({error: msg, line: line, location: url}).level('ERROR').send();
                if (oldHandler) {
                    return oldHandler(msg, url, line);
                } else {
                    return false;
                }
            };
            window.onerror = newHandler;
        }

        var _agentInfo = function() {
            var nav = window.navigator || {doNotTrack: undefined};
            var screen = window.screen || {};
            var location = window.location || {};

            return {
              url: location.pathname,
              referrer: document.referrer,
              screen: {
                width: screen.width,
                height: screen.height
              },
              window: {
                width: window.innerWidth,
                height: window.innerHeight
              },
              browser: {
                name: nav.appName,
                version: nav.appVersion,
                cookie_enabled: nav.cookieEnabled,
                do_not_track: nav.doNotTrack
              },
              platform: nav.platform
            };
        };

        var _getEvent = function() {
            var raw = null;
            var args = Array.prototype.slice.call(arguments);
            if (args.length === 0) {
                throw new Error("No arguments!");
            } else if (args.length === 1) {
                raw = args[0];
            } else {
                // Handle a variadic overload,
                // e.g. _rawLog("some text ", x, " ...", 1);
              raw = args;
            }
            return raw;
        };

        // Single arg stops the compiler arity warning
        var _rawLog = function(msg) {
            var event = _getEvent.apply(this, arguments);

            var data = {event: event};

            // Add agent info if required
            if (_pageInfo !== 'never') {
                if (!_sentPageInfo || _pageInfo === 'per-entry') {
                    _sentPageInfo = true;
                    if (typeof event.screen === "undefined" &&
                        typeof event.browser === "undefined")
                      _rawLog(_agentInfo()).level('PAGE').send();
                }
            }

            // Add trace code if required
            if (_doTrace) {
                data.trace = _traceCode;
            }

            return {level: function(l) {
                // Don't log PAGE events to console
                // PAGE events are generated for the agentInfo function
                    if (_print && typeof console !== "undefined" && l !== 'PAGE') {
                      var serialized = null;
                      if (typeof XDomainRequest !== "undefined") {
                        // We're using IE8/9
                        serialized = data.trace + ' ' + data.event;
                      }
                      try {
                        console[l.toLowerCase()].call(console, (serialized || data));
                      } catch (ex) {
                        // IE compat fix
                        console.log((serialized || data));
                      }
                    }
                    data.level = l;

                    return {send: function() {
                        var cache = [];
                        var serialized = JSON.stringify(data, function(key, value) {

                          // cross-browser indexOf fix
                          var _indexOf = function(array, obj) {
                            for (var i = 0; i < array.length; i++) {
                              if (obj === array[i]) {
                                return i;
                              }
                            }
                            return -1;
                          };
                              if (typeof value === "undefined") {
                                return "undefined";
                              } else if (typeof value === "object" && value !== null) {
                                if (_indexOf(cache, value) !== -1) {
                                  // We've seen this object before;
                                  // return a placeholder instead to prevent
                                  // cycles
                                  return "<?>";
                                }
                                cache.push(value);
                              }
                          return value;
                        });

                            if (_active) {
                                _backlog.push(serialized);
                            } else {
                                _apiCall(_token, serialized);
                            }
                        }};
                }};
        };

        /** @expose */
        this.log = _rawLog;

        var _apiCall = function(token, data) {
            _active = true;

            // Obtain a browser-specific XHR object
            var _getAjaxObject = function() {
              if (typeof XDomainRequest !== "undefined") {
                // We're using IE8/9
                return new XDomainRequest();
              }
              return new XMLHttpRequest();
            };

            var request = _getAjaxObject();

            if (_shouldCall) {
                if (request.constructor === XMLHttpRequest) {
                    // Currently we don't support fine-grained error
                    // handling in older versions of IE
                    request.onreadystatechange = function() {
                    if (request.readyState === 4) {
                        // Handle any errors
                        if (request.status >= 400) {
                            console.error("Couldn't submit events.");
                            if (request.status === 410) {
                                // This API version has been phased out
                                console.warn("This version of le_js is no longer supported!");
                            }
                        } else {
                            if (request.status === 301) {
                                // Server issued a deprecation warning
                                console.warn("This version of le_js is deprecated! Consider upgrading.");
                            }
                            if (_backlog.length > 0) {
                                // Submit the next event in the backlog
                                _apiCall(token, _backlog.shift());
                            } else {
                                _active = false;
                            }
                        }
                    }

                    };
                } else {
                  request.onload = function() {
                    if (_backlog.length > 0) {
                      // Submit the next event in the backlog
                      _apiCall(token, _backlog.shift());
                    } else {
                      _active = false;
                    }
                  };
                }

                var uri = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
                request.open("POST", uri, true);
                if (request.constructor === XMLHttpRequest) {
                    request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                    request.setRequestHeader('Content-type', 'text/json');
                }
                request.send(data);
            }
        };
    }

    /**
     * A single log object
     * @constructor
     * @param {Object} options
     */
    function Logger(options) {
        var logger;

        // Default values
        var dict = {
            ssl: true,
            catchall: false,
            trace: true,
            page_info: 'never',
            print: false,
            endpoint: null,
            token: null
        };

        if (typeof options === "object")
            for (var k in options)
                dict[k] = options[k];
        else
            throw new Error("Invalid parameters for createLogStream()");

        if (dict.token === null) {
            throw new Error("Token not present.");
        } else {
            logger = new LogStream(dict);
        }

        var _log = function(msg) {
            if (logger) {
                return logger.log.apply(this, arguments);
            } else
                throw new Error("You must call LE.init(...) first.");
        };

         // The public interface
        return {
            log: function() {
                _log.apply(this, arguments).level('LOG').send();
            },
            warn: function() {
                _log.apply(this, arguments).level('WARN').send();
            },
            error: function() {
                _log.apply(this, arguments).level('ERROR').send();
            },
            info: function() {
                _log.apply(this, arguments).level('INFO').send();
            }
        };
    }

    // Array of Logger elements
    var loggers = {};

    var _getLogger = function(name) {
        if (!loggers.hasOwnProperty(name))
           throw new Error("Invalid name for logStream");

        return loggers[name];
    };

    var  _createLogStream = function(options) {
        if (typeof options.name !== "string")
            throw new Error("Name not present.");
        else if (loggers.hasOwnProperty(options.name))
            throw new Error("A logger with that name already exists!");
        loggers[options.name] = new Logger(options);

        return true;
    };

    var _deprecatedInit = function(options) {
        var dict = {
            name : "default"
        };

        if (typeof options === "object")
            for (var k in options)
                dict[k] = options[k];
        else if (typeof options === "string")
            dict.token = options;
        else
            throw new Error("Invalid parameters for init()");

        return _createLogStream(dict);
    };

    var _destroyLogStream = function(name) {
        if (typeof name === 'undefined'){
            name = 'default';
        }

        delete loggers[name];
    };

    // The public interface
    return {
        init: _deprecatedInit,
        createLogStream: _createLogStream,
        to: _getLogger,
        destroy: _destroyLogStream,
        log: function() {
            for (var k in loggers)
                loggers[k].log.apply(this, arguments);
        },
        warn: function() {
            for (var k in loggers)
                loggers[k].warn.apply(this, arguments);
        },
        error: function() {
            for (var k in loggers)
                loggers[k].error.apply(this, arguments);
        },
        info: function() {
            for (var k in loggers)
                loggers[k].info.apply(this, arguments);
        }
    };
}));
<commit_msg>Suppress FF notice about not finding xml body<commit_after>/**
 * @license Copyright 2013 Logentries.
 * Please view license at https://raw.github.com/logentries/le_js/master/LICENSE
 */

/*jslint browser:true*/
/*global define, module, exports, console, global */

/** @param {Object} window */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(function() {
            return factory(root);
        });
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        if (typeof global === 'object') {
            // Browserify. The calling object `this` does not reference window.
            // `global` and `this` are equivalent in Node, preferring global
            // adds support for Browserify.
            root = global;
        }
        module.exports = factory(root);
    } else {
        // Browser globals (root is window)
        root.LE = factory(root);
    }
}(this, function(window) {
    "use strict";

    /**
     * A single log event stream.
     * @constructor
     * @param {Object} options
     */
    function LogStream(options) {
        /**
         * @const
         * @type {string} */
        var _traceCode = (Math.random() + Math.PI).toString(36).substring(2, 10);
        /** @type {boolean} */
        var _doTrace = options.trace;
        /** @type {string} */
        var _pageInfo = options.page_info;
        /** @type {string} */
        var _token = options.token;
        /** @type {boolean} */
        var _print = options.print;
        /**
         * @type {string} */
        var _endpoint;
        if (window.LEENDPOINT) {
            _endpoint = window.LEENDPOINT;
        } else {
            _endpoint = "localhost:8080/v1";
        }

        /**
         * Flag to prevent further invocations on network err
         ** @type {boolean} */
        var _shouldCall = true;
        /** @type {boolean} */
        var _SSL = function() {
            if (typeof XDomainRequest === "undefined") {
                return options.ssl;
            }
            // If we're relying on XDomainRequest, we
            // must adhere to the page's encryption scheme.
            return window.location.protocol === "https:" ? true : false;
        }();
        /** @type {Array.<string>} */
        var _backlog = [];
        /** @type {boolean} */
        var _active = false;
        /** @type {boolean} */
        var _sentPageInfo = false;

        if (options.catchall) {
            var oldHandler = window.onerror;
            var newHandler = function(msg, url, line) {
                _rawLog({error: msg, line: line, location: url}).level('ERROR').send();
                if (oldHandler) {
                    return oldHandler(msg, url, line);
                } else {
                    return false;
                }
            };
            window.onerror = newHandler;
        }

        var _agentInfo = function() {
            var nav = window.navigator || {doNotTrack: undefined};
            var screen = window.screen || {};
            var location = window.location || {};

            return {
              url: location.pathname,
              referrer: document.referrer,
              screen: {
                width: screen.width,
                height: screen.height
              },
              window: {
                width: window.innerWidth,
                height: window.innerHeight
              },
              browser: {
                name: nav.appName,
                version: nav.appVersion,
                cookie_enabled: nav.cookieEnabled,
                do_not_track: nav.doNotTrack
              },
              platform: nav.platform
            };
        };

        var _getEvent = function() {
            var raw = null;
            var args = Array.prototype.slice.call(arguments);
            if (args.length === 0) {
                throw new Error("No arguments!");
            } else if (args.length === 1) {
                raw = args[0];
            } else {
                // Handle a variadic overload,
                // e.g. _rawLog("some text ", x, " ...", 1);
              raw = args;
            }
            return raw;
        };

        // Single arg stops the compiler arity warning
        var _rawLog = function(msg) {
            var event = _getEvent.apply(this, arguments);

            var data = {event: event};

            // Add agent info if required
            if (_pageInfo !== 'never') {
                if (!_sentPageInfo || _pageInfo === 'per-entry') {
                    _sentPageInfo = true;
                    if (typeof event.screen === "undefined" &&
                        typeof event.browser === "undefined")
                      _rawLog(_agentInfo()).level('PAGE').send();
                }
            }

            // Add trace code if required
            if (_doTrace) {
                data.trace = _traceCode;
            }

            return {level: function(l) {
                // Don't log PAGE events to console
                // PAGE events are generated for the agentInfo function
                    if (_print && typeof console !== "undefined" && l !== 'PAGE') {
                      var serialized = null;
                      if (typeof XDomainRequest !== "undefined") {
                        // We're using IE8/9
                        serialized = data.trace + ' ' + data.event;
                      }
                      try {
                        console[l.toLowerCase()].call(console, (serialized || data));
                      } catch (ex) {
                        // IE compat fix
                        console.log((serialized || data));
                      }
                    }
                    data.level = l;

                    return {send: function() {
                        var cache = [];
                        var serialized = JSON.stringify(data, function(key, value) {

                          // cross-browser indexOf fix
                          var _indexOf = function(array, obj) {
                            for (var i = 0; i < array.length; i++) {
                              if (obj === array[i]) {
                                return i;
                              }
                            }
                            return -1;
                          };
                              if (typeof value === "undefined") {
                                return "undefined";
                              } else if (typeof value === "object" && value !== null) {
                                if (_indexOf(cache, value) !== -1) {
                                  // We've seen this object before;
                                  // return a placeholder instead to prevent
                                  // cycles
                                  return "<?>";
                                }
                                cache.push(value);
                              }
                          return value;
                        });

                            if (_active) {
                                _backlog.push(serialized);
                            } else {
                                _apiCall(_token, serialized);
                            }
                        }};
                }};
        };

        /** @expose */
        this.log = _rawLog;

        var _apiCall = function(token, data) {
            _active = true;

            // Obtain a browser-specific XHR object
            var _getAjaxObject = function() {
              if (typeof XDomainRequest !== "undefined") {
                // We're using IE8/9
                return new XDomainRequest();
              }
              return new XMLHttpRequest();
            };

            var request = _getAjaxObject();

            if (_shouldCall) {
                if (request.constructor === XMLHttpRequest) {
                    // Currently we don't support fine-grained error
                    // handling in older versions of IE
                    request.onreadystatechange = function() {
                    if (request.readyState === 4) {
                        // Handle any errors
                        if (request.status >= 400) {
                            console.error("Couldn't submit events.");
                            if (request.status === 410) {
                                // This API version has been phased out
                                console.warn("This version of le_js is no longer supported!");
                            }
                        } else {
                            if (request.status === 301) {
                                // Server issued a deprecation warning
                                console.warn("This version of le_js is deprecated! Consider upgrading.");
                            }
                            if (_backlog.length > 0) {
                                // Submit the next event in the backlog
                                _apiCall(token, _backlog.shift());
                            } else {
                                _active = false;
                            }
                        }
                    }

                    };
                } else {
                  request.onload = function() {
                    if (_backlog.length > 0) {
                      // Submit the next event in the backlog
                      _apiCall(token, _backlog.shift());
                    } else {
                      _active = false;
                    }
                  };
                }

                var uri = (_SSL ? "https://" : "http://") + _endpoint + "/logs/" + _token;
                request.open("POST", uri, true);
                if (request.constructor === XMLHttpRequest) {
                    request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                    request.setRequestHeader('Content-type', 'text/json');
                }

                if (request.overrideMimeType) {
                    request.overrideMimeType('text');
                }

                request.send(data);
            }
        };
    }

    /**
     * A single log object
     * @constructor
     * @param {Object} options
     */
    function Logger(options) {
        var logger;

        // Default values
        var dict = {
            ssl: true,
            catchall: false,
            trace: true,
            page_info: 'never',
            print: false,
            endpoint: null,
            token: null
        };

        if (typeof options === "object")
            for (var k in options)
                dict[k] = options[k];
        else
            throw new Error("Invalid parameters for createLogStream()");

        if (dict.token === null) {
            throw new Error("Token not present.");
        } else {
            logger = new LogStream(dict);
        }

        var _log = function(msg) {
            if (logger) {
                return logger.log.apply(this, arguments);
            } else
                throw new Error("You must call LE.init(...) first.");
        };

         // The public interface
        return {
            log: function() {
                _log.apply(this, arguments).level('LOG').send();
            },
            warn: function() {
                _log.apply(this, arguments).level('WARN').send();
            },
            error: function() {
                _log.apply(this, arguments).level('ERROR').send();
            },
            info: function() {
                _log.apply(this, arguments).level('INFO').send();
            }
        };
    }

    // Array of Logger elements
    var loggers = {};

    var _getLogger = function(name) {
        if (!loggers.hasOwnProperty(name))
           throw new Error("Invalid name for logStream");

        return loggers[name];
    };

    var  _createLogStream = function(options) {
        if (typeof options.name !== "string")
            throw new Error("Name not present.");
        else if (loggers.hasOwnProperty(options.name))
            throw new Error("A logger with that name already exists!");
        loggers[options.name] = new Logger(options);

        return true;
    };

    var _deprecatedInit = function(options) {
        var dict = {
            name : "default"
        };

        if (typeof options === "object")
            for (var k in options)
                dict[k] = options[k];
        else if (typeof options === "string")
            dict.token = options;
        else
            throw new Error("Invalid parameters for init()");

        return _createLogStream(dict);
    };

    var _destroyLogStream = function(name) {
        if (typeof name === 'undefined'){
            name = 'default';
        }

        delete loggers[name];
    };

    // The public interface
    return {
        init: _deprecatedInit,
        createLogStream: _createLogStream,
        to: _getLogger,
        destroy: _destroyLogStream,
        log: function() {
            for (var k in loggers)
                loggers[k].log.apply(this, arguments);
        },
        warn: function() {
            for (var k in loggers)
                loggers[k].warn.apply(this, arguments);
        },
        error: function() {
            for (var k in loggers)
                loggers[k].error.apply(this, arguments);
        },
        info: function() {
            for (var k in loggers)
                loggers[k].info.apply(this, arguments);
        }
    };
}));
<|endoftext|>