<commit_before>(function() {
    gf.Sprite = gf.Entity.extend({
        //initializes a new entity with the start position (pos) and the settings defined in Tiled
        init: function(pos, settings) {
            /****************************************************************************
             * Properties that are defined in the `settings` object,
             * these can be specified in the properties of the object layer
             * in Tiled, and overriden on a per-object basis
             ****************************************************************************/
            //size of the sprite (each frame)
            this.size = new THREE.Vector2(0, 0);

            //number of frames accross x / y (calculated, not set)
            this.numFrames = new THREE.Vector2(0, 0);

            //offset in the image to the first frame (in pixels)
            this.offset = new THREE.Vector2(0, 0);

            //opacity of the sprite
            this.opacity = 1.0;

            //raw THREE.Texture instance loaded by the gf.loader
            this.texture = null;

            //defined sprite animations
            this.anim = {};

            //currently active animation
            this.currentAnim = null;

            //currently active frame's active time
            this.currentFrameTime = 0;

            //animations paused
            this.animPaused = false;

            //use screen coords instead of world coords
            this.useScreenCoords = false;

            /****************************************************************************
             * Call base constructor
             ****************************************************************************/
            this._super(pos, settings);

            this._baseHitSize = this.hitSize.clone();
            this._baseScaledHitSize = this.scaledHitSize.clone();
            this._baseHitOffset = this.hitOffset.clone();

            // create a default animation sequence with all sprites
            this.addAnimation('default', null, 1000, true);
            // set as default
            this.setActiveAnimation('default');
        },
        //add a new animation
        addAnimation: function(name, settings) {
            settings = settings || {};

            if(settings instanceof Array)
                settings = { frames: settings };

            this.anim[name] = {
                name: name,
                frames: [],
                idx: 0,
                length: 0,
                frameTime: 0,
                loop: !!settings.loop,
                hitSize: settings.hitSize,
                hitOffset: settings.hitOffset
            };

            if(!settings.frames) {
                settings.frames = [];
                // create a default animation with all sprites in the spritesheet
                for(var f = 0, fl = this.numFrames.x * this.numFrames.y; f < fl; ++f) {
                    settings.frames[f] = f;
                }
            }

            // compute and add the offset of each frame
            for(var i = 0, il = settings.frames.length; i < il; ++i) {
                this.anim[name].frames[i] = new THREE.Vector2(this.size.x * (settings.frames[i] % this.numFrames.x), this.size.y * Math.floor(settings.frames[i] / this.numFrames.x));
            }
            this.anim[name].length = this.anim[name].frames.length;
            this.anim[name].frameTime = (settings.duration || 0) / this.anim[name].frames.length;

            return this;
        },
        setActiveAnimation: function(name, resetAnim, cb) {
            if(typeof resetAnim == 'function') {
                cb = resetAnim;
                resetAnim = null;
            }

            if(!this.anim[name]) {
                this.currentAnim = name;
            } else {
                if(this.currentAnim && this.currentAnim._cb && !this.currentAnim._cbCalled) {
                    this.currentAnim._cbCalled = true;
                    this.currentAnim._cb(true);
                }

                this.currentAnim = this.anim[name];
                this.currentAnim._done = false;
                this.currentAnim._cbCalled = false;
                this.currentAnim._cb = cb;
                if(resetAnim) this.currentAnim.idx = 0;
                this._setOffset();

                //if we are changing hitbox, save current settings and update
                if(this.currentAnim.hitSize) {
                    if(this.currentAnim.hitSize instanceof THREE.Vector2) {
                        this.hitSize.copy(this.currentAnim.hitSize);
                        this.scaledHitSize.copy(this.currentAnim.hitSize).multiplyScalar(this.scale);
                    }
                    else if(this.currentAnim.hitSize instanceof Array) {
                        this.hitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]);
                        this.scaledHitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]).multiplyScalar(this.scale);
                    }
                }
                else {
                    this.hitSize.copy(this._baseHitSize);
                    this.scaledHitSize.copy(this._baseScaledHitSize);
                }

                if(this.currentAnim.hitOffset) {
                    if(this.currentAnim.hitOffset instanceof THREE.Vector2)
                        this.hitOffset.copy(this.currentAnim.hitOffset);
                    else if(this.currentAnim.hitOffset instanceof Array)
                        this.hitOffset.set(this.currentAnim.hitOffset[0], this.currentAnim.hitOffset[1]);
                }
                else {
                    this.hitOffset.copy(this._baseHitOffset);
                }

                this._hitboxMesh.scale.set(this.scaledHitSize.x, this.scaledHitSize.y, 1);
                this.setPosition(this._mesh.position);
            }

            this.currentFrameTime = 0;

            return this;
        },
        isActiveAnimation: function(name) {
            return this.currentAnim.name == name;
        },
        update: function() {
            this._super();

            if(!this.currentAnim || this.currentAnim.frameTime === 0 || this.currentAnim.length <= 1) return;

            this.currentFrameTime += gf.game._delta * 1000;

            //update animation if necessary
            if (this.isVisible && !this.animPaused && !this.currentAnim._done && (this.currentFrameTime > this.currentAnim.frameTime)) {
                while(this.currentFrameTime > this.currentAnim.frameTime) {
                    this.currentFrameTime -= this.currentAnim.frameTime;
                    this.currentAnim.idx++;

                    if(this.currentAnim.idx == this.currentAnim.length) {
                        if(this.currentAnim.loop) {
                            this.currentAnim.idx = 0;
                        }
                        else {
                            this.currentAnim._done = true;
                            if(this.currentAnim._cb && !this.currentAnim._cbCalled) {
                                this.currentAnim._cbCalled = true;
                                this.currentAnim._cb();
                            }
                            return;
                        }
                    }

                    this._setOffset();
                }
            }

            return this;
        },
        _setOffset: function() {
            //for some reason when inversed, the tiles need to start at 1 instead of 0
            var add = this.currentAnim.inverse ? 1 : 0,
                frame = this.currentAnim.frames[this.currentAnim.idx];

            this._material.uvOffset.set(
                frame.x / this.texture.image.width,
                1 - (this.size.y / this.texture.image.height) - (frame.y / this.texture.image.height)
            );
        },
        //override Entity mesh with a sprite instead
        _createMesh: function() {
            if(this._mesh) return;

            if(this.filtered) {
                this.texture.magFilter = THREE.LinearFilter;
                this.texture.minFilter = THREE.LinearMipMapLinearFilter;
            } else {
                this.texture.magFilter = THREE.NearestFilter;
                this.texture.minFilter = THREE.NearestMipMapNearestFilter;
            }

            this.numFrames.set(this.texture.image.width / this.size.x, this.texture.image.height / this.size.y);

            this._material = new THREE.SpriteMaterial({
                color: 0xffffff,
                opacity: 1,
                map: this.texture,

                blending: THREE.NormalBlending,
                alignment: THREE.SpriteAlignment.center,
                useScreenCoordinates: this.useScreenCoords,
                //depthTest: false, //default: !useScreenCoordinates
                sizeAttenuation: true, //default: !useScreenCoordinates
                //scaledByViewport: true //default: !sizeAttenuation
            });

            this._material.uvScale.set(
                1 / this.numFrames.x,
                1 / this.numFrames.y
            );
            this._material.uvOffset.set(
                0,
                1 - this._material.uvScale.y //transform to topleft offset
            );

            this._mesh = new THREE.Sprite(this._material);

            this._mesh.scale.set(
                1 / this.numFrames.x / 2,
                1 / this.numFrames.y / 3,
                1
            );
        }
    });
})();<commit_msg>fix a scaling issue for sprites<commit_after>(function() {
    gf.Sprite = gf.Entity.extend({
        //initializes a new entity with the start position (pos) and the settings defined in Tiled
        init: function(pos, settings) {
            /****************************************************************************
             * Properties that are defined in the `settings` object,
             * these can be specified in the properties of the object layer
             * in Tiled, and overriden on a per-object basis
             ****************************************************************************/
            //size of the sprite (each frame)
            this.size = new THREE.Vector2(0, 0);

            //number of frames accross x / y (calculated, not set)
            this.numFrames = new THREE.Vector2(0, 0);

            //offset in the image to the first frame (in pixels)
            this.offset = new THREE.Vector2(0, 0);

            //opacity of the sprite
            this.opacity = 1.0;

            //raw THREE.Texture instance loaded by the gf.loader
            this.texture = null;

            //defined sprite animations
            this.anim = {};

            //currently active animation
            this.currentAnim = null;

            //currently active frame's active time
            this.currentFrameTime = 0;

            //animations paused
            this.animPaused = false;

            //use screen coords instead of world coords
            this.useScreenCoords = false;

            /****************************************************************************
             * Call base constructor
             ****************************************************************************/
            this._super(pos, settings);

            this._baseHitSize = this.hitSize.clone();
            this._baseScaledHitSize = this.scaledHitSize.clone();
            this._baseHitOffset = this.hitOffset.clone();

            // create a default animation sequence with all sprites
            this.addAnimation('default', null, 1000, true);
            // set as default
            this.setActiveAnimation('default');
        },
        //add a new animation
        addAnimation: function(name, settings) {
            settings = settings || {};

            if(settings instanceof Array)
                settings = { frames: settings };

            this.anim[name] = {
                name: name,
                frames: [],
                idx: 0,
                length: 0,
                frameTime: 0,
                loop: !!settings.loop,
                hitSize: settings.hitSize,
                hitOffset: settings.hitOffset
            };

            if(!settings.frames) {
                settings.frames = [];
                // create a default animation with all sprites in the spritesheet
                for(var f = 0, fl = this.numFrames.x * this.numFrames.y; f < fl; ++f) {
                    settings.frames[f] = f;
                }
            }

            // compute and add the offset of each frame
            for(var i = 0, il = settings.frames.length; i < il; ++i) {
                this.anim[name].frames[i] = new THREE.Vector2(this.size.x * (settings.frames[i] % this.numFrames.x), this.size.y * Math.floor(settings.frames[i] / this.numFrames.x));
            }
            this.anim[name].length = this.anim[name].frames.length;
            this.anim[name].frameTime = (settings.duration || 0) / this.anim[name].frames.length;

            return this;
        },
        setActiveAnimation: function(name, resetAnim, cb) {
            if(typeof resetAnim == 'function') {
                cb = resetAnim;
                resetAnim = null;
            }

            if(!this.anim[name]) {
                this.currentAnim = name;
            } else {
                if(this.currentAnim && this.currentAnim._cb && !this.currentAnim._cbCalled) {
                    this.currentAnim._cbCalled = true;
                    this.currentAnim._cb(true);
                }

                this.currentAnim = this.anim[name];
                this.currentAnim._done = false;
                this.currentAnim._cbCalled = false;
                this.currentAnim._cb = cb;
                if(resetAnim) this.currentAnim.idx = 0;
                this._setOffset();

                //if we are changing hitbox, save current settings and update
                if(this.currentAnim.hitSize) {
                    if(this.currentAnim.hitSize instanceof THREE.Vector2) {
                        this.hitSize.copy(this.currentAnim.hitSize);
                        this.scaledHitSize.copy(this.currentAnim.hitSize).multiplyScalar(this.scale);
                    }
                    else if(this.currentAnim.hitSize instanceof Array) {
                        this.hitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]);
                        this.scaledHitSize.set(this.currentAnim.hitSize[0], this.currentAnim.hitSize[1]).multiplyScalar(this.scale);
                    }
                }
                else {
                    this.hitSize.copy(this._baseHitSize);
                    this.scaledHitSize.copy(this._baseScaledHitSize);
                }

                if(this.currentAnim.hitOffset) {
                    if(this.currentAnim.hitOffset instanceof THREE.Vector2)
                        this.hitOffset.copy(this.currentAnim.hitOffset);
                    else if(this.currentAnim.hitOffset instanceof Array)
                        this.hitOffset.set(this.currentAnim.hitOffset[0], this.currentAnim.hitOffset[1]);
                }
                else {
                    this.hitOffset.copy(this._baseHitOffset);
                }

                this._hitboxMesh.scale.set(this.scaledHitSize.x, this.scaledHitSize.y, 1);
                this.setPosition(this._mesh.position);
            }

            this.currentFrameTime = 0;

            return this;
        },
        isActiveAnimation: function(name) {
            return this.currentAnim.name == name;
        },
        update: function() {
            this._super();

            if(!this.currentAnim || this.currentAnim.frameTime === 0 || this.currentAnim.length <= 1) return;

            this.currentFrameTime += gf.game._delta * 1000;

            //update animation if necessary
            if (this.isVisible && !this.animPaused && !this.currentAnim._done && (this.currentFrameTime > this.currentAnim.frameTime)) {
                while(this.currentFrameTime > this.currentAnim.frameTime) {
                    this.currentFrameTime -= this.currentAnim.frameTime;
                    this.currentAnim.idx++;

                    if(this.currentAnim.idx == this.currentAnim.length) {
                        if(this.currentAnim.loop) {
                            this.currentAnim.idx = 0;
                        }
                        else {
                            this.currentAnim._done = true;
                            if(this.currentAnim._cb && !this.currentAnim._cbCalled) {
                                this.currentAnim._cbCalled = true;
                                this.currentAnim._cb();
                            }
                            return;
                        }
                    }

                    this._setOffset();
                }
            }

            return this;
        },
        _setOffset: function() {
            //for some reason when inversed, the tiles need to start at 1 instead of 0
            var add = this.currentAnim.inverse ? 1 : 0,
                frame = this.currentAnim.frames[this.currentAnim.idx];

            this._material.uvOffset.set(
                frame.x / this.texture.image.width,
                1 - (this.size.y / this.texture.image.height) - (frame.y / this.texture.image.height)
            );
        },
        //override Entity mesh with a sprite instead
        _createMesh: function() {
            if(this._mesh) return;

            if(this.filtered) {
                this.texture.magFilter = THREE.LinearFilter;
                this.texture.minFilter = THREE.LinearMipMapLinearFilter;
            } else {
                this.texture.magFilter = THREE.NearestFilter;
                this.texture.minFilter = THREE.NearestMipMapNearestFilter;
            }

            this.numFrames.set(this.texture.image.width / this.size.x, this.texture.image.height / this.size.y);

            this._material = new THREE.SpriteMaterial({
                color: 0xffffff,
                opacity: 1,
                map: this.texture,

                blending: THREE.NormalBlending,
                alignment: THREE.SpriteAlignment.center,
                useScreenCoordinates: this.useScreenCoords,
                //depthTest: false, //default: !useScreenCoordinates
                sizeAttenuation: true, //default: !useScreenCoordinates
                //scaledByViewport: true //default: !sizeAttenuation
            });

            this._material.uvScale.set(
                1 / this.numFrames.x,
                1 / this.numFrames.y
            );
            this._material.uvOffset.set(0, 0);

            this._mesh = new THREE.Sprite(this._material);

            this._mesh.scale.set(
                (1 / this.numFrames.x) * this.scale,
                (1 / this.numFrames.x) * this.scale,
                1
            );
        }
    });
})();<|endoftext|>


<commit_before>/**
 * The base Sprite class. This class is the base for all images on the screen
 *
 * @module gf
 * @class Sprite
 * @extends DisplayObject
 * @constructor
 * @param pos {Array|Vector|Point} The starting position of the sprite
 * @param settings {Object} Settings to override the defauls
 * @example
 *      var spr = new gf.Sprite([10, 1], { name: 'MySprite' });
 */
gf.Sprite = function(pos, settings) {
    /**
     * The size of the sprite
     *
     * @property size
     * @type gf.Vector
     * @default new gf.Vector(0, 0);
     */
    this.size = new gf.Vector(0, 0);

    /**
     * The name of this sprite
     *
     * @property name
     * @type String
     * @default ''
     */
    this.name = '';

    /**
     * The defined animations for this Sprite, this maps the names to the childIndexes
     *
     * @property anim
     * @private
     * @readOnly
     * @type Object
     */
    this.anim = {};

    /**
     * The currently active animation
     *
     * @property currentAnim
     * @private
     * @readOnly
     * @type Object
     */
    this.currentAnim = null;

    /**
     * Whether or not this Sprite is interactive. Please either pass this in with the
     * ctor or use setInteractive to change it later. Changing this property directly
     * on-the-fly will yield unexpected results.
     *
     * @property interactive
     * @type Boolean
     */
    this.interactive = false;

    //call base ctor
    gf.DisplayObject.call(this);

    //mixin user's settings
    gf.utils.setValues(this, settings);

    //add the animations passed to ctor
    if(settings.animations) {
        for(var name in settings.animations) {
            this.addAnimation(name, settings.animations[name]);
        }
    }

    //if a texture is passed, make this just display the texture
    if(settings.texture) {
        var spr = new PIXI.Sprite(settings.texture);
        if(settings.interactive) spr.setInteractive(true);
        this.addChild(spr);
        this.anim['default'] = spr;
    }

    //copied from http://www.goodboydigital.com/pixijs/docs/files/src_pixi_Sprite.js.html

    /*
    * MOUSE Callbacks
    */
    /**
    * A callback that is used when the users clicks on the sprite with thier mouse
    * @method click
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user clicks the mouse down over the sprite
    * @method mousedown
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user releases the mouse that was over the sprite
    * for this callback to be fired the mouse must have been pressed down over the sprite
    * @method mouseup
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the users mouse rolls over the sprite
    * @method mouseover
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the users mouse leaves the sprite
    * @method mouseout
    * @param interactionData {InteractionData}
    */

    /*
    * TOUCH Callbacks
    */

    /**
    * A callback that is used when the users taps on the sprite with thier finger
    * basically a touch version of click
    * @method tap
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user touch's over the sprite
    * @method touchstart
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user releases the touch that was over the sprite
    * for this callback to be fired. The touch must have started over the sprite
    * @method touchend
    * @param interactionData {InteractionData}
    */
};

gf.inherits(gf.Sprite, gf.DisplayObject, {
    /**
     * Defines a new animation on the Sprite
     *
     * @method addAnimation
     * @param name {String} The name of the animation, any string you want to name it
     * @param frames {Texture|Array} The frames of the animation, you can pass one gf.Texture
     *      as a frame, or an Array of gf.Texture's
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.addAnimation('walk-left', new gf.Texture())
     *          .addAnimation('walk-right', [new gf.Texture(), new gf.Texture()]);
     */
    addAnimation: function(name, frames, speed) {
        if(!frames)
            throw 'No textures passed to addAnimation()';

        //ensure all the items in the array are textures
        if(frames instanceof Array) {
            for(var i = 0, il = frames.length; i < il; ++i) {
                if(typeof frames[i] === 'string') {
                    if(!PIXI.TextureCache[frames[i]])
                        throw 'Texture ' + frames[i] + ' is not in cache, please load it first';

                    frames[i] = PIXI.TextureCache[frames[i]];
                }
            }
        }

        //if there is a single texture passed, then put it in an array
        if(frames instanceof gf.Texture)
            frames = [frames];

        //if a string is passed, get it from the texture cache
        if(typeof frames === 'string') {
            if(!PIXI.TextureCache[frames])
                throw 'Texture ' + frames + ' is not in cache, please load it first';

            frames = [PIXI.TextureCache[frames]];
        }

        //create a movie clip from the textures
        var clip = new PIXI.MovieClip(frames);
        clip.stop();
        clip.visible = false;
        clip.name = name;

        if(this.interactive)
            clip.setInteractive(this.interactive);

        if(speed)
            clip.animationSpeed = speed;

        this.addChild(clip);

        this.anim[name] = clip.childIndex;

        return this;
    },
    /**
     * Sets the active animation of the sprite, and starts the animation at index 0
     *
     * @method setActiveAnimation
     * @param name {String} The name of the animation to play (defined with addAnimation());
     * @param cb {Function} Callback when the animation completes, NOT YET IMPLEMENTED
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.addAnimation('me', new gf.Texture())
     *          .setActiveAnimation('me');
     */
    setActiveAnimation: function(name, cb) {
        if(this.anim[name] !== undefined) {
            if(this.currentAnim) {
                this.currentAnim.stop();
                this.currentAnim.visible = false;
            }

            this.currentAnim = this.children[this.anim[name]];
            this.currentAnim.visible = true;
            this.currentAnim.gotoAndPlay(0);
            //TODO: Callback
            if(cb) setTimeout(cb, 1);
        } else {
            throw 'Unknown animation ' + name;
        }

        return this;
    },
    /**
     * Convenience method for setting the position of the Sprite.
     *
     * @method setPosition
     * @param x {Number|Array|Vector|Point} X coord to put the sprite at.
     *       If an Array, Vector, or Point is passed then the y parameter is ignored
     * @param y {Number} Y coord to put the sprite at
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.setPosition(1, 1)
     *          .setPosition([5, 5])
     *          .setPosition(new gf.Point(10, 10))
     *          .setPosition(new gf.Vector(20, 20));
     */
    setPosition: function(x, y) {
        if(x instanceof gf.Vector || x instanceof gf.Point) {
            this.position.x = x.x;
            this.position.y = x.y;
        }
        else if(x instanceof Array) {
            this.position.x = x[0];
            this.position.y = x[1];
        } else {
            this.position.x = x;
            this.position.y = y;
        }

        return this;
    },
    /**
     * Sets whether or not this sprite is interactive (can be clicked)
     *
     * @method setInteractive
     * @param interactive {Boolean}
     */
    setInteractive: function(interactive) {
        this.interactive = interactive;
        for(var i = 0, il = this.children.length; i < il; ++i) {
            if(this.children[i].setInteractive)
                this.children[i].setInteractive(interactive);
        }
    },
    /**
     * Checks if the name is the active animation
     *
     * @method isActiveAnimation
     * @param name {String} The name of the animation to check if it is currently active
     * @return {Boolean} true if the animation is active, false otherwise.
     * @example
     *      spr.addAnimation('walk-left', new gf.Texture())
     *          .isActiveAnimation('walk-left'); //false
     *
     *      spr.setActiveAnimation('walk-left')
     *          .isActiveAnimation('walk-left'); //true
     */
    isActiveAnimation: function(name) {
        return this.currentAnim.name === name;
    }
});<commit_msg>Add some PIXI.MovieClip functions to sprite<commit_after>/**
 * The base Sprite class. This class is the base for all images on the screen
 *
 * @module gf
 * @class Sprite
 * @extends DisplayObject
 * @constructor
 * @param pos {Array|Vector|Point} The starting position of the sprite
 * @param settings {Object} Settings to override the defauls
 * @example
 *      var spr = new gf.Sprite([10, 1], { name: 'MySprite' });
 */
gf.Sprite = function(pos, settings) {
    /**
     * The size of the sprite
     *
     * @property size
     * @type gf.Vector
     * @default new gf.Vector(0, 0);
     */
    this.size = new gf.Vector(0, 0);

    /**
     * The name of this sprite
     *
     * @property name
     * @type String
     * @default ''
     */
    this.name = '';

    /**
     * The defined animations for this Sprite, this maps the names to the childIndexes
     *
     * @property anim
     * @private
     * @readOnly
     * @type Object
     */
    this.anim = {};

    /**
     * The currently active animation
     *
     * @property currentAnim
     * @private
     * @readOnly
     * @type Object
     */
    this.currentAnim = null;

    /**
     * Whether or not this Sprite is interactive. Please either pass this in with the
     * ctor or use setInteractive to change it later. Changing this property directly
     * on-the-fly will yield unexpected results.
     *
     * @property interactive
     * @type Boolean
     */
    this.interactive = false;

    //call base ctor
    gf.DisplayObject.call(this);

    //mixin user's settings
    gf.utils.setValues(this, settings);

    //add the animations passed to ctor
    if(settings.animations) {
        for(var name in settings.animations) {
            this.addAnimation(name, settings.animations[name]);
        }
    }

    //if a texture is passed, make this just display the texture
    if(settings.texture) {
        var spr = new PIXI.Sprite(settings.texture);
        if(settings.interactive) spr.setInteractive(true);
        this.addChild(spr);
        this.anim['default'] = spr;
    }

    //copied from http://www.goodboydigital.com/pixijs/docs/files/src_pixi_Sprite.js.html

    /*
    * MOUSE Callbacks
    */
    /**
    * A callback that is used when the users clicks on the sprite with thier mouse
    * @method click
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user clicks the mouse down over the sprite
    * @method mousedown
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user releases the mouse that was over the sprite
    * for this callback to be fired the mouse must have been pressed down over the sprite
    * @method mouseup
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the users mouse rolls over the sprite
    * @method mouseover
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the users mouse leaves the sprite
    * @method mouseout
    * @param interactionData {InteractionData}
    */

    /*
    * TOUCH Callbacks
    */

    /**
    * A callback that is used when the users taps on the sprite with thier finger
    * basically a touch version of click
    * @method tap
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user touch's over the sprite
    * @method touchstart
    * @param interactionData {InteractionData}
    */

    /**
    * A callback that is used when the user releases the touch that was over the sprite
    * for this callback to be fired. The touch must have started over the sprite
    * @method touchend
    * @param interactionData {InteractionData}
    */
};

gf.inherits(gf.Sprite, gf.DisplayObject, {
    /**
     * Defines a new animation on the Sprite
     *
     * @method addAnimation
     * @param name {String} The name of the animation, any string you want to name it
     * @param frames {Texture|Array} The frames of the animation, you can pass one gf.Texture
     *      as a frame, or an Array of gf.Texture's
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.addAnimation('walk-left', new gf.Texture())
     *          .addAnimation('walk-right', [new gf.Texture(), new gf.Texture()]);
     */
    addAnimation: function(name, frames, speed) {
        if(!frames)
            throw 'No textures passed to addAnimation()';

        //ensure all the items in the array are textures
        if(frames instanceof Array) {
            for(var i = 0, il = frames.length; i < il; ++i) {
                if(typeof frames[i] === 'string') {
                    if(!PIXI.TextureCache[frames[i]])
                        throw 'Texture ' + frames[i] + ' is not in cache, please load it first';

                    frames[i] = PIXI.TextureCache[frames[i]];
                }
            }
        }

        //if there is a single texture passed, then put it in an array
        if(frames instanceof gf.Texture)
            frames = [frames];

        //if a string is passed, get it from the texture cache
        if(typeof frames === 'string') {
            if(!PIXI.TextureCache[frames])
                throw 'Texture ' + frames + ' is not in cache, please load it first';

            frames = [PIXI.TextureCache[frames]];
        }

        //create a movie clip from the textures
        var clip = new PIXI.MovieClip(frames);
        clip.stop();
        clip.visible = false;
        clip.name = name;

        if(this.interactive)
            clip.setInteractive(this.interactive);

        if(speed)
            clip.animationSpeed = speed;

        this.addChild(clip);

        this.anim[name] = clip.childIndex;

        return this;
    },
    /**
     * Sets the active animation of the sprite, and starts the animation at index 0
     *
     * @method setActiveAnimation
     * @param name {String} The name of the animation to play (defined with addAnimation());
     * @param cb {Function} Callback when the animation completes, NOT YET IMPLEMENTED
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.addAnimation('me', new gf.Texture())
     *          .setActiveAnimation('me');
     */
    setActiveAnimation: function(name, cb) {
        if(this.anim[name] !== undefined) {
            if(this.currentAnim) {
                this.currentAnim.stop();
                this.currentAnim.visible = false;
            }

            this.currentAnim = this.children[this.anim[name]];
            this.currentAnim.visible = true;
            this.currentAnim.gotoAndPlay(0);
            //TODO: Callback
            if(cb) setTimeout(cb, 1);
        } else {
            throw 'Unknown animation ' + name;
        }

        return this;
    },
    /**
     * Convenience method for setting the position of the Sprite.
     *
     * @method setPosition
     * @param x {Number|Array|Vector|Point} X coord to put the sprite at.
     *       If an Array, Vector, or Point is passed then the y parameter is ignored
     * @param y {Number} Y coord to put the sprite at
     * @return {Sprite} Returns itself for chainability
     * @example
     *      spr.setPosition(1, 1)
     *          .setPosition([5, 5])
     *          .setPosition(new gf.Point(10, 10))
     *          .setPosition(new gf.Vector(20, 20));
     */
    setPosition: function(x, y) {
        if(x instanceof gf.Vector || x instanceof gf.Point) {
            this.position.x = x.x;
            this.position.y = x.y;
        }
        else if(x instanceof Array) {
            this.position.x = x[0];
            this.position.y = x[1];
        } else {
            this.position.x = x;
            this.position.y = y;
        }

        return this;
    },
    /**
     * Checks if the name is the active animation
     *
     * @method isActiveAnimation
     * @param name {String} The name of the animation to check if it is currently active
     * @return {Boolean} true if the animation is active, false otherwise.
     * @example
     *      spr.addAnimation('walk-left', new gf.Texture())
     *          .isActiveAnimation('walk-left'); //false
     *
     *      spr.setActiveAnimation('walk-left')
     *          .isActiveAnimation('walk-left'); //true
     */
    isActiveAnimation: function(name) {
        return this.currentAnim.name === name;
    },
    /**
     * Sets whether or not this sprite is interactive (can be clicked)
     *
     * @method setInteractive
     * @param interactive {Boolean}
     */
    setInteractive: function(interactive) {
        this.interactive = interactive;
        for(var i = 0, il = this.children.length; i < il; ++i) {
            if(this.children[i].setInteractive)
                this.children[i].setInteractive(interactive);
        }
    }
});

//Add some PIXI.MovieClip functions that just call that
// function for the currently playing animation
['stop', 'play', 'gotoAndStop', 'gotoAndPlay'].forEach(function(fn) {
    gf.Sprite.prototype[fn] = function() {
        if(this.currentAnim && this.currentAnim[fn])
            this.currentAnim[fn].apply(this.currentAnim, arguments);
    };
});

/**
 * Stops the currently active animation
 * @method stop
 */

/**
 * Plays the currently active animation
 * @method play
 */

/**
 * Stops the currently active animation and goes to a specific frame
 * @method gotoAndStop
 * @param frameNumber {Number} frame index to stop at
 */

/**
 * Goes to a specific frame and begins playing the currently active animation
 * @method gotoAndPlay
 * @param frameNumber {Number} frame index to start at
 */<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars */
/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { ToggleBar, DataTablePageView, SearchBar, PageButton } from '../widgets';
import { DataTable, DataTableRow, DataTableHeaderRow } from '../widgets/DataTable';

import { recordKeyExtractor, getItemLayout, getPageDispatchers } from './dataTableUtilities';

import { ROUTES } from '../navigation';
import { generalStrings, vaccineStrings } from '../localization';
import globalStyles from '../globalStyles';

const VaccineAdminPageComponent = ({
  data,
  sortKey,
  isAscending,
  columns,
  dataSet,
  modalKey,
  dataState,
  searchTerm,
  onFilterData,
  onEditLocationCode,
  onEditLocationDescription,
  onSortColumn,
  onToggleFridges,
  onToggleSensors,
}) => {
  const getCallback = colKey => {
    switch (colKey) {
      case 'description':
        return onEditLocationDescription;
      case 'code':
        return onEditLocationCode;
      default:
        return null;
    }
  };

  const renderHeader = React.useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        onPress={onSortColumn}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending, columns]
  );

  const renderRow = React.useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = recordKeyExtractor(item);

      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          rowIndex={index}
        />
      );
    },
    [data, dataState]
  );

  const placeholderString =
    dataSet === 'fridges'
      ? `${generalStrings.search_by} ${generalStrings.code} ${generalStrings.or} ${generalStrings.description}`
      : `${generalStrings.search_by} ${generalStrings.name}`;

  const toggles = React.useMemo(
    () => [
      { text: vaccineStrings.fridges, onPress: onToggleFridges, isOn: dataSet === 'fridges' },
      { text: vaccineStrings.sensors, onPress: onToggleSensors, isOn: dataSet === 'sensors' },
    ],
    [dataSet]
  );

  return (
    <DataTablePageView>
      <View style={globalStyles.pageTopSectionContainer}>
        <ToggleBar toggles={toggles} />
        <SearchBar onChangeText={onFilterData} value={searchTerm} placeholder={placeholderString} />
        <PageButton text="" />
      </View>
      <DataTable
        data={data}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={recordKeyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
      />
    </DataTablePageView>
  );
};

const mapStateToProps = state => {
  const { pages } = state;
  const { vaccinesAdmin } = pages;

  return vaccinesAdmin;
};

const mapDispatchToProps = dispatch => getPageDispatchers(dispatch, null, ROUTES.VACCINES_ADMIN);

VaccineAdminPageComponent.propTypes = {
  data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  columns: PropTypes.array.isRequired,
  dataSet: PropTypes.string.isRequired,
  modalKey: PropTypes.string.isRequired,
  dataState: PropTypes.object.isRequired,
  searchTerm: PropTypes.string.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onEditLocationCode: PropTypes.func.isRequired,
  onEditLocationDescription: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
  onToggleFridges: PropTypes.func.isRequired,
  onToggleSensors: PropTypes.func.isRequired,
};

export const VaccineAdminPage = connect(
  mapStateToProps,
  mapDispatchToProps
)(VaccineAdminPageComponent);
<commit_msg>Add VaccineAdminPage updates<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import React from 'react';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { ToggleBar, DataTablePageView, SearchBar, PageButton } from '../widgets';
import { DataTable, DataTableRow, DataTableHeaderRow } from '../widgets/DataTable';
import { DataTablePageModal } from '../widgets/modals';
import { BottomConfirmModal } from '../widgets/bottomModals';

import { recordKeyExtractor, getItemLayout, getPageDispatchers } from './dataTableUtilities';

import { TemperatureSyncActions } from '../actions/TemperatureSyncActions';
import { selectIsSyncingTemperatures } from '../selectors/temperatureSync';

import { ROUTES } from '../navigation';
import { MODAL_KEYS } from '../utilities';

import { modalStrings, buttonStrings, generalStrings, vaccineStrings } from '../localization';
import globalStyles, { SUSSOL_ORANGE } from '../globalStyles';

const VaccineAdminPageComponent = ({
  data,
  sortKey,
  isAscending,
  columns,
  dataSet,
  modalKey,
  dataState,
  searchTerm,
  onFilterData,
  onEditLocationCode,
  onEditLocationDescription,
  onSortColumn,
  onToggleFridges,
  onToggleSensors,
  onSelectLocation,
  onEditSensorName,
  onApplySensorLocation,
  onCloseModal,
  modalValue,
  dispatch,
  onAddFridge,
  hasSelection,
  onDeselectAll,
  onDeleteRecords,
  onCheck,
  scanForSensors,
  isScanning,
}) => {
  const getCallback = colKey => {
    switch (colKey) {
      case 'description':
        return onEditLocationDescription;
      case 'code':
        return onEditLocationCode;
      case 'name':
        return onEditSensorName;
      case 'currentLocationName':
        return onSelectLocation;
      case 'remove':
        return onCheck;
      default:
        return null;
    }
  };

  const renderHeader = React.useCallback(
    () => (
      <DataTableHeaderRow
        columns={columns}
        onPress={onSortColumn}
        isAscending={isAscending}
        sortKey={sortKey}
      />
    ),
    [sortKey, isAscending, columns]
  );

  const renderRow = React.useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = recordKeyExtractor(item);

      return (
        <DataTableRow
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          columns={columns}
          getCallback={getCallback}
          rowIndex={index}
        />
      );
    },
    [data, dataState]
  );

  const getModalOnSelect = () => {
    switch (modalKey) {
      case MODAL_KEYS.SELECT_LOCATION:
        return onApplySensorLocation;

      default:
        return null;
    }
  };

  const onPress = dataSet === 'fridges' ? onAddFridge : scanForSensors;
  const buttonText = dataSet === 'fridges' ? buttonStrings.add_fridge : buttonStrings.start_scan;
  const placeholderString =
    dataSet === 'fridges'
      ? `${generalStrings.search_by} ${generalStrings.code} ${generalStrings.or} ${generalStrings.description}`
      : `${generalStrings.search_by} ${generalStrings.name}`;
  const toggles = React.useMemo(
    () => [
      { text: vaccineStrings.fridges, onPress: onToggleFridges, isOn: dataSet === 'fridges' },
      { text: vaccineStrings.sensors, onPress: onToggleSensors, isOn: dataSet === 'sensors' },
    ],
    [dataSet]
  );

  return (
    <DataTablePageView>
      <View style={globalStyles.pageTopSectionContainer}>
        <ToggleBar toggles={toggles} />
        <SearchBar
          viewStyle={localStyles.searchBar}
          onChangeText={onFilterData}
          value={searchTerm}
          placeholder={placeholderString}
        />
        {isScanning ? (
          <ActivityIndicator
            style={localStyles.indicatorContainer}
            color={SUSSOL_ORANGE}
            size="small"
          />
        ) : (
          <PageButton text={buttonText} onPress={onPress} />
        )}
      </View>

      <DataTable
        data={data}
        renderRow={renderRow}
        extraData={dataState}
        renderHeader={renderHeader}
        keyExtractor={recordKeyExtractor}
        getItemLayout={getItemLayout}
        columns={columns}
      />

      <DataTablePageModal
        fullScreen={false}
        isOpen={!!modalKey}
        modalKey={modalKey}
        onClose={onCloseModal}
        onSelect={getModalOnSelect()}
        dispatch={dispatch}
        currentValue={modalValue}
      />

      <BottomConfirmModal
        isOpen={hasSelection}
        questionText={modalStrings.remove_these_items}
        onCancel={onDeselectAll}
        onConfirm={onDeleteRecords}
        confirmText={modalStrings.remove}
      />
    </DataTablePageView>
  );
};

const localStyles = StyleSheet.create({
  // Required to override search bar styles to have it properly nest between
  // toggles and buttons.
  searchBar: {
    borderBottomWidth: 1,
    flexDirection: 'row',
    alignItems: 'center',
    marginHorizontal: 5,
    flex: 1,
    flexGrow: 1,
  },
  // Used to match the width of a PageButton
  indicatorContainer: { width: 140 },
});

const mapStateToProps = state => {
  const { pages } = state;
  const { vaccinesAdmin } = pages;
  const isScanning = selectIsSyncingTemperatures(state);

  return { ...vaccinesAdmin, isScanning };
};

const mapDispatchToProps = dispatch => {
  const scanForSensors = () => dispatch(TemperatureSyncActions.scanForSensors());

  return { ...getPageDispatchers(dispatch, 'Location', ROUTES.VACCINES_ADMIN), scanForSensors };
};

VaccineAdminPageComponent.defaultProps = { modalValue: null };

VaccineAdminPageComponent.propTypes = {
  data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
  sortKey: PropTypes.string.isRequired,
  isAscending: PropTypes.bool.isRequired,
  columns: PropTypes.array.isRequired,
  dataSet: PropTypes.string.isRequired,
  modalKey: PropTypes.string.isRequired,
  dataState: PropTypes.object.isRequired,
  searchTerm: PropTypes.string.isRequired,
  onFilterData: PropTypes.func.isRequired,
  onEditLocationCode: PropTypes.func.isRequired,
  onEditLocationDescription: PropTypes.func.isRequired,
  onSortColumn: PropTypes.func.isRequired,
  onToggleFridges: PropTypes.func.isRequired,
  onToggleSensors: PropTypes.func.isRequired,
  onSelectLocation: PropTypes.func.isRequired,
  onEditSensorName: PropTypes.func.isRequired,
  onApplySensorLocation: PropTypes.func.isRequired,
  onCloseModal: PropTypes.func.isRequired,
  modalValue: PropTypes.any,
  dispatch: PropTypes.func.isRequired,
  onAddFridge: PropTypes.func.isRequired,
  hasSelection: PropTypes.bool.isRequired,
  onDeselectAll: PropTypes.func.isRequired,
  onDeleteRecords: PropTypes.func.isRequired,
  onCheck: PropTypes.func.isRequired,
  scanForSensors: PropTypes.func.isRequired,
  isScanning: PropTypes.bool.isRequired,
};

export const VaccineAdminPage = connect(
  mapStateToProps,
  mapDispatchToProps
)(VaccineAdminPageComponent);
<|endoftext|>


<commit_before>import {
  CHECKBOX_UPDATE,
  RANGE_UPDATE,
  FUZZY,
  SEARCH_KEY_UPDATE,
  SETTINGS_RESET,
} from '../../actions/types';

export function updateCheckbox(key, value) {
  return {
    type: CHECKBOX_UPDATE,
    payload: {
      key,
      value,
    },
  };
}

export function updateFuzzyCheckbox(key, value) {
  return {
    type: FUZZY + CHECKBOX_UPDATE,
    payload: {
      key,
      value,
    },
  };
}

export function updateFuzzyThresholdRange(value) {
  return {
    type: FUZZY + RANGE_UPDATE,
    payload: {
      key: 'threshold',
      value: value / 10,
    },
  };
}

export function updateFuzzySearchKeys(value) {
  return {
    type: FUZZY + SEARCH_KEY_UPDATE,
    payload: {
      key: 'keys',
      value,
    },
  };
}

export function resetSettings() {
  return {
    type: SETTINGS_RESET,
  };
}
<commit_msg>Add NUMBER_UPDATE action in settings actons<commit_after>import {
  CHECKBOX_UPDATE,
  RANGE_UPDATE,
  FUZZY,
  SEARCH_KEY_UPDATE,
  SETTINGS_RESET,
  NUMBER_UPDATE,
} from '../../actions/types';

export function updateNumber(key, value) {
  return {
    type: NUMBER_UPDATE,
    payload: {
      key,
      value,
    },
  };
}

export function updateCheckbox(key, value) {
  return {
    type: CHECKBOX_UPDATE,
    payload: {
      key,
      value,
    },
  };
}

export function updateFuzzyCheckbox(key, value) {
  return {
    type: FUZZY + CHECKBOX_UPDATE,
    payload: {
      key,
      value,
    },
  };
}

export function updateFuzzyThresholdRange(value) {
  return {
    type: FUZZY + RANGE_UPDATE,
    payload: {
      key: 'threshold',
      value: value / 10,
    },
  };
}

export function updateFuzzySearchKeys(value) {
  return {
    type: FUZZY + SEARCH_KEY_UPDATE,
    payload: {
      key: 'keys',
      value,
    },
  };
}

export function resetSettings() {
  return {
    type: SETTINGS_RESET,
  };
}
<|endoftext|>


<commit_before>(function ($) {
  function allFieldsValid() {
    var fields = [
      'BatchVol',
      'GBill',
      'HBill',
      'DHop',
      'BoilTime',
      'BoilRate',
      'TempGrain',
      'TempMash',
      'VolSparge',
      'PotSize',
      'KettleID',
      'LossTrub',
      'LossFermTrub',
      'HChilled',
      'VolPackaged',
      'Gabs',
      'Habs'
    ];
    for (i = 0; i < fields.length; i++) {
      if (!$('#' + fields[i]).val().match(/^d*(.\d+)?/)) {
        return false;
      }
    }
    return true;
  }
  function updateCalc() {
    if (!allFieldsValid()) {
      return;
    }
    var BatchVol = parseFloat($('#BatchVol').val()),
    GBill = parseFloat($('#GBill').val()),
    HBill = parseFloat($('#HBill').val()),
    DHop = parseFloat($('#DHop').val()),
    BoilTime = parseFloat($('#BoilTime').val()),
    BoilRate = parseFloat($('#BoilRate').val()),
    TempGrain = parseFloat($('#TempGrain').val()),
    TempMash = parseFloat($('#TempMash').val()),
    VolSparge = parseFloat($('#VolSparge').val()),
    PotSize = parseFloat($('#PotSize').val()),
    KettleID = parseFloat($('#KettleID').val()),
    LossTrub = parseFloat($('#LossTrub').val()),
    Gabs = parseFloat($('#Gabs').val()),
    Habs = parseFloat($('#Habs').val()),
    MashAdj = parseFloat($('#MashAdj').val()),
    StrikeAdj = parseFloat($('#StrikeAdj').val()),
    LossFermTrub = parseFloat($('#LossFermTrub').val()),
    LossBoil = BoilTime * BoilRate / 60,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    LossTot = LossGrain + LossHop + LossBoil + LossTrub,
    WaterTot = BatchVol + LossTot,
    VolStart = (WaterTot - VolSparge),
    TempStrike = TempMash + (0.05 * GBill / VolStart) * (TempMash - TempGrain),
    MashAdj = 1.022494888,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempMash').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempMash').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempMash').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempMash').val(),3) + 0.00000532769 * Math.pow($('#TempMash').val(),2) - 0.000292675 * $('#TempMash').val() + 1.00493),
    StrikeAdj = 1.025641026,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempStrike').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempStrike').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempStrike').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempStrike').val(),3) + 0.00000532769 * Math.pow($('#TempStrike').val(),2) - 0.000292675 * $('#TempStrike').val() + 1.00493),
    VolStrike = VolStart * StrikeAdj,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    VolMash = (VolStart + GBill * 0.08) * MashAdj,
    VolPre = (WaterTot - LossGrain) * 1.043841336,
    VolPost = (WaterTot - LossTot + LossTrub) * 1.043841336,
    VolChilled = ( VolPost / 1.043841336 ) - LossTrub,
    VolPackaged = VolChilled - LossFermTrub - ( DHop * Gabs ),
    GalH = 294.118334834 / (KettleID * KettleID),
    HTot = GalH * WaterTot,
    HStart = GalH * VolStart,
    HStrike = GalH * VolStrike,
    HMash = GalH * VolMash,
    HPre = GalH * VolPre,
    HChilled = GalH * VolChilled,
    MashThick = VolStart * 4 / GBill,
    VolMinSparge = Math.max(0,((WaterTot + GBill * 0.08) * MashAdj) - (PotSize - 0.01 )),
    HPost = GalH * VolPost;

    // console.log(VolStrike, WaterTot, MashThick, TempStrike);
    $('#WaterTot').text(WaterTot.toPrecision(3));
    $('#VolStrike').text(VolStrike.toPrecision(3));
    $('#LossBoil').text(LossBoil.toPrecision(3));
    $('#LossHop').text(LossHop.toPrecision(3));
    $('#LossGrain').text(LossGrain.toPrecision(3));
    $('#LossTot').text(LossTot.toPrecision(3));
    $('#LossFermTrub').text(LossFermTrub.toPrecision(3));
    $('#VolStart').text(VolStart.toPrecision(3));
    $('#VolMash').text(VolMash.toPrecision(3));
    $('#VolPre').text(VolPre.toPrecision(3));
    $('#VolPost').text(VolPost.toPrecision(3));
    $('#TempStrike').text(TempStrike.toPrecision(4));
    $('#MashAdj').text(MashAdj.toPrecision(12));
    $('#Strikeadj').text(StrikeAdj.toPrecision(12));
    $('#GalH').text(GalH.toPrecision(5));
    $('#HTot').text(HTot.toPrecision(5));
    $('#HStart').text(HStart.toPrecision(5));
    $('#HStrike').text(HStrike.toPrecision(5));
    $('#HMash').text(HMash.toPrecision(5));
    $('#HPre').text(HPre.toPrecision(5));
    $('#HPost').text(HPost.toPrecision(5));
    $('#DHop').text(DHop.toPrecision(5));
    $('#HChilled').text(HChilled.toPrecision(5));
    $('#MashThick').text(MashThick.toPrecision(3));
    $('#VolMinSparge').text(VolMinSparge.toPrecision(3));
    $('#VolChilled').text(VolChilled.toPrecision(3));
    $('#VolPackaged').text(VolPackaged.toPrecision(3));
  }
  function validateField(field) {
    $field = $(field);
    if ($field.val().match(/^d*(.\d+)?/)) {
      $field.parents('div.control-group:first').removeClass('error');
      return true;
    }
    $field.parents('div.control-group:first').addClass('error');
    return false;
  }
  $(document).ready(function () {
    $('#calcForm').delegate('input[type=text]', 'change', function () {
      if (validateField(this)) {
        updateCalc();
      }
    }).submit(function (e) {
      e.defaultPrevented;
      updateCalc();
    });
    updateCalc();
    $('#GBill').focus();
  });
}) (jQuery);
<commit_msg>Update PricelessCalc-Imperial.js<commit_after>(function ($) {
  function allFieldsValid() {
    var fields = [
      'BatchVol',
      'GBill',
      'HBill',
      'DHop',
      'BoilTime',
      'BoilRate',
      'TempGrain',
      'TempMash',
      'VolSparge',
      'PotSize',
      'KettleID',
      'LossTrub',
      'LossFermTrub',
      'HChilled',
      'VolPackaged',
      'Gabs',
      'Habs'
    ];
    for (i = 0; i < fields.length; i++) {
      if (!$('#' + fields[i]).val().match(/^d*(.\d+)?/)) {
        return false;
      }
    }
    return true;
  }
  function updateCalc() {
    if (!allFieldsValid()) {
      return;
    }
    var BatchVol = parseFloat($('#BatchVol').val()),
    GBill = parseFloat($('#GBill').val()),
    HBill = parseFloat($('#HBill').val()),
    DHop = parseFloat($('#DHop').val()),
    BoilTime = parseFloat($('#BoilTime').val()),
    BoilRate = parseFloat($('#BoilRate').val()),
    TempGrain = parseFloat($('#TempGrain').val()),
    TempMash = parseFloat($('#TempMash').val()),
    VolSparge = parseFloat($('#VolSparge').val()),
    PotSize = parseFloat($('#PotSize').val()),
    KettleID = parseFloat($('#KettleID').val()),
    LossTrub = parseFloat($('#LossTrub').val()),
    Gabs = parseFloat($('#Gabs').val()),
    Habs = parseFloat($('#Habs').val()),
    MashAdj = parseFloat($('#MashAdj').val()),
    StrikeAdj = parseFloat($('#StrikeAdj').val()),
    LossFermTrub = parseFloat($('#LossFermTrub').val()),
    LossBoil = BoilTime * BoilRate / 60,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    LossTot = LossGrain + LossHop + LossBoil + LossTrub,
    WaterTot = BatchVol + LossTot,
    VolStart = (WaterTot - VolSparge),
    TempStrike = TempMash + (0.05 * GBill / VolStart) * (TempMash - TempGrain),
    MashAdj = 1.022494888,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempMash').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempMash').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempMash').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempMash').val(),3) + 0.00000532769 * Math.pow($('#TempMash').val(),2) - 0.000292675 * $('#TempMash').val() + 1.00493),
    StrikeAdj = 1.025641026,
    //( 4.13643 * Math.pow(10,-16) * Math.pow($('#TempStrike').val(),6) - 4.05998 * Math.pow(10,-13) * Math.pow($('#TempStrike').val(),5) + 1.61536 * Math.pow(10,-10) * Math.pow($('#TempStrike').val(),4) - 3.44854 * Math.pow(10,-8) * Math.pow($('#TempStrike').val(),3) + 0.00000532769 * Math.pow($('#TempStrike').val(),2) - 0.000292675 * $('#TempStrike').val() + 1.00493),
    VolStrike = VolStart * StrikeAdj,
    LossHop = HBill * Habs,
    LossGrain = GBill * Gabs,
    VolMash = (VolStart + GBill * 0.08) * MashAdj,
    VolPre = (WaterTot - LossGrain) * 1.043841336,
    VolPost = (WaterTot - LossTot + LossTrub) * 1.043841336,
    VolChilled = ( VolPost / 1.043841336 ) - LossTrub,
    VolPackaged = VolChilled - LossFermTrub - ( DHop * Gabs ),
    GalH = 294.118334834 / (KettleID * KettleID),
    HTot = GalH * WaterTot,
    HStart = GalH * VolStart,
    HStrike = GalH * VolStrike,
    HMash = GalH * VolMash,
    HPre = GalH * VolPre,
    HChilled = GalH * VolChilled,
    MashThick = VolStart * 4 / GBill,
    VolMinSparge = Math.max(0,((WaterTot + GBill * 0.08) * MashAdj) - (PotSize - 0.01 )),
    HPost = GalH * VolPost;

    // console.log(VolStrike, WaterTot, MashThick, TempStrike);
    $('#WaterTot').text(WaterTot.toPrecision(3));
    $('#VolStrike').text(VolStrike.toPrecision(3));
    $('#LossBoil').text(LossBoil.toPrecision(3));
    $('#LossHop').text(LossHop.toPrecision(3));
    $('#LossGrain').text(LossGrain.toPrecision(3));
    $('#LossTot').text(LossTot.toPrecision(3));
    $('#LossFermTrub').text(LossFermTrub.toPrecision(3));
    $('#VolStart').text(VolStart.toPrecision(3));
    $('#VolMash').text(VolMash.toPrecision(3));
    $('#VolPre').text(VolPre.toPrecision(3));
    $('#VolPost').text(VolPost.toPrecision(3));
    $('#TempStrike').text(TempStrike.toPrecision(4));
    $('#MashAdj').text(MashAdj.toPrecision(12));
    $('#Strikeadj').text(StrikeAdj.toPrecision(12));
    $('#GalH').text(GalH.toPrecision(4));
    $('#HTot').text(HTot.toPrecision(4));
    $('#HStart').text(HStart.toPrecision(4));
    $('#HStrike').text(HStrike.toPrecision(4));
    $('#HMash').text(HMash.toPrecision(4));
    $('#HPre').text(HPre.toPrecision(4));
    $('#HPost').text(HPost.toPrecision(4));
    $('#DHop').text(DHop.toPrecision(4));
    $('#HChilled').text(HChilled.toPrecision(4));
    $('#MashThick').text(MashThick.toPrecision(3));
    $('#VolMinSparge').text(VolMinSparge.toPrecision(3));
    $('#VolChilled').text(VolChilled.toPrecision(3));
    $('#VolPackaged').text(VolPackaged.toPrecision(3));
  }
  function validateField(field) {
    $field = $(field);
    if ($field.val().match(/^d*(.\d+)?/)) {
      $field.parents('div.control-group:first').removeClass('error');
      return true;
    }
    $field.parents('div.control-group:first').addClass('error');
    return false;
  }
  $(document).ready(function () {
    $('#calcForm').delegate('input[type=text]', 'change', function () {
      if (validateField(this)) {
        updateCalc();
      }
    }).submit(function (e) {
      e.defaultPrevented;
      updateCalc();
    });
    updateCalc();
    $('#GBill').focus();
  });
}) (jQuery);
<|endoftext|>


<commit_before>/*
 * angular-feedback-form
 *
 * Angular JS Feedback Button with a basic Feedback Form
 *
 * (c) 2015 ediri
 * MIT Licenese
 */

(function () {
    'use strict';

    angular.module('feedbackForm', []).directive('feedbackbutton', ['$window', function ($window) {
        return {
            restrict: 'E',
            replace: true,
            scope: {
                buttonText: '@'
            },
            link: function (scope, element, attrs) {

            },
            template: "
{{buttonText}}
"
        }
            ;
    }]);
})();<commit_msg>initial import<commit_after>/*
 * angular-feedback-form
 *
 * Angular JS Feedback Button with a basic Feedback Form
 *
 * (c) 2015 ediri
 * MIT Licenese
 */

(function () {
    'use strict';

    angular.module('feedbackForm', []).directive('feedbackbutton', ['$window', function ($window) {
            return {
                restrict: 'E',
                replace: true,
                scope: {
                    buttonText: '@',
                    modalId:'@'
                },
                link: function (scope, element, attrs) {

                },
                template: "
{{buttonText}}
"
            }
        }])

        .directive('feedbackmodal', ['$window', function ($window) {
            return {
                restrict: 'E',
                replace: true,
                scope: {
                    id: '@'
                },
                link: function (scope, element, attrs) {

                },
                template: "
 \

 \

 \

 \
                               <button type=\"button\" class=\"close\" data-dismiss=\"modal\">×</button> \

Modal Header
 \

 \

 \

Some text in the modal.
 \

 \

 \
                               <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Close</button> \

 \

 \

 \

"
            }
        }]);
})();<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import FilterOptions from './FilterOptions';
import _isUndefined from 'lodash/isUndefined';

class Filter extends React.Component {
  constructor() {
    super();
    this.state = {
      clearFieldIsVisible: false,
    };
    this.doFilterBySearchField = this.doFilterBySearchField.bind(this);
    this.clearAllFields = this.clearAllFields.bind(this);
  }

  toggleClearField() {
    this.setState({
      clearFieldIsVisible: this.refs.search.value !== '',
    });
  }

  doFilterBySearchField() {
    if (this.props.onFilter) {
      this.props.onFilter(this.refs.search.value);
    }
  }

  clearAllFields() {
    if (this.props.onClearAll) {
      this.props.onClearAll(this.refs.search.value);
    }
  }

  clearSearchField() {
    if (this.props.onFilter) {
      this.refs.search.value = '';
      this.setState({ clearFieldIsVisible: false }, this.props.onFilter(this.refs.search.value));
    }
  }

  render() {
    const timesIconStyle = {
      paddingTop: '10px',
      marginLeft: '-25px',
      cursor: 'pointer',
    };

    const {
      applyFilterButtonLabel,
      cancelButtonLabel,
      clearFilterOptionsButtonLabel,
      filterButtonLabel,
      placeholder,
      children,
    } = this.props;

    let clearFieldIcon = '';

    if (this.state.clearFieldIsVisible) {
      clearFieldIcon = (

           this.clearSearchField()} style={timesIconStyle} />

      );
    }

    return (
      <form className="sv-form">

          <input
            className="on-center"
            onKeyUp={() => {
              this.toggleClearField();
              this.doFilterBySearchField();
            }}
            placeholder={placeholder}
            ref="search"
            type="search"
          />
          {clearFieldIcon}
          <FilterOptions
            applyFilterButtonLabel={applyFilterButtonLabel}
            cancelButtonLabel={cancelButtonLabel}
            clearAllFields={this.clearAllFields}
            clearFilterOptionsButtonLabel={clearFilterOptionsButtonLabel}
            doFilterBySearchField={this.doFilterBySearchField}
            filterButtonLabel={filterButtonLabel}
            hasFilterOptions={!_isUndefined(children)}
          >
            {this.props.children}
          </FilterOptions>

      </form>
    );
  }
}

Filter.defaultProps = {
  applyFilterButtonLabel: 'Aplicar Filtro',
  cancelButtonLabel: 'Cancelar',
  clearFilterOptionsButtonLabel: 'Limpar Filtro',
  filterButtonLabel: 'Opções',
  placeholder: 'Buscar',
};

Filter.propTypes = {
  applyFilterButtonLabel: PropTypes.string,
  cancelButtonLabel: PropTypes.string,
  clearFilterOptionsButtonLabel: PropTypes.string,
  filterButtonLabel: PropTypes.string,
  onClearAll: PropTypes.func,
  onFilter: PropTypes.func,
  placeholder: PropTypes.string,
};

Filter.displayName = 'Filter';
export default Filter;
<commit_msg>fix ref problems<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import FilterOptions from './FilterOptions';
import _isUndefined from 'lodash/isUndefined';

class Filter extends React.Component {
  constructor() {
    super();
    this.state = {
      clearFieldIsVisible: false,
    };
    this.doFilterBySearchField = this.doFilterBySearchField.bind(this);
    this.clearAllFields = this.clearAllFields.bind(this);
    this.searchFiled;
  }

  toggleClearField() {
    this.setState({
      clearFieldIsVisible: this.searchFiled.value !== '',
    });
  }

  doFilterBySearchField() {
    if (this.props.onFilter) {
      this.props.onFilter(this.searchFiled.value);
    }
  }

  clearAllFields() {
    if (this.props.onClearAll) {
      this.props.onClearAll(this.searchFiled.value);
    }
  }

  clearSearchField() {
    if (this.props.onFilter) {
      this.searchFiled.value = '';
      this.setState({ clearFieldIsVisible: false }, this.props.onFilter(this.searchFiled.value));
    }
  }

  render() {
    const timesIconStyle = {
      paddingTop: '10px',
      marginLeft: '-25px',
      cursor: 'pointer',
    };

    const {
      applyFilterButtonLabel,
      cancelButtonLabel,
      clearFilterOptionsButtonLabel,
      filterButtonLabel,
      placeholder,
      children,
    } = this.props;

    let clearFieldIcon = '';

    if (this.state.clearFieldIsVisible) {
      clearFieldIcon = (

           this.clearSearchField()} style={timesIconStyle} />

      );
    }

    return (
      <form className="sv-form">

          <input
            className="on-center"
            onKeyUp={() => {
              this.toggleClearField();
              this.doFilterBySearchField();
            }}
            placeholder={placeholder}
            ref={searchField => (this.searchFiled = searchField)}
            type="search"
          />
          {clearFieldIcon}
          <FilterOptions
            applyFilterButtonLabel={applyFilterButtonLabel}
            cancelButtonLabel={cancelButtonLabel}
            clearAllFields={this.clearAllFields}
            clearFilterOptionsButtonLabel={clearFilterOptionsButtonLabel}
            doFilterBySearchField={this.doFilterBySearchField}
            filterButtonLabel={filterButtonLabel}
            hasFilterOptions={!_isUndefined(children)}
          >
            {this.props.children}
          </FilterOptions>

      </form>
    );
  }
}

Filter.defaultProps = {
  applyFilterButtonLabel: 'Aplicar Filtro',
  cancelButtonLabel: 'Cancelar',
  clearFilterOptionsButtonLabel: 'Limpar Filtro',
  filterButtonLabel: 'Opções',
  placeholder: 'Buscar',
};

Filter.propTypes = {
  applyFilterButtonLabel: PropTypes.string,
  cancelButtonLabel: PropTypes.string,
  clearFilterOptionsButtonLabel: PropTypes.string,
  filterButtonLabel: PropTypes.string,
  onClearAll: PropTypes.func,
  onFilter: PropTypes.func,
  placeholder: PropTypes.string,
};

Filter.displayName = 'Filter';
export default Filter;
<|endoftext|>


<commit_before>/* globals jquery, History */
'use strict';

var FAIL_CONTROLLER_NAME = 'failController';

var nitram = {
  version: '1.0.0',
  base: '',
  routed: false,
  routes: {},
  autoScrollFn: function () {
    jquery('html, body').scrollTop(0);
  }
};

/**
 * Util functions for controller default options
 */
var _noop = function () {};
var _noopMW = function (next) {
  next();
};
var _echo = function (x) {
  return x;
};

/**
 * Current controller name
 */
var _currControllerName = null;

/**
 * Registered controllers
 */
var _controllers = {};

_controllers[FAIL_CONTROLLER_NAME] = _noop;

/**
 * Returns all body route classes, separated by ' '
 * @returns {string}
 */
var _getBodyClasses = function () {
  var key, r, c;
  var arr = [];

  for (key in nitram.routes) {
    c = nitram.routes[key];
    if (typeof c.bodyClass !== 'undefined') {
      arr.push(c.bodyClass);
    }
  }
  r = arr.join(' ');
  return r;
};

/**
 * Parses a URL query (window.location.search) into an object
 * @param {string} urlSearch
 * @returns {Object.<string,string>}
 */
var _getQuery = function (urlSearch) {
  var urlParams;
  var match;

  // Regex for replacing addition symbol with a space
  var pl = /\+/g;

  var search = /([^&=]+)=?([^&]*)/g;
  var decode = function (s) {
    return decodeURIComponent(s.replace(pl, ' '));
  };
  var query = urlSearch.substring(1);

  urlParams = {};
  while ((match = search.exec(query))) {
    urlParams[decode(match[1])] = decode(match[2]);
  }

  return urlParams;
};

/**
 * El resultado de matchRoute.
 *
 * @typedef matchedRoute
 * @type Object
 * @property {boolean} found - Determina si se encontró o no la ruta
 * @property {Object.<string,string>} params - Parámetros en la ruta. Ej:
 * La ruta /hello/:who tiene el parámetro 'who' de manera que en
 * /hello/world tendríamos que params.who === 'world'.
 */

/**
 * Match route pattern
 *
 * @param {string} route
 * @returns {matchedRoute} Especifica si se encontró y devuelve los
 * parámetros
 */
var _matchRoute = function (route) {
  var i, patternSplit, pattern;
  var routeSplit = route.split('/');
  var failed = false;
  var found = false;
  var params = {};

  for (pattern in nitram.routes) {
    patternSplit = pattern.split('/');
    if (routeSplit.length === patternSplit.length) {
      failed = false;
      for (i = 0; i < patternSplit.length; i++) {
        if (patternSplit[i][0] === ':' && patternSplit[i].length > 1) {
          params[patternSplit[i].substr(1)] = routeSplit[i];
        } else if (patternSplit[i] !== routeSplit[i]) {
          params = {};
          failed = true;
          break;
        }
      }
      if (!failed) {
        found = pattern;
        break;
      }
    }
  }

  return {
    found: found,
    params: params
  };
};

/**
 * Call controller
 * @param {Object} state
 */
var _callController = function (state) {
  var matchedRoute = _matchRoute(state.route);
  var routeData = nitram.routes[matchedRoute.found];

  // reset body route classes
  jquery('body').removeClass(_getBodyClasses());

  // set body route class
  if (typeof routeData !== 'undefined' &&
    typeof routeData.bodyClass !== 'undefined') {
    jquery('body').addClass(routeData.bodyClass);
  }

  //
  // lifecycle calls
  //

  _currControllerName = state.controller;
  if (_currControllerName && _controllers[_currControllerName]) {
    (function (controller) {
      var $view = jquery(controller.view);

      // loaded
      controller.loaded.call(null, function () {

        // render
        $view.html(controller.partialFilter(state.data));
        nitram.compile($view);

        // ready
        controller.ready.call(null, state.route, state.data, state.params);
      }, state.route, state.data, state.params);

    })(_controllers[_currControllerName]);
  }

  if (state.hash) {
    window.location.hash = state.hash;
  } else if (state.autoscroll) {
    nitram.autoScrollFn();
  }
};

/**
 * On state change
 */
var _onStateChange = function () {
  var state = History.getState().data;

  var next = function () {
    if (nitram.routed) {
      _callController(state);
    }
  };

  // lifecycle call
  if (_currControllerName && _controllers[_currControllerName]) {
    _controllers[_currControllerName].afterRouteChange
      .call(null, next, state.route, state.data, state.params);
  }
};

/**
 * Intercepta request de links para hacer requests XHR en vez de recargar
 * toda la página.
 *
 * @param {Object} e - Click event
 */
var _intercept = function (e) {
  var self = this;
  var href = jquery(self).attr('href');

  var next = function () {
    if (href.indexOf('http://') !== 0 && href.indexOf('https://') !== 0) {
      if (jquery(self).data('xhr') === 'back') {
        History.back();
      } else {
        nitram.route(href, {

          // data convierte strings a valores de javascript
          //   (http://api.jquery.com/data/#data-html5)
          // el !! es para asegurarnos que sea boolean
          autoscroll: !!jquery(self).data('autoscroll')
        });
      }
    }
  };

  // lifecycle call
  if (_currControllerName && _controllers[_currControllerName]) {
    _controllers[_currControllerName].beforeRouteChange
      .call(null, next, e);
  }

  e.preventDefault();
};

//
// Exposed API
//

/**
 * Nitram init
 */
nitram.init = function () {
  var route;

  // history events
  History.Adapter.bind(window, 'statechange', _onStateChange);

  // initial compilation
  nitram.compile();

  // route
  route = window.location.pathname + window.location.search;
  if (route.indexOf(this.base) === 0) {
    route = route.substr(this.base.length);
  }
  nitram.route(route, true);
};

/**
 * Compile
 *
 * @param {Object} [$el=jquery(document)] - jQuery element in which to links to
 * intercept.
 */
nitram.compile = function ($el) {
  if (typeof $el === 'undefined') {
    $el = jquery(document);
  }

  // link interceptor
  $el.find('a[data-xhr]').unbind('click').click(_intercept);
};

/**
 * Route
 *
 * @param {string} _route
 * @param {(boolean|Object)} [_options={replace:false,autoscroll:false}] -
 * If passed a boolean, it will be the value for the replace option, and
 * autoscroll will get the default value false.
 */
nitram.route = function (_route, _options) {
  var routeData;
  var controller;
  var callController;
  var route;
  var parser;
  var buggy = false;
  var params = {};
  var options = {
    replace: false,
    autoscroll: false
  };

  // thanks to https://gist.github.com/jlong/2428561
  parser = document.createElement('a');
  parser.href = this.base + _route;

  // IE bug fix thanks to http://stackoverflow.com/a/13405933/368850
  if (parser.host === '') {
    buggy = true;
    parser.href = parser.href;
  }

  // call controller
  callController = function (data, status, params, controller,
    route, routeData, replace, autoscroll) {
    var f = replace ? 'replaceState' : 'pushState';
    var state = {
      controller: controller,
      route: route,
      hash: parser.hash,
      search: _getQuery(parser.search),
      fullPath: parser.href,
      data: data,
      params: params,
      status: status,
      autoscroll: autoscroll
    };

    // set document title
    document.title = routeData.title;

    if (History.enabled) {

      // Push state
      History[f](state, routeData.title, route);

      // Call controller directly when replacing the state
      if (replace) {
        nitram.routed = true;
        _callController(state);
      }
    } else {
      _callController(state);
    }
  };

  route = parser.pathname;
  if (buggy) {
    route = '/' + route;
  }

  // quitar trailing slash, pero dejarlo si la ruta es '/'
  if (route.length > 1 && route.lastIndexOf('/') === route.length - 1) {
    route = route.substr(0, route.length - 1);
  }

  // options defaults
  if (typeof _options !== 'undefined') {
    if (typeof _options === 'boolean') {
      options.replace = _options;
    } else {
      if (typeof _options.replace !== 'undefined') {
        options.replace = _options.replace;
      }
      if (typeof _options.autoscroll !== 'undefined') {
        options.autoscroll = _options.autoscroll;
      }
    }
  }

  // replace to true if we are on the same path
  options.replace = (window.location.pathname === this.base + route) ||
    options.replace;

  // find route data
  routeData = nitram.routes[route];
  if (typeof routeData === 'undefined') {
    routeData = _matchRoute(route);
    if (routeData.found !== false) {
      params = routeData.params;
      routeData = nitram.routes[routeData.found];
    } else {
      window.location = _route;
      return;
    }
  }

  // route data defautls
  if (typeof routeData.req === 'undefined') {
    routeData.req = true;
  }

  if (typeof routeData.title === 'undefined') {
    routeData.title = document.title;
  }

  // get controller
  controller = routeData.controller;

  // GET
  if (routeData.req) {
    jquery.ajaxSetup({
      cache: false
    });
    jquery.get(parser.href, function (data, textStatus, jqXHR) {
        callController(data, jqXHR.status, params, controller, route,
          routeData, options.replace, options.autoscroll);
      })
      .fail(function (jqXHR) {
        controller = FAIL_CONTROLLER_NAME;
        callController(null, jqXHR.status, params, controller, route,
          routeData, options.replace, options.autoscroll);
      });
  } else {
    callController(null, 200, params, controller, route, routeData,
      options.replace, options.autoscroll);
  }
};

/**
 * Register controller
 * @param {string} controller name
 * @param {Controller} controller
 */
nitram.register = function (name, controller) {
  _controllers[name] = controller ?
    controller : nitram.controllerFactory.make();
};

/**
 * Register fail controller
 * @param {Controller} controller
 */
nitram.registerFailController = function (controller) {
  _controllers[FAIL_CONTROLLER_NAME] = controller;
};

/**
 * Controller factory
 */
nitram.controllerFactory = {
  _defaults: {

    // ID or DOM element where the partial is going to be injected
    view: '#main',

    /**
     * Function that filters the partial from the ajax response. The returned
     * value is the one that nitram injects in the view.
     *
     * @example
     * partialFilter: function (data) { return data.html; }
     */
    partialFilter: _echo,

    /**
     * Entry lifecycle function that is called once the request is ready, and
     * just before the partial is injected into the view. Receives a callback
     * that need to be called when finished.
     *
     * @example
     * loaded: function (next) {
     *   jquery('#loading').hide();
     *   next();
     * }
     */
    loaded: _noopMW,

    /**
     * Entry lifecycle function that is called after the partial injection.
     * Does not have a callback, because this is the last step of the
     * controller entry lifecycle.
     */
    ready: _noop,

    /**
     * Exit lifecycle function that is called before a change of route: when
     * user clicks a nitram compiled ancho or with programtic routing
     * (i.e. nitram.route('/contact')). It gets not called on a state change
     * that wasn't generated by the previous two actions. Receives a callback
     * that need to be called when finished.
     */
    beforeRouteChange: _noopMW,

    /**
     * Exit lifecycle function that is called when there is a change of route.
     * Receives a callback that need to be called when finished.
     */
    afterRouteChange: _noopMW
  }
};

/**
 * Set defaults
 */
nitram.controllerFactory.setDefaults = function (options) {
  var key;
  for (key in options) {
    if (nitram.controllerFactory._defaults.hasOwnProperty(key)) {
      nitram.controllerFactory._defaults[key] = options[key];
    }
  }
};

/**
 * Call default lifecycle method
 */
nitram.controllerFactory.callDefault = function (name) {
  var args = Array.prototype.slice.call(arguments);
  args.shift();

  nitram.controllerFactory._defaults[name].apply(null, args);
};

/**
 * Make controller
 * @param {Object} [options={}]
 * @param {string} [options.view='#main']
 * @param {Function} [options.partialFilter=_echo]
 * @param {Function} [options.loaded=_noopMW]
 * @param {Function} [options.ready=_noop]
 * @param {Function} [options.beforeRouteChange=_noopMW]
 * @param {Function} [options.afterRouteChange=_noopMW]
 * @returns {Controller}
 */
nitram.controllerFactory.make = function (options) {
  var props = ['view', 'partialFilter', 'loaded', 'ready',
    'beforeRouteChange', 'afterRouteChange'
  ];
  var controller = {};
  var i, n;

  for (i = 0, n = props.length; i < n; i++) {
    if (options && typeof options[props[i]] !== 'undefined') {
      controller[props[i]] = options[props[i]];
    } else {
      controller[props[i]] = nitram.controllerFactory._defaults[props[i]];
    }
  }

  return controller;
};<commit_msg>hash added to route<commit_after>/* globals jquery, History */
'use strict';

var FAIL_CONTROLLER_NAME = 'failController';

var nitram = {
  version: '1.0.0',
  base: '',
  routed: false,
  routes: {},
  autoScrollFn: function () {
    jquery('html, body').scrollTop(0);
  }
};

/**
 * Util functions for controller default options
 */
var _noop = function () {};
var _noopMW = function (next) {
  next();
};
var _echo = function (x) {
  return x;
};

/**
 * Current controller name
 */
var _currControllerName = null;

/**
 * Registered controllers
 */
var _controllers = {};

_controllers[FAIL_CONTROLLER_NAME] = _noop;

/**
 * Returns all body route classes, separated by ' '
 * @returns {string}
 */
var _getBodyClasses = function () {
  var key, r, c;
  var arr = [];

  for (key in nitram.routes) {
    c = nitram.routes[key];
    if (typeof c.bodyClass !== 'undefined') {
      arr.push(c.bodyClass);
    }
  }
  r = arr.join(' ');
  return r;
};

/**
 * Parses a URL query (window.location.search) into an object
 * @param {string} urlSearch
 * @returns {Object.<string,string>}
 */
var _getQuery = function (urlSearch) {
  var urlParams;
  var match;

  // Regex for replacing addition symbol with a space
  var pl = /\+/g;

  var search = /([^&=]+)=?([^&]*)/g;
  var decode = function (s) {
    return decodeURIComponent(s.replace(pl, ' '));
  };
  var query = urlSearch.substring(1);

  urlParams = {};
  while ((match = search.exec(query))) {
    urlParams[decode(match[1])] = decode(match[2]);
  }

  return urlParams;
};

/**
 * El resultado de matchRoute.
 *
 * @typedef matchedRoute
 * @type Object
 * @property {boolean} found - Determina si se encontró o no la ruta
 * @property {Object.<string,string>} params - Parámetros en la ruta. Ej:
 * La ruta /hello/:who tiene el parámetro 'who' de manera que en
 * /hello/world tendríamos que params.who === 'world'.
 */

/**
 * Match route pattern
 *
 * @param {string} route
 * @returns {matchedRoute} Especifica si se encontró y devuelve los
 * parámetros
 */
var _matchRoute = function (route) {
  var i, patternSplit, pattern;
  var routeSplit = route.split('/');
  var failed = false;
  var found = false;
  var params = {};

  for (pattern in nitram.routes) {
    patternSplit = pattern.split('/');
    if (routeSplit.length === patternSplit.length) {
      failed = false;
      for (i = 0; i < patternSplit.length; i++) {
        if (patternSplit[i][0] === ':' && patternSplit[i].length > 1) {
          params[patternSplit[i].substr(1)] = routeSplit[i];
        } else if (patternSplit[i] !== routeSplit[i]) {
          params = {};
          failed = true;
          break;
        }
      }
      if (!failed) {
        found = pattern;
        break;
      }
    }
  }

  return {
    found: found,
    params: params
  };
};

/**
 * Call controller
 * @param {Object} state
 */
var _callController = function (state) {
  var matchedRoute = _matchRoute(state.route);
  var routeData = nitram.routes[matchedRoute.found];

  // reset body route classes
  jquery('body').removeClass(_getBodyClasses());

  // set body route class
  if (typeof routeData !== 'undefined' &&
    typeof routeData.bodyClass !== 'undefined') {
    jquery('body').addClass(routeData.bodyClass);
  }

  //
  // lifecycle calls
  //

  _currControllerName = state.controller;
  if (_currControllerName && _controllers[_currControllerName]) {
    (function (controller) {
      var $view = jquery(controller.view);

      // loaded
      controller.loaded.call(null, function () {

        // render
        $view.html(controller.partialFilter(state.data));
        nitram.compile($view);

        // ready
        controller.ready.call(null, state.route, state.data, state.params);
      }, state.route, state.data, state.params);

    })(_controllers[_currControllerName]);
  }

  if (state.hash) {
    window.location.hash = state.hash;
  } else if (state.autoscroll) {
    nitram.autoScrollFn();
  }
};

/**
 * On state change
 */
var _onStateChange = function () {
  var state = History.getState().data;

  var next = function () {
    if (nitram.routed) {
      _callController(state);
    }
  };

  // lifecycle call
  if (_currControllerName && _controllers[_currControllerName]) {
    _controllers[_currControllerName].afterRouteChange
      .call(null, next, state.route, state.data, state.params);
  }
};

/**
 * Intercepta request de links para hacer requests XHR en vez de recargar
 * toda la página.
 *
 * @param {Object} e - Click event
 */
var _intercept = function (e) {
  var self = this;
  var href = jquery(self).attr('href');

  var next = function () {
    if (href.indexOf('http://') !== 0 && href.indexOf('https://') !== 0) {
      if (jquery(self).data('xhr') === 'back') {
        History.back();
      } else {
        nitram.route(href, {

          // data convierte strings a valores de javascript
          //   (http://api.jquery.com/data/#data-html5)
          // el !! es para asegurarnos que sea boolean
          autoscroll: !!jquery(self).data('autoscroll')
        });
      }
    }
  };

  // lifecycle call
  if (_currControllerName && _controllers[_currControllerName]) {
    _controllers[_currControllerName].beforeRouteChange
      .call(null, next, e);
  }

  e.preventDefault();
};

//
// Exposed API
//

/**
 * Nitram init
 */
nitram.init = function () {
  var route;

  // history events
  History.Adapter.bind(window, 'statechange', _onStateChange);

  // initial compilation
  nitram.compile();

  // route
  route = window.location.pathname + window.location.hash +
    window.location.search;
  if (route.indexOf(this.base) === 0) {
    route = route.substr(this.base.length);
  }
  nitram.route(route, true);
};

/**
 * Compile
 *
 * @param {Object} [$el=jquery(document)] - jQuery element in which to links to
 * intercept.
 */
nitram.compile = function ($el) {
  if (typeof $el === 'undefined') {
    $el = jquery(document);
  }

  // link interceptor
  $el.find('a[data-xhr]').unbind('click').click(_intercept);
};

/**
 * Route
 *
 * @param {string} _route
 * @param {(boolean|Object)} [_options={replace:false,autoscroll:false}] -
 * If passed a boolean, it will be the value for the replace option, and
 * autoscroll will get the default value false.
 */
nitram.route = function (_route, _options) {
  var routeData;
  var controller;
  var callController;
  var route;
  var parser;
  var buggy = false;
  var params = {};
  var options = {
    replace: false,
    autoscroll: false
  };

  // thanks to https://gist.github.com/jlong/2428561
  parser = document.createElement('a');
  parser.href = this.base + _route;

  // IE bug fix thanks to http://stackoverflow.com/a/13405933/368850
  if (parser.host === '') {
    buggy = true;
    parser.href = parser.href;
  }

  // call controller
  callController = function (data, status, params, controller,
    route, routeData, replace, autoscroll) {
    var f = replace ? 'replaceState' : 'pushState';
    var state = {
      controller: controller,
      route: route,
      hash: parser.hash,
      search: _getQuery(parser.search),
      fullPath: parser.href,
      data: data,
      params: params,
      status: status,
      autoscroll: autoscroll
    };

    // set document title
    document.title = routeData.title;

    if (History.enabled) {

      // Push state
      History[f](state, routeData.title, route);

      // Call controller directly when replacing the state
      if (replace) {
        nitram.routed = true;
        _callController(state);
      }
    } else {
      _callController(state);
    }
  };

  route = parser.pathname;
  if (buggy) {
    route = '/' + route;
  }

  // quitar trailing slash, pero dejarlo si la ruta es '/'
  if (route.length > 1 && route.lastIndexOf('/') === route.length - 1) {
    route = route.substr(0, route.length - 1);
  }

  // options defaults
  if (typeof _options !== 'undefined') {
    if (typeof _options === 'boolean') {
      options.replace = _options;
    } else {
      if (typeof _options.replace !== 'undefined') {
        options.replace = _options.replace;
      }
      if (typeof _options.autoscroll !== 'undefined') {
        options.autoscroll = _options.autoscroll;
      }
    }
  }

  // replace to true if we are on the same path
  options.replace = (window.location.pathname === this.base + route) ||
    options.replace;

  // find route data
  routeData = nitram.routes[route];
  if (typeof routeData === 'undefined') {
    routeData = _matchRoute(route);
    if (routeData.found !== false) {
      params = routeData.params;
      routeData = nitram.routes[routeData.found];
    } else {
      window.location = _route;
      return;
    }
  }

  // route data defautls
  if (typeof routeData.req === 'undefined') {
    routeData.req = true;
  }

  if (typeof routeData.title === 'undefined') {
    routeData.title = document.title;
  }

  // get controller
  controller = routeData.controller;

  // GET
  if (routeData.req) {
    jquery.ajaxSetup({
      cache: false
    });
    jquery.get(parser.href, function (data, textStatus, jqXHR) {
        callController(data, jqXHR.status, params, controller, route,
          routeData, options.replace, options.autoscroll);
      })
      .fail(function (jqXHR) {
        controller = FAIL_CONTROLLER_NAME;
        callController(null, jqXHR.status, params, controller, route,
          routeData, options.replace, options.autoscroll);
      });
  } else {
    callController(null, 200, params, controller, route, routeData,
      options.replace, options.autoscroll);
  }
};

/**
 * Register controller
 * @param {string} controller name
 * @param {Controller} controller
 */
nitram.register = function (name, controller) {
  _controllers[name] = controller ?
    controller : nitram.controllerFactory.make();
};

/**
 * Register fail controller
 * @param {Controller} controller
 */
nitram.registerFailController = function (controller) {
  _controllers[FAIL_CONTROLLER_NAME] = controller;
};

/**
 * Controller factory
 */
nitram.controllerFactory = {
  _defaults: {

    // ID or DOM element where the partial is going to be injected
    view: '#main',

    /**
     * Function that filters the partial from the ajax response. The returned
     * value is the one that nitram injects in the view.
     *
     * @example
     * partialFilter: function (data) { return data.html; }
     */
    partialFilter: _echo,

    /**
     * Entry lifecycle function that is called once the request is ready, and
     * just before the partial is injected into the view. Receives a callback
     * that need to be called when finished.
     *
     * @example
     * loaded: function (next) {
     *   jquery('#loading').hide();
     *   next();
     * }
     */
    loaded: _noopMW,

    /**
     * Entry lifecycle function that is called after the partial injection.
     * Does not have a callback, because this is the last step of the
     * controller entry lifecycle.
     */
    ready: _noop,

    /**
     * Exit lifecycle function that is called before a change of route: when
     * user clicks a nitram compiled ancho or with programtic routing
     * (i.e. nitram.route('/contact')). It gets not called on a state change
     * that wasn't generated by the previous two actions. Receives a callback
     * that need to be called when finished.
     */
    beforeRouteChange: _noopMW,

    /**
     * Exit lifecycle function that is called when there is a change of route.
     * Receives a callback that need to be called when finished.
     */
    afterRouteChange: _noopMW
  }
};

/**
 * Set defaults
 */
nitram.controllerFactory.setDefaults = function (options) {
  var key;
  for (key in options) {
    if (nitram.controllerFactory._defaults.hasOwnProperty(key)) {
      nitram.controllerFactory._defaults[key] = options[key];
    }
  }
};

/**
 * Call default lifecycle method
 */
nitram.controllerFactory.callDefault = function (name) {
  var args = Array.prototype.slice.call(arguments);
  args.shift();

  nitram.controllerFactory._defaults[name].apply(null, args);
};

/**
 * Make controller
 * @param {Object} [options={}]
 * @param {string} [options.view='#main']
 * @param {Function} [options.partialFilter=_echo]
 * @param {Function} [options.loaded=_noopMW]
 * @param {Function} [options.ready=_noop]
 * @param {Function} [options.beforeRouteChange=_noopMW]
 * @param {Function} [options.afterRouteChange=_noopMW]
 * @returns {Controller}
 */
nitram.controllerFactory.make = function (options) {
  var props = ['view', 'partialFilter', 'loaded', 'ready',
    'beforeRouteChange', 'afterRouteChange'
  ];
  var controller = {};
  var i, n;

  for (i = 0, n = props.length; i < n; i++) {
    if (options && typeof options[props[i]] !== 'undefined') {
      controller[props[i]] = options[props[i]];
    } else {
      controller[props[i]] = nitram.controllerFactory._defaults[props[i]];
    }
  }

  return controller;
};<|endoftext|>