<commit_before>;( function( $ ) {
    "use strict";

    var _scrollbarWidth,
        root = window,
        $root = $( window );

    /**
     * Gets the TextRectangle coordinates relative to a container element.
     *
     * Do not call if the container is a window (redundant) or a document. Both calls would fail.
     */
    function getRelativeRect ( rect, $container ) {
        var containerRect = $container[0].getBoundingClientRect(),
            containerBorders = $container.css( [ "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth" ] );

        // gBCR coordinates enclose padding, and leave out margin. That is perfect because
        //
        // - padding scrolls (ie,o it is part of the scrollable area, and gBCR puts it inside)
        // - margin doesn't scroll (ie, it pushes the scrollable area to another position, and gBCR records that)
        //
        // Borders, however, don't scroll, so they are not part of the scrollable area - but gBCR puts them inside.
        //
        // (See http://jsbin.com/pivata/10 for an extensive test of gBCR behaviour.)
        return {
            top: rect.top - containerRect.top + parseFloat( containerBorders.borderTopWidth ),
            bottom: rect.bottom - containerRect.top - parseFloat( containerBorders.borderBottomWidth ),
            left: rect.left - containerRect.left + parseFloat( containerBorders.borderLeftWidth ),
            right: rect.right - containerRect.left - parseFloat( containerBorders.borderRightWidth )
        };
    }

    /**
     * Establishes the container and returns it in a jQuery wrapper.
     *
     * Resolves and normalizes the input, which may be a document, HTMLElement, window, or selector string. Corrects
     * likely mistakes, such as passing in a document or an iframe, rather than the corresponding window.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} container
     * @returns {jQuery}
     */
    function wrapContainer ( container ) {
        var $container;

        $container = container instanceof $ ? container : container === root ? $root : $( container );

        if ( !$container.length ) throw new Error( 'Invalid container: empty jQuery object' );

        container = $container[0];

        if ( container.nodeType === 9 ) {
            // Document is passed in, transform to window
            $container = wrapContainer( $container[0].defaultView || $container[0].parentWindow );
        } else if ( container.nodeType === 1 && container.tagName.toLowerCase() === "iframe" ) {
            // IFrame element is passed in, transform to IFrame content window
            if ( ! ( container.contentWindow && container.contentWindow.document && container.contentWindow.document.childNodes && container.contentWindow.document.childNodes.length ) ) throw new Error( 'Invalid container: IFrame with inaccessible content' );
            $container = wrapContainer( container.contentWindow );
        }

        // Check if the container matches the requirements
        if ( !$.isWindow( $container[0] ) && $container.css( "overflow" ) === "visible" ) throw new Error( 'Invalid container: is set to overflow:visible. Containers must have the ability to obscure some of their content, otherwise the in-view test is pointless. Containers must be set to overflow:scroll/auto/hide, or be a window' );

        return $container;
    }

    /**
     * Checks if the element is a child of the container, and throws an error otherwise.
     *
     * For performance reasons, this check should *not* be run on every element in a set.
     *
     * @param {jQuery} $elem
     * @param {jQuery} $container
     */
    function checkHierarchy ( $elem, $container ) {

        if ( ! $.isWindow( $container[0] ) ) {
            if ( ! $elem.parents().has( $container ) ) throw new Error( "Invalid container: is a descendant of the element" );
        }

    }

    /**
     * Spots likely option mistakes and throws appropriate errors.
     *
     * @param {Object} opts
     */
    function checkOptions ( opts ) {

        if ( opts.direction && !( opts.direction === 'vertical' || opts.direction === 'horizontal' || opts.direction === 'both' ) ) {
            throw new Error( 'Invalid option value: direction = "' + opts.direction + '"' );
        }

    }

    /**
     * Prepares the configuration for a single element query. Returns the config object which is to be consumed by
     * _isInView().
     *
     * @param {jQuery}                                    $elem                    single element, or set of elements
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {Object}
     */
    function _prepareConfig ( $elem, container, opts ) {

        var $container, direction,
            config = {};

        opts || ( opts = {} );

        container || ( container = $elem.ownerWindow() );
        config.$container = $container = wrapContainer( container );
        config.container = container = $container[0];

        checkOptions( opts );

        direction = opts.direction || 'both';
        config.useVertical = direction === 'both' || direction === 'vertical';
        config.useHorizontal = direction === 'both' || direction === 'horizontal';

        config.anyPart = opts.anyPart;
        config.zeroSize = opts.zeroSize;
        config.containerIsWindow = $.isWindow( container );

        return config;
    }

    /**
     * Returns if an element is in view, with regard to a given configuration.
     *
     * The configuration is built with _prepareConfig().
     *
     * @param {HTMLElement}        elem
     * @param {Object}             config
     * @param {HTMLElement|Window} config.container
     * @param {jQuery}             config.$container
     * @param {boolean}            config.containerIsWindow
     * @param {boolean}            config.useHorizontal
     * @param {boolean}            config.useVertical
     * @param {boolean}            config.anyPart            todo rename?
     * @param {boolean}            config.zeroSize           todo rename?
     *
     * @returns {boolean}
     */
    function _isInView ( elem, config ) {

        var viewportWidth, viewportHeight, rect,
            container = config.container,
            $container = config.$container,
            isInView = true;

        if ( elem === container ) throw new Error( "Invalid container: is the same as the element" );

        // If non-zero size is required, we check if an element consumes space in the document. And we out immediately
        // if the element doesn't meet the requirement.
        //
        // The test employed for this works in the vast majority of cases, but there is a limitation. We use offsetWidth
        // and offsetHeight, which considers the content (incl. borders) but ignores margins. Zero-size content with a
        // margin might actually consume space sometimes, but it won't be detected (see http://jsbin.com/tiwabo/3).
        //
        // That said, the definition of visibility and the actual test are the same as in jQuery :visible.
        if ( !( config.zeroSize || ( elem.offsetWidth > 0 && elem.offsetHeight > 0 ) ) ) return false;

        if ( config.useHorizontal ) viewportWidth = $container.width();
        if ( config.useVertical ) viewportHeight = $container.height();

        // We can safely use getBoundingClientRect without a fallback. Its core properties (top, left, bottom, right)
        // are supported on the desktop for ages (IE5+). On mobile, too: supported from Blackberry 6+ (2010), iOS 4
        // (2010, iPhone 3GS+), according to the jQuery source comment in $.fn.offset.
        //
        // In oldIE (up to IE8), the coordinates were 2px off in each dimension because the "viewport" began at (2,2) of
        // the window. Can be feature-tested by creating an absolutely positioned div at (0,0) and reading the rect
        // coordinates. Won't be fixed here because the quirk is too minor to justify the overhead, just for oldIE.
        //
        // (See http://stackoverflow.com/a/10231202/508355 and Zakas, Professional Javascript (2012), p. 406)

        rect = elem.getBoundingClientRect();
        if ( ! config.containerIsWindow ) rect = getRelativeRect( rect, $container );

        if ( config.anyPart ) {
            if ( config.useVertical ) isInView = rect.top < viewportHeight && rect.bottom > 0;
            if ( config.useHorizontal ) isInView = isInView && rect.left < viewportWidth && rect.right > 0;
        } else {
            if ( config.useVertical ) isInView = rect.top >= 0 && rect.top < viewportHeight && rect.bottom > 0 && rect.bottom <= viewportHeight;
            if ( config.useHorizontal ) isInView = isInView && rect.left >= 0 && rect.left < viewportWidth && rect.right > 0 && rect.right <= viewportWidth;
        }

        return isInView;

    }

    /**
     * Returns the size (width) of the scrollbar, in pixels, as a number without unit.
     *
     * If the browser doesn't provide permanent scrollbars, and instead shows them as a temporary overlay while actually
     * scrolling the page, scrollbar size is reported as 0. That is the default behaviour in mobile browsers, and in
     * current versions of OS X.
     *
     * Adapted from Ben Alman's scrollbarWidth plugin. See
     * - http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth
     * - http://jsbin.com/zeliy/1
     *
     * @returns {number}
     */
        // todo use it!
    $.scrollbarWidth = function () {
        var $parent, $child;

        if ( _scrollbarWidth === undefined ) {

            $child = $( "<div/>" ).css( { margin: 0, padding: 0, borderStyle: "none" } );
            $parent = $( "<div/>" )
                .css( {
                    width: "100px", height: "100px", overflow: "auto",
                    position: "absolute", top: "-500px", left: "-500px",
                    margin: 0, padding: 0, borderStyle: "none"
                } )
                .append( $child )
                .appendTo( "body" );

            _scrollbarWidth = $child.innerWidth() - $child.height( 150 ).innerWidth();
            $parent.remove();

        }

        return _scrollbarWidth;
    };

    /**
     * Returns the window containing the the first element in the set of matched elements.
     *
     * If the element is a window, `ownerWindow` returns the window itself. If there aren't any matched elements,
     * `ownerWindow` returns undefined.
     *
     * If the element is _inside_ a frame or iframe, `ownerWindow`  returns the window representing the iframe . This is
     * subject to cross-domain security restrictions. Trying to access the content of the (i)frame will throw an error
     * if it is not from the same domain, or fetched with a different protocol.
     *
     * However, if the element _is_ the frame or iframe, `ownerWindow` returns the window containing the (i)frame.
     *
     * @returns {Window|undefined}
     */
    $.fn.ownerWindow = function () {
        var elem = this[0],
            ownerDocument = elem && ( elem.nodeType === 9 ? elem : elem.ownerDocument );

        return ownerDocument && ( ownerDocument.defaultView || ownerDocument.parentWindow ) || $.isWindow( elem ) && elem || undefined;
    };

    /**
     * Acts as a filter and returns the elements in the set which are in view inside the window, or inside another
     * container.
     *
     * The container can be a window, iframe, scrollable element (overflow:scroll or overflow:auto), an element with
     * overflow:hidden, or a selector for any of these. Defaults to the window containing the elements.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {jQuery}
     */
    $.fn.inView = function ( container, opts ) {

        var config,
            $elems = this,
            inView = [];

        if ( !$elems.length ) return $();

        config = _prepareConfig( $elems, container, opts );

        // Check if the elements are children of the container. For performance reasons, only the first element is
        // examined.
        if ( ! config.containerIsWindow ) checkHierarchy( $elems[0], config.$container );

        $elems.each( function () {
            if ( _isInView( this, config ) ) inView.push( this );
        } );

        return $( inView );
    };

    /**
     * Acts as a filter and returns the elements in the set which are in view inside the window.
     *
     * Shorthand for $elem.inView( $elem.ownerWindow(), opts );
     *
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {jQuery}
     */
    $.fn.inViewport = function ( opts ) {
        return this.inView( this.ownerWindow(), opts );
    };

    /**
     * Returns true if the element is in view inside the window, or inside another container. Examines the first element
     * in a set.
     *
     * The container can be a window, iframe, scrollable element (overflow:scroll or overflow:auto), an element with
     * overflow:hidden, or a selector for any of these. Defaults to the window containing the elements.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {boolean}
     */
    $.fn.isInView = function ( container, opts ) {

        var config,
            $elem = this,
            elem = this[0];

        if ( !$elem.length ) return false;

        config = _prepareConfig( $elem, container, opts );
        if ( ! config.containerIsWindow ) checkHierarchy( $elem, config.$container ); // todo when dealing with sets in inView, do this only for the first element (perf!)

        return _isInView( elem, config );

    };

    /**
     * Returns true if the element is in view inside the window. Examines the first element in a set.
     *
     * Shorthand for $elem.isInView( $elem.ownerWindow(), opts );
     *
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {boolean}
     */
    $.fn.isInViewport = function ( opts ) {
        return this.isInView( this.ownerWindow(), opts );
    };

}( jQuery || $ ));  // todo best solution?<commit_msg>Fixed hierarchy check<commit_after>;( function( $ ) {
    "use strict";

    var _scrollbarWidth,
        root = window,
        $root = $( window );

    /**
     * Gets the TextRectangle coordinates relative to a container element.
     *
     * Do not call if the container is a window (redundant) or a document. Both calls would fail.
     */
    function getRelativeRect ( rect, $container ) {
        var containerRect = $container[0].getBoundingClientRect(),
            containerBorders = $container.css( [ "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth" ] );

        // gBCR coordinates enclose padding, and leave out margin. That is perfect because
        //
        // - padding scrolls (ie,o it is part of the scrollable area, and gBCR puts it inside)
        // - margin doesn't scroll (ie, it pushes the scrollable area to another position, and gBCR records that)
        //
        // Borders, however, don't scroll, so they are not part of the scrollable area - but gBCR puts them inside.
        //
        // (See http://jsbin.com/pivata/10 for an extensive test of gBCR behaviour.)
        return {
            top: rect.top - containerRect.top + parseFloat( containerBorders.borderTopWidth ),
            bottom: rect.bottom - containerRect.top - parseFloat( containerBorders.borderBottomWidth ),
            left: rect.left - containerRect.left + parseFloat( containerBorders.borderLeftWidth ),
            right: rect.right - containerRect.left - parseFloat( containerBorders.borderRightWidth )
        };
    }

    /**
     * Establishes the container and returns it in a jQuery wrapper.
     *
     * Resolves and normalizes the input, which may be a document, HTMLElement, window, or selector string. Corrects
     * likely mistakes, such as passing in a document or an iframe, rather than the corresponding window.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} container
     * @returns {jQuery}
     */
    function wrapContainer ( container ) {
        var $container;

        $container = container instanceof $ ? container : container === root ? $root : $( container );

        if ( !$container.length ) throw new Error( 'Invalid container: empty jQuery object' );

        container = $container[0];

        if ( container.nodeType === 9 ) {
            // Document is passed in, transform to window
            $container = wrapContainer( $container[0].defaultView || $container[0].parentWindow );
        } else if ( container.nodeType === 1 && container.tagName.toLowerCase() === "iframe" ) {
            // IFrame element is passed in, transform to IFrame content window
            if ( ! ( container.contentWindow && container.contentWindow.document && container.contentWindow.document.childNodes && container.contentWindow.document.childNodes.length ) ) throw new Error( 'Invalid container: IFrame with inaccessible content' );
            $container = wrapContainer( container.contentWindow );
        }

        // Check if the container matches the requirements
        if ( !$.isWindow( $container[0] ) && $container.css( "overflow" ) === "visible" ) throw new Error( 'Invalid container: is set to overflow:visible. Containers must have the ability to obscure some of their content, otherwise the in-view test is pointless. Containers must be set to overflow:scroll/auto/hide, or be a window' );

        return $container;
    }

    /**
     * Checks if the element is a child of the container, and throws an error otherwise.
     *
     * For performance reasons, this check should *not* be run on every element in a set.
     *
     * @param {HTMLElement}                 elem
     * @param {Window|Document|HTMLElement} container
     */
    function checkHierarchy ( elem, container ) {

        var elemIsContained;

        // We need a DOM element for this check, so we use the documentElement as a proxy if the container is a window
        // or document.
        if ( $.isWindow( container ) ) {
            elemIsContained = $.contains( container.document.documentElement, elem );
        } else if ( container.nodeType === 9 ) {
            elemIsContained = $.contains( container.documentElement, elem );
        } else {
            elemIsContained = $.contains( container, elem );
        }

        if ( !elemIsContained ) throw new Error( "Invalid container: is a descendant of the element" );

    }

    /**
     * Spots likely option mistakes and throws appropriate errors.
     *
     * @param {Object} opts
     */
    function checkOptions ( opts ) {

        if ( opts.direction && !( opts.direction === 'vertical' || opts.direction === 'horizontal' || opts.direction === 'both' ) ) {
            throw new Error( 'Invalid option value: direction = "' + opts.direction + '"' );
        }

    }

    /**
     * Prepares the configuration for a single element query. Returns the config object which is to be consumed by
     * _isInView().
     *
     * @param {jQuery}                                    $elem                    single element, or set of elements
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {Object}
     */
    function _prepareConfig ( $elem, container, opts ) {

        var $container, direction,
            config = {};

        opts || ( opts = {} );

        container || ( container = $elem.ownerWindow() );
        config.$container = $container = wrapContainer( container );
        config.container = container = $container[0];

        checkOptions( opts );

        direction = opts.direction || 'both';
        config.useVertical = direction === 'both' || direction === 'vertical';
        config.useHorizontal = direction === 'both' || direction === 'horizontal';

        config.anyPart = opts.anyPart;
        config.zeroSize = opts.zeroSize;
        config.containerIsWindow = $.isWindow( container );

        return config;
    }

    /**
     * Returns if an element is in view, with regard to a given configuration.
     *
     * The configuration is built with _prepareConfig().
     *
     * @param {HTMLElement}        elem
     * @param {Object}             config
     * @param {HTMLElement|Window} config.container
     * @param {jQuery}             config.$container
     * @param {boolean}            config.containerIsWindow
     * @param {boolean}            config.useHorizontal
     * @param {boolean}            config.useVertical
     * @param {boolean}            config.anyPart            todo rename?
     * @param {boolean}            config.zeroSize           todo rename?
     *
     * @returns {boolean}
     */
    function _isInView ( elem, config ) {

        var viewportWidth, viewportHeight, rect,
            container = config.container,
            $container = config.$container,
            isInView = true;

        if ( elem === container ) throw new Error( "Invalid container: is the same as the element" );

        // If non-zero size is required, we check if an element consumes space in the document. And we out immediately
        // if the element doesn't meet the requirement.
        //
        // The test employed for this works in the vast majority of cases, but there is a limitation. We use offsetWidth
        // and offsetHeight, which considers the content (incl. borders) but ignores margins. Zero-size content with a
        // margin might actually consume space sometimes, but it won't be detected (see http://jsbin.com/tiwabo/3).
        //
        // That said, the definition of visibility and the actual test are the same as in jQuery :visible.
        if ( !( config.zeroSize || ( elem.offsetWidth > 0 && elem.offsetHeight > 0 ) ) ) return false;

        if ( config.useHorizontal ) viewportWidth = $container.width();
        if ( config.useVertical ) viewportHeight = $container.height();

        // We can safely use getBoundingClientRect without a fallback. Its core properties (top, left, bottom, right)
        // are supported on the desktop for ages (IE5+). On mobile, too: supported from Blackberry 6+ (2010), iOS 4
        // (2010, iPhone 3GS+), according to the jQuery source comment in $.fn.offset.
        //
        // In oldIE (up to IE8), the coordinates were 2px off in each dimension because the "viewport" began at (2,2) of
        // the window. Can be feature-tested by creating an absolutely positioned div at (0,0) and reading the rect
        // coordinates. Won't be fixed here because the quirk is too minor to justify the overhead, just for oldIE.
        //
        // (See http://stackoverflow.com/a/10231202/508355 and Zakas, Professional Javascript (2012), p. 406)

        rect = elem.getBoundingClientRect();
        if ( ! config.containerIsWindow ) rect = getRelativeRect( rect, $container );

        if ( config.anyPart ) {
            if ( config.useVertical ) isInView = rect.top < viewportHeight && rect.bottom > 0;
            if ( config.useHorizontal ) isInView = isInView && rect.left < viewportWidth && rect.right > 0;
        } else {
            if ( config.useVertical ) isInView = rect.top >= 0 && rect.top < viewportHeight && rect.bottom > 0 && rect.bottom <= viewportHeight;
            if ( config.useHorizontal ) isInView = isInView && rect.left >= 0 && rect.left < viewportWidth && rect.right > 0 && rect.right <= viewportWidth;
        }

        return isInView;

    }

    /**
     * Returns the size (width) of the scrollbar, in pixels, as a number without unit.
     *
     * If the browser doesn't provide permanent scrollbars, and instead shows them as a temporary overlay while actually
     * scrolling the page, scrollbar size is reported as 0. That is the default behaviour in mobile browsers, and in
     * current versions of OS X.
     *
     * Adapted from Ben Alman's scrollbarWidth plugin. See
     * - http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth
     * - http://jsbin.com/zeliy/1
     *
     * @returns {number}
     */
        // todo use it!
    $.scrollbarWidth = function () {
        var $parent, $child;

        if ( _scrollbarWidth === undefined ) {

            $child = $( "<div/>" ).css( { margin: 0, padding: 0, borderStyle: "none" } );
            $parent = $( "<div/>" )
                .css( {
                    width: "100px", height: "100px", overflow: "auto",
                    position: "absolute", top: "-500px", left: "-500px",
                    margin: 0, padding: 0, borderStyle: "none"
                } )
                .append( $child )
                .appendTo( "body" );

            _scrollbarWidth = $child.innerWidth() - $child.height( 150 ).innerWidth();
            $parent.remove();

        }

        return _scrollbarWidth;
    };

    /**
     * Returns the window containing the the first element in the set of matched elements.
     *
     * If the element is a window, `ownerWindow` returns the window itself. If there aren't any matched elements,
     * `ownerWindow` returns undefined.
     *
     * If the element is _inside_ a frame or iframe, `ownerWindow`  returns the window representing the iframe . This is
     * subject to cross-domain security restrictions. Trying to access the content of the (i)frame will throw an error
     * if it is not from the same domain, or fetched with a different protocol.
     *
     * However, if the element _is_ the frame or iframe, `ownerWindow` returns the window containing the (i)frame.
     *
     * @returns {Window|undefined}
     */
    $.fn.ownerWindow = function () {
        var elem = this[0],
            ownerDocument = elem && ( elem.nodeType === 9 ? elem : elem.ownerDocument );

        return ownerDocument && ( ownerDocument.defaultView || ownerDocument.parentWindow ) || $.isWindow( elem ) && elem || undefined;
    };

    /**
     * Acts as a filter and returns the elements in the set which are in view inside the window, or inside another
     * container.
     *
     * The container can be a window, iframe, scrollable element (overflow:scroll or overflow:auto), an element with
     * overflow:hidden, or a selector for any of these. Defaults to the window containing the elements.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {jQuery}
     */
    $.fn.inView = function ( container, opts ) {

        var config,
            $elems = this,
            inView = [];

        if ( !$elems.length ) return $();

        config = _prepareConfig( $elems, container, opts );

        // Check if the elements are children of the container. For performance reasons, only the first element is
        // examined.
        if ( ! config.containerIsWindow ) checkHierarchy( $elems[0], config.container );

        $elems.each( function () {
            if ( _isInView( this, config ) ) inView.push( this );
        } );

        return $( inView );
    };

    /**
     * Acts as a filter and returns the elements in the set which are in view inside the window.
     *
     * Shorthand for $elem.inView( $elem.ownerWindow(), opts );
     *
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {jQuery}
     */
    $.fn.inViewport = function ( opts ) {
        return this.inView( this.ownerWindow(), opts );
    };

    /**
     * Returns true if the element is in view inside the window, or inside another container. Examines the first element
     * in a set.
     *
     * The container can be a window, iframe, scrollable element (overflow:scroll or overflow:auto), an element with
     * overflow:hidden, or a selector for any of these. Defaults to the window containing the elements.
     *
     * @param {Window|Document|HTMLElement|jQuery|string} [container=window]
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {boolean}
     */
    $.fn.isInView = function ( container, opts ) {

        var config,
            $elem = this,
            elem = this[0];

        if ( !$elem.length ) return false;

        config = _prepareConfig( $elem, container, opts );
        if ( ! config.containerIsWindow ) checkHierarchy( elem, config.container );

        return _isInView( elem, config );

    };

    /**
     * Returns true if the element is in view inside the window. Examines the first element in a set.
     *
     * Shorthand for $elem.isInView( $elem.ownerWindow(), opts );
     *
     * @param {Object}                                    [opts]
     * @param {boolean}                                   [opts.anyPart=false]     // todo or inFull=true
     * @param {boolean}                                   [opts.zeroSize=false]    // todo or collapsed=false, or hasDimensions=true, requireDimensions, mustTakeSpace, visibleImpact, displayedOnly=true
     * @param {string}                                    [opts.direction="both"]
     *
     * @returns {boolean}
     */
    $.fn.isInViewport = function ( opts ) {
        return this.isInView( this.ownerWindow(), opts );
    };

}( jQuery || $ ));  // todo best solution?<|endoftext|>


<commit_before>/* ========================================================================
 * ZUI: chosen.icons.js
 * http://zui.sexy
 * ========================================================================
 * Copyright (c) 2014 cnezsoft.com; Licensed MIT
 * ======================================================================== */

+ function($) {
    'use strict';

    var ChosenIcons = function(element, options) {
        this.$ = $(element);
        this.options = this.getOptions(options);
        this.lang = ChosenIcons.LANGS[this.options.lang];
        this.id = 'chosen-icons-' + parseInt(Math.random() * 10000000000 + 1);

        this.init();
    };

    ChosenIcons.DEFAULTS = {
        canEmpty: true,
        lang: 'zh-cn',
        commonIcons: ['heart', 'user', 'group', 'list-ul', 'th', 'th-large', 'star', 'star-empty', 'search', 'envelope', 'dashboard', 'sitemap', 'umbrella', 'lightbulb', 'envelope-alt', 'cog', 'ok', 'remove', 'home', 'time', 'flag', 'flag-alt', 'flag-checkered', 'qrcode', 'tag', 'tags', 'book', 'bookmark', 'bookmark-empty', 'print', 'camera', 'picture', 'globe', 'map-marker', 'edit', 'edit-sign', 'play', 'stop', 'plus-sign', 'minus-sign', 'remove-sign', 'ok-sign', 'check-sign', 'question-sign', 'info-sign', 'exclamation-sign', 'plus', 'plus-sign', 'minus', 'minus-sign', 'asterisk', 'calendar', 'calendar-empty', 'comment', 'comment-alt', 'comments', 'comments-alt', 'folder-close', 'folder-open', 'folder-close-alt', 'folder-open-alt', 'thumbs-up', 'thumbs-down', 'pushpin', 'building', 'phone', 'rss', 'rss-sign', 'bullhorn', 'bell', 'bell-alt', 'certificate', 'wrench', 'tasks', 'cloud', 'beaker', 'magic', 'smile', 'frown', 'meh', 'code', 'location-arrow'],
        webIcons: ['share', 'pencil', 'trash', 'file-alt', 'file', 'file-text', 'download-alt', 'upload-alt', 'inbox', 'repeat', 'refresh', 'lock', 'check', 'check-empty', 'eye-open', 'eye-close', 'key', 'signin', 'signout', 'external-link', 'external-link-sign', 'link', 'reorder', 'quote-left', 'quote-right', 'spinner', 'reply', 'question', 'info', 'archive', 'collapse', 'collapse-top'],
        editorIcons: ['table', 'copy', 'save', 'list-ol', 'paste', 'keyboard', 'paper-clip', 'crop', 'unlink', 'sort-by-alphabet', 'sort-by-alphabet-alt', 'sort-by-attributes', 'sort-by-attributes-alt', 'sort-by-order', 'sort-by-order-alt'],
        directionalIcons: ['chevron-left', 'chevron-right', 'chevron-down', 'chevron-up', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'hand-right', 'hand-left', 'hand-up', 'hand-down', 'circle-arrow-left', 'circle-arrow-right', 'circle-arrow-up', 'circle-arrow-down', 'double-angle-left', 'double-angle-right', 'double-angle-down', 'double-angle-up', 'angle-left', 'angle-right', 'angle-down', 'angle-up', 'long-arrow-left', 'long-arrow-right', 'long-arrow-down', 'long-arrow-up', 'caret-left', 'caret-right', 'caret-down', 'caret-up'],
        otherIcons: ['desktop', 'laptop', 'tablet', 'mobile', 'building', 'firefox', 'ie', 'opera', 'qq', 'lemon', 'sign-blank', 'circle', 'circle-blank', 'terminal', 'html5', 'android', 'apple', 'windows', 'weibo', 'renren', 'bug', 'moon', 'sun']
    };

    ChosenIcons.LANGS = {};
    ChosenIcons.LANGS['zh-cn'] = {
        emptyIcon: '[没有图标]',
        commonIcons: '常用图标',
        webIcons: 'Web 图标',
        editorIcons: '编辑器图标',
        directionalIcons: '箭头总汇',
        otherIcons: '其他图标',
    };
    ChosenIcons.LANGS['en'] = {
        emptyIcon: '[No Icon]',
        commonIcons: 'Common Icons',
        webIcons: 'Web Icons',
        editorIcons: 'Editor Icons',
        directionalIcons: 'Directional Icons',
        otherIcons: 'Other Icons'
    };
    ChosenIcons.LANGS['zh-tw'] = {
        emptyIcon: '[沒有圖標]',
        commonIcons: '常用圖標',
        webIcons: 'Web 圖標',
        editorIcons: '編輯器圖標',
        directionalIcons: '箭頭總匯',
        otherIcons: '其他圖標'
    };

    ChosenIcons.prototype.getOptions = function(options) {
        options = $.extend({}, ChosenIcons.DEFAULTS, this.$.data(), options);
        return options;
    };

    ChosenIcons.prototype.init = function() {
        var $this = this.$.addClass('chosen-icons').addClass(this.id).removeClass('form-control');

        $this.empty();

        if(this.options.canEmpty) {
            $this.append(this.getOptionHtml());
        }

        var lang = this.lang;

        $this.append(this.getgroupHtml('commonIcons'));
        $this.append(this.getgroupHtml('webIcons'));
        $this.append(this.getgroupHtml('editorIcons'));
        $this.append(this.getgroupHtml('directionalIcons'));
        $this.append(this.getgroupHtml('otherIcons'));

        $this.chosen({
            placeholder_text: ' ',
            disable_search: true,
            width: '100%',
            inherit_select_classes: true
        });

        var chosenSelector = '.chosen-container.' + this.id;

        $this.on('chosen:showing_dropdown', function() {
            $(chosenSelector + ' .chosen-results .group-option').each(function() {
                var $this = $(this).addClass('icon');
                var text = $(this).text();
                $this.html('');
            });
        }).change(function() {
            var span = $(chosenSelector + ' .chosen-single > span');
            var text = $(this).val();

            if(text && text.length > 0)
                span.html('   ' + text.substr(5).replace(/-/g, ' ') + '');
            else span.html('' + lang.emptyIcon + '')

        });

        var val = $this.data('value');
        if(val) {
            $this.val(val).change();
        }
    }

    ChosenIcons.prototype.getgroupHtml = function(name) {
        var icons = this.options[name],
            iconsHtml = [],
            that = this;

        $.each(icons, function(i, icon) {
            iconsHtml.push(that.getOptionHtml(icon));
        });

        return '<optgroup label="' + this.lang[name] + '">' + iconsHtml.join('') + '</optgroup>';
    }

    ChosenIcons.prototype.getOptionHtml = function(value) {
        var name = value;
        if(value && value.length > 0) {
            value = 'icon-' + value;
        } else {
            value = '';
            name = this.lang.emptyIcon;
        }
        return '<option value="' + value + '">' + name + '</option>';
    }

    $.fn.chosenIcons = function(option) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('zui.chosenIcons');
            var options = typeof option == 'object' && option;

            if(!data) $this.data('zui.chosenIcons', (data = new ChosenIcons(this, options)));

            if(typeof option == 'string') data[option]();
        })
    };

    $.fn.chosenIcons.Constructor = ChosenIcons;
}(jQuery);

<commit_msg> * refactor chosen icons component.<commit_after>/* ========================================================================
 * ZUI: chosen.icons.js
 * http://zui.sexy
 * ========================================================================
 * Copyright (c) 2014 cnezsoft.com; Licensed MIT
 * ======================================================================== */

+ function($) {
    'use strict';

    var ChosenIcons = function(element, options) {
        this.$ = $(element);
        this.options = this.getOptions(options);
        this.lang = ChosenIcons.LANGS[this.options.lang];
        this.id = 'chosen-icons-' + parseInt(Math.random() * 10000000000 + 1);

        this.init();
    };

    ChosenIcons.DEFAULTS = {
        optional: true,
        lang: 'zh-cn',
        icons: {
            common: ['heart', 'user', 'group', 'list-ul', 'th', 'th-large', 'star', 'star-empty', 'search', 'envelope', 'dashboard', 'sitemap', 'umbrella', 'lightbulb', 'envelope-alt', 'cog', 'ok', 'remove', 'home', 'time', 'flag', 'flag-alt', 'flag-checkered', 'qrcode', 'tag', 'tags', 'book', 'bookmark', 'bookmark-empty', 'print', 'camera', 'picture', 'globe', 'map-marker', 'edit', 'edit-sign', 'play', 'stop', 'plus-sign', 'minus-sign', 'remove-sign', 'ok-sign', 'check-sign', 'question-sign', 'info-sign', 'exclamation-sign', 'plus', 'plus-sign', 'minus', 'minus-sign', 'asterisk', 'calendar', 'calendar-empty', 'comment', 'comment-alt', 'comments', 'comments-alt', 'folder-close', 'folder-open', 'folder-close-alt', 'folder-open-alt', 'thumbs-up', 'thumbs-down', 'pushpin', 'building', 'phone', 'rss', 'rss-sign', 'bullhorn', 'bell', 'bell-alt', 'certificate', 'wrench', 'tasks', 'cloud', 'beaker', 'magic', 'smile', 'frown', 'meh', 'code', 'location-arrow'],
            web: ['share', 'pencil', 'trash', 'file-alt', 'file', 'file-text', 'download-alt', 'upload-alt', 'inbox', 'repeat', 'refresh', 'lock', 'check', 'check-empty', 'eye-open', 'eye-close', 'key', 'signin', 'signout', 'external-link', 'external-link-sign', 'link', 'reorder', 'quote-left', 'quote-right', 'spinner', 'reply', 'question', 'info', 'archive', 'collapse', 'collapse-top'],
            editor: ['table', 'copy', 'save', 'list-ol', 'paste', 'keyboard', 'paper-clip', 'crop', 'unlink', 'sort-by-alphabet', 'sort-by-alphabet-alt', 'sort-by-attributes', 'sort-by-attributes-alt', 'sort-by-order', 'sort-by-order-alt'],
            directional: ['chevron-left', 'chevron-right', 'chevron-down', 'chevron-up', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'hand-right', 'hand-left', 'hand-up', 'hand-down', 'circle-arrow-left', 'circle-arrow-right', 'circle-arrow-up', 'circle-arrow-down', 'double-angle-left', 'double-angle-right', 'double-angle-down', 'double-angle-up', 'angle-left', 'angle-right', 'angle-down', 'angle-up', 'long-arrow-left', 'long-arrow-right', 'long-arrow-down', 'long-arrow-up', 'caret-left', 'caret-right', 'caret-down', 'caret-up'],
            other: ['desktop', 'laptop', 'tablet', 'mobile', 'building', 'firefox', 'ie', 'opera', 'qq', 'lemon', 'sign-blank', 'circle', 'circle-blank', 'terminal', 'html5', 'android', 'apple', 'windows', 'weibo', 'renren', 'bug', 'moon', 'sun']
        }
    };

    ChosenIcons.LANGS = {};
    ChosenIcons.LANGS['zh-cn'] = {
        emptyIcon: '[没有图标]',
        commonIcons: '常用图标',
        webIcons: 'Web 图标',
        editorIcons: '编辑器图标',
        directionalIcons: '箭头总汇',
        otherIcons: '其他图标',
    };
    ChosenIcons.LANGS['en'] = {
        emptyIcon: '[No Icon]',
        commonIcons: 'Common Icons',
        webIcons: 'Web Icons',
        editorIcons: 'Editor Icons',
        directionalIcons: 'Directional Icons',
        otherIcons: 'Other Icons'
    };
    ChosenIcons.LANGS['zh-tw'] = {
        emptyIcon: '[沒有圖標]',
        commonIcons: '常用圖標',
        webIcons: 'Web 圖標',
        editorIcons: '編輯器圖標',
        directionalIcons: '箭頭總匯',
        otherIcons: '其他圖標'
    };

    ChosenIcons.prototype.getOptions = function(options) {
        options = $.extend(true, {
            placeholder_text: ' ',
            disable_search: true,
            width: '100%',
            inherit_select_classes: true
        }, ChosenIcons.DEFAULTS, this.$.data(), options);
        return options;
    };

    ChosenIcons.prototype.init = function() {
        var that = this;
        var $this = this.$.addClass('chosen-icons').addClass(this.id).removeClass('form-control');

        $this.empty();

        if(this.options.optional) {
            $this.append(this.getOptionHtml());
        }

        var lang = this.lang;

        var iconsHtml = [];
        $.each(this.options.icons, function(name, icons) {
            iconsHtml.push(that.getgroupHtml(name, icons));
        });

        $this.append(iconsHtml.join(''));

        $this.chosen(this.options);

        var chosenSelector = '.chosen-container.' + this.id;

        $this.on('chosen:showing_dropdown', function() {
            $(chosenSelector + ' .chosen-results .group-option').each(function() {
                var $this = $(this).addClass('icon');
                var text = $(this).text();
                $this.html('');
            });
        }).change(function() {
            var span = $(chosenSelector + ' .chosen-single > span');
            var text = $(this).val();

            if(text && text.length > 0) {
                span.html('   ' + text.substr(5).replace(/-/g, ' ') + '');
            }
            else {
                span.html('' + lang.emptyIcon + '');
            }
        });

        var val = $this.data('value');
        if(val) {
            $this.val(val).change();
        }
    }

    ChosenIcons.prototype.getgroupHtml = function(name, icons) {
        icons = icons || this.options.icons[name]
        var iconsHtml = [],
            that = this;

        $.each(icons, function(i, icon) {
            iconsHtml.push(that.getOptionHtml(icon));
        });

        return '<optgroup label="' + this.lang[name + 'Icons'] + '">' + iconsHtml.join('') + '</optgroup>';
    }

    ChosenIcons.prototype.getOptionHtml = function(value) {
        var name = value;
        if(value && value.length > 0) {
            value = 'icon-' + value;
        } else {
            value = '';
            name = this.lang.emptyIcon;
        }
        return '<option value="' + value + '">' + name + '</option>';
    }

    $.fn.chosenIcons = function(option) {
        return this.each(function() {
            var $this = $(this);
            var data = $this.data('zui.chosenIcons');
            var options = typeof option == 'object' && option;

            if(!data) $this.data('zui.chosenIcons', (data = new ChosenIcons(this, options)));

            if(typeof option == 'string') data[option]();
        })
    };

    $.fn.chosenIcons.Constructor = ChosenIcons;
}(jQuery);

<|endoftext|>


<commit_before>var FloorManager = function() {
    this.floorObjects = null;
    this.spriteGroup = null;
    this.timer1 = null;
    this.floorsTraveled = 0;
    this.doorState = '';

    this.initialize = function() {
        this.floorObjects = new Array(__c_.numFloors);
        for (i = 0; i < __c_.numFloors; ++i) {
            this.floorObjects[i] = {upCalled:false, downCalled:false, upCalledSprite:null, downCalledSprite:null, elevDoorGraphics:null, elevAtSprite:null };
        }

        this.spriteGroup = game.add.group();
        this.spriteGroup.z = 2;

        // add all event handlers
        this.addListener('elev-called', this.elevCalled);
        this.addListener('elev-arrived', this.elevArrived);
        this.addListener('elev-boarded', this.elevBoarded);

        //cycle through each floor
        //initialize the display of the up/down call buttons
        //instantly draw a closed door (not closing door)
        _.each(this.floorObjects, function(fo,i) {
            floorManager.setCallStatus("up", false, i);
            floorManager.setCallStatus("down", false, i);
            floorManager._paintClosedElevator(fo, i);
        });

        // start a timer every 20 msecs which does actions as required on all persons
        this.intervalID = window.setInterval(this._processTimer, 20);
    };

    this.shutdown = function() {
        if (this.intervalID != 'undefined' && this.intervalID != null) {
            window.clearInterval(this.intervalID);
            this.intervalID = null;
        }
    };

    this._processTimer = function() {
        t = floorManager.timer1;
        if (t !== null && t.type == 'countdown') { // a countdown timer is fired after n intervals
            if (t.countdown === 0) { // n intervals done
                t.callback(floorManager, t.ctx); // invoke the callback
                if (t.id == floorManager.timer1.id) floorManager.timer1 = null; // null at end of countdown, but only if the timer hasnt been substituted during the call
            } else t.countdown--; // one more interval done
        } else if (t !== null && t.type == 'loop') {
            ret = t.callback(floorManager, t.ctx); // invoke the callback and check the return value
            if (!ret && t.id == floorManager.timer1.id) floorManager.timer1 = null; // if the callback had returned true, it means the callbacks should continue, else false means stop further callbacks (unless the callback changed the timer itself)
        }
    };

    this.getProperty = function(floorNumber, key) {
        return this.floorObjects[floorNumber][key];
    };

    this._paintClosedElevator = function(floorObject, floorNumber) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();
        else floorObject.elevDoorGraphics.clear();

        floorObject.elevDoorGraphics.beginFill(0xfff5ee);
        floorObject.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
        // a perfect closed elevator has 2 doors which are fully halfway each
        floorObject.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+30, 62, 93);
        floorObject.elevDoorGraphics.drawRect(__c_.maxX-131+64, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+30, 62, 93);
        floorObject.elevDoorGraphics.endFill();
        game.world.bringToTop(floorObject.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
    };

    this._paintOpeningElevator = function(floorObject, floorNumber, callback) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{counter:0, callCounter:0, fo:floorObject, fn:floorNumber, cb:callback}, callback:function(m, ctx) {
            m.doorState = 'opening';
            if ((ctx.callCounter++)%2 !== 0) return true; // slow down the opening a bit, dont process every callback

            ctx.fo.elevDoorGraphics.clear();
            ctx.fo.elevDoorGraphics.beginFill(0xfff5ee);
            ctx.fo.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 62-ctx.counter, 93);
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131+64+ctx.counter, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 62-ctx.counter, 93);
            ctx.fo.elevDoorGraphics.endFill();
            game.world.bringToTop(ctx.fo.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
            ctx.counter++;

            if (ctx.counter == 60) {
                m.doorState = '';
                if (ctx.cb !== null) ctx.cb(); // invoke the callback at the end of opening the elevator
                return false;
            } else return true; // we need to continue the callbacks
        }};
    };

    this._paintClosingElevator = function(floorObject, floorNumber, callback) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{counter:0, callCounter:0, fo:floorObject, fn:floorNumber, cb:callback}, callback:function(m, ctx) {
            m.doorState = 'closing';
            if ((ctx.callCounter++)%2 !== 0) return true; // slow down the opening a bit, dont process every callback

            ctx.fo.elevDoorGraphics.clear();
            ctx.fo.elevDoorGraphics.beginFill(0xfff5ee);
            ctx.fo.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 3+ctx.counter, 93);
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131+64+59-ctx.counter, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 3+ctx.counter, 93);
            ctx.fo.elevDoorGraphics.endFill();
            game.world.bringToTop(ctx.fo.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
            ctx.counter++;

            if (ctx.counter == 60) {
                m.doorState = '';
                if (ctx.cb !== null) ctx.cb(); // invoke the callback at the end of closing the elevator
                return false;
            } else return true; // we need to continue the callbacks
        }};
    };

    this.setCallStatus = function(direction, newVal, floorNumber) {
        floorObject = this.floorObjects[floorNumber];
        currVal = direction == "up" ? floorObject.upCalled : floorObject.downCalled;
        if (currVal != newVal || (direction == "up" && floorObject.upCalledSprite === null) || (direction == "down" && floorObject.downCalledSprite === null)) {
            // delete the current sprite, if any
            sprite = direction == "up" ? floorObject.upCalledSprite : floorObject.downCalledSprite;
            if (sprite !== null) sprite.destroy();

            // recreate the sprites which show the call button
            if (direction == "up") {
                floorObject.upCalled = newVal;
                floorObject.upCalledSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+64, floorObject.upCalled ? 'upon' : 'upoff');
                this.spriteGroup.add(floorObject.upCalledSprite);
            } else {
                floorObject.downCalled = newVal;
                floorObject.downCalledSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+76, floorObject.downCalled ? 'downon' : 'downoff');
                this.spriteGroup.add(floorObject.downCalledSprite);
            }
        }

        // notify all persons on that floor waiting for the elevator in that direction so they do not go on again to press the call button
        personManager.emit('elev-called', floorNumber, direction);
    };

    this.elevCalled = function(floorNumber, direction) {
        this.setCallStatus(direction, true, floorNumber);
        controller.emit('elev-called', floorNumber+1, direction); // send an event to the controller that the elevator has been called on this floor
    };

    this.elevArrived = function(_floorNumber, direction) {
        window.console.log('Arrived at floor ' + _floorNumber);
        floorNumber = _floorNumber-1; // this will be invoked from the controller which uses floor numbering starting from 1

        // reset the appropriate call status for that floor
        this.setCallStatus(direction,false, floorNumber);

        // update the floor display sprite on all floors to reflect that the elevator is now on this floor
        _.each(this.floorObjects, function(floorObject,i) {
            if (floorObject.elevAtSprite !== null) floorObject.elevAtSprite.destroy();
            floorObject.elevAtSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(i+1))+36, '' + _floorNumber + '');
            floorManager.spriteGroup.add(floorObject.elevAtSprite);
        });

        // inform people waiting to go in that direction on that floor, so they 'look' at the elevator and collect them for boarding
        personManager.emit('elev-opening', floorNumber, direction);
    };

    this.openElevator = function(floorNumber) {
        // paint an opening elevator and when opened inform personmanager so it can send people to board or people on board looking to get off can leave
        this._paintOpeningElevator(this.floorObjects[floorNumber], floorNumber, function() {
            window.console.log('elevator opened');
            personManager.emit('elev-opened', floorNumber);
        });
    };

    this.elevBoarded = function(floorNumber, direction) {
        this._paintClosingElevator(this.floorObjects[floorNumber], floorNumber, function() {
            window.console.log('Elevator boarded at floor ' + floorNumber + ' in direction ' + direction);
            // once the elevator has closed, people in the elevator can click buttons to request a floor
            personManager.emit('elev-closed', floorNumber);

            // whether or not there are people in the elevator or not and whether they have pressed the request buttons or not, tell the controller that the elevator is now closed
            controller.emit('elev-closed');
        });
    };

    this.travelToFloor = function(_fromFloor, _toFloor) {
        // when called from the controller, the floor numbering is 1 greater than how floor manager considers it
        fromFloor = _fromFloor-1;
        toFloor = _toFloor-1;

        // we have been asked to show the elevator moving from one floor to another
        // now increment the floor of the elevator by 1 or -1 (depending on direction) in a timed interval
        // at each floor, change the floor indicator to new floor on all floors
        // also, move the sprite for all persons on the elevator by 1 floor height
        incr = toFloor > fromFloor ? 1 : -1;
        direction = toFloor > fromFloor ? "up" : "down";

        window.console.log('Traveling from ' + fromFloor + ' to ' + toFloor);

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{callCounter:0, cf:fromFloor, incr:incr, tf:toFloor, dir:direction}, callback:function(m, ctx) {
            if ((ctx.callCounter++)%50 != 49) return true; // move the elevator slowly, dont process every callback

            ctx.cf += ctx.incr;
            m.floorsTraveled++;

            _.each(floorManager.floorObjects, function(floorObject,i) {
                if (floorObject.elevAtSprite !== null) floorObject.elevAtSprite.destroy();
                floorObject.elevAtSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(i+1))+36, '' + (ctx.cf+1) + ''); // floor number displayed is US convention, 1 for ground floor
                floorManager.spriteGroup.add(floorObject.elevAtSprite);
            });

            if ((ctx.cf == ctx.tf) || (ctx.cf == __c_.numFloors-1 && ctx.incr == 1) || (ctx.cf === 0 && ctx.incr == -1)) {
                // we have reached the top going up or bottom coming down or we have reached the requested floor, just stop the elevator
                controller.emit('floor-arrived', ctx.cf+1, ctx.dir);
                return false; // we have reached the destination, no more callbacks required
            }

            controller.emit('interim-floor-arrived', ctx.cf+1); // inform that we are on the way and reached one floor

            return true; // still on our way, continue callbacks
        }};
    };
};

// allow for event processing for this object
FloorManager.prototype = new EventEmitter();

var floorManager = new FloorManager();
<commit_msg>Handle reset and restart<commit_after>var FloorManager = function() {
    this.floorObjects = null;
    this.spriteGroup = null;
    this.timer1 = null;
    this.floorsTraveled = 0;
    this.doorState = '';

    this.initialize = function() {
        this.floorObjects = new Array(__c_.numFloors);
        for (i = 0; i < __c_.numFloors; ++i) {
            this.floorObjects[i] = {upCalled:false, downCalled:false, upCalledSprite:null, downCalledSprite:null, elevDoorGraphics:null, elevAtSprite:null };
        }

        this.spriteGroup = game.add.group();
        this.spriteGroup.z = 2;

        // add all event handlers
        this.addListener('elev-called', this.elevCalled);
        this.addListener('elev-arrived', this.elevArrived);
        this.addListener('elev-boarded', this.elevBoarded);

        this._reset();

        // start a timer every n msecs which does actions as required on all persons
        this.stopTimer();
        this.intervalID = window.setInterval(this._processTimer, 12);
    };

    this._reset = function() {
        this.timer1 = null;
        this.floorsTraveled = 0;

        //cycle through each floor
        //initialize the display of the up/down call buttons
        //instantly draw a closed door (not closing door)
        _.each(this.floorObjects, function(fo,i) {
            floorManager.setCallStatus("up", false, i);
            floorManager.setCallStatus("down", false, i);
            floorManager._paintClosedElevator(fo, i);
        });
        // update the floor display sprite on all floors to reflect that the elevator is on the 1st floor
        this._updateFloorNumberDisplay(1);
    };

    this.shutdown = function() {
        this._reset();
        this.stopTimer();
    };

    this.stopTimer = function() {
        if (this.intervalID != 'undefined' && this.intervalID != null) {
            window.clearInterval(this.intervalID);
            this.intervalID = null;
        }
    };

    this._processTimer = function() {
        t = floorManager.timer1;
        if (t !== null && t.type == 'countdown') { // a countdown timer is fired after n intervals
            if (t.countdown === 0) { // n intervals done
                t.callback(floorManager, t.ctx); // invoke the callback
                if (t.id == floorManager.timer1.id) floorManager.timer1 = null; // null at end of countdown, but only if the timer hasnt been substituted during the call
            } else t.countdown--; // one more interval done
        } else if (t !== null && t.type == 'loop') {
            ret = t.callback(floorManager, t.ctx); // invoke the callback and check the return value
            if (!ret && t.id == floorManager.timer1.id) floorManager.timer1 = null; // if the callback had returned true, it means the callbacks should continue, else false means stop further callbacks (unless the callback changed the timer itself)
        }
    };

    this.getProperty = function(floorNumber, key) {
        return this.floorObjects[floorNumber][key];
    };

    this._paintClosedElevator = function(floorObject, floorNumber) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();
        else floorObject.elevDoorGraphics.clear();

        floorObject.elevDoorGraphics.beginFill(0xfff5ee);
        floorObject.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
        // a perfect closed elevator has 2 doors which are fully halfway each
        floorObject.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+30, 62, 93);
        floorObject.elevDoorGraphics.drawRect(__c_.maxX-131+64, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+30, 62, 93);
        floorObject.elevDoorGraphics.endFill();
        game.world.bringToTop(floorObject.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
    };

    this._paintOpeningElevator = function(floorObject, floorNumber, callback) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{counter:0, callCounter:0, fo:floorObject, fn:floorNumber, cb:callback}, callback:function(m, ctx) {
            m.doorState = 'opening';
            if ((ctx.callCounter++)%2 !== 0) return true; // slow down the opening a bit, dont process every callback

            ctx.fo.elevDoorGraphics.clear();
            ctx.fo.elevDoorGraphics.beginFill(0xfff5ee);
            ctx.fo.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 62-ctx.counter, 93);
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131+64+ctx.counter, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 62-ctx.counter, 93);
            ctx.fo.elevDoorGraphics.endFill();
            game.world.bringToTop(ctx.fo.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
            ctx.counter++;

            if (ctx.counter == 60) {
                m.doorState = '';
                if (ctx.cb !== null) ctx.cb(); // invoke the callback at the end of opening the elevator
                return false;
            } else return true; // we need to continue the callbacks
        }};
    };

    this._paintClosingElevator = function(floorObject, floorNumber, callback) {
        if (floorObject.elevDoorGraphics === null) floorObject.elevDoorGraphics = game.add.graphics();

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{counter:0, callCounter:0, fo:floorObject, fn:floorNumber, cb:callback}, callback:function(m, ctx) {
            m.doorState = 'closing';
            if ((ctx.callCounter++)%2 !== 0) return true; // slow down the opening a bit, dont process every callback

            ctx.fo.elevDoorGraphics.clear();
            ctx.fo.elevDoorGraphics.beginFill(0xfff5ee);
            ctx.fo.elevDoorGraphics.lineStyle(2, 0x000000, 1); // thick black rectangle filled in white, representing a half of the elevator door
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 3+ctx.counter, 93);
            ctx.fo.elevDoorGraphics.drawRect(__c_.maxX-131+64+59-ctx.counter, __c_.maxY()-(__c_.tilePix*__c_.floorH*(ctx.fn+1))+30, 3+ctx.counter, 93);
            ctx.fo.elevDoorGraphics.endFill();
            game.world.bringToTop(ctx.fo.elevDoorGraphics); // or else this will be hidden behind the elevator sprite
            ctx.counter++;

            if (ctx.counter == 60) {
                m.doorState = '';
                if (ctx.cb !== null) ctx.cb(); // invoke the callback at the end of closing the elevator
                return false;
            } else return true; // we need to continue the callbacks
        }};
    };

    this.setCallStatus = function(direction, newVal, floorNumber) {
        floorObject = this.floorObjects[floorNumber];
        currVal = direction == "up" ? floorObject.upCalled : floorObject.downCalled;
        if (currVal != newVal || (direction == "up" && floorObject.upCalledSprite === null) || (direction == "down" && floorObject.downCalledSprite === null)) {
            // delete the current sprite, if any
            sprite = direction == "up" ? floorObject.upCalledSprite : floorObject.downCalledSprite;
            if (sprite !== null) sprite.destroy();

            // recreate the sprites which show the call button
            if (direction == "up") {
                floorObject.upCalled = newVal;
                floorObject.upCalledSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+64, floorObject.upCalled ? 'upon' : 'upoff');
                this.spriteGroup.add(floorObject.upCalledSprite);
            } else {
                floorObject.downCalled = newVal;
                floorObject.downCalledSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(floorNumber+1))+76, floorObject.downCalled ? 'downon' : 'downoff');
                this.spriteGroup.add(floorObject.downCalledSprite);
            }
        }

        // notify all persons on that floor waiting for the elevator in that direction so they do not go on again to press the call button
        personManager.emit('elev-called', floorNumber, direction);
    };

    this.elevCalled = function(floorNumber, direction) {
        this.setCallStatus(direction, true, floorNumber);
        controller.emit('elev-called', floorNumber+1, direction); // send an event to the controller that the elevator has been called on this floor
    };

    this.elevArrived = function(_floorNumber, direction) {
        window.console.log('Arrived at floor ' + _floorNumber);
        floorNumber = _floorNumber-1; // this will be invoked from the controller which uses floor numbering starting from 1

        // reset the appropriate call status for that floor
        this.setCallStatus(direction,false, floorNumber);

        // update the floor display sprite on all floors to reflect that the elevator is now on this floor
        this._updateFloorNumberDisplay(_floorNumber);

        // inform people waiting to go in that direction on that floor, so they 'look' at the elevator and collect them for boarding
        personManager.emit('elev-opening', floorNumber, direction);
    };

    this._updateFloorNumberDisplay = function(_floorNumber) {
        // update the floor display sprite on all floors to reflect that the elevator is now on this floor
        _.each(this.floorObjects, function(floorObject,i) {
            if (floorObject.elevAtSprite !== null) floorObject.elevAtSprite.destroy();
            floorObject.elevAtSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(i+1))+36, '' + _floorNumber + '');
            floorManager.spriteGroup.add(floorObject.elevAtSprite);
        });
    };

    this.openElevator = function(floorNumber) {
        // paint an opening elevator and when opened inform personmanager so it can send people to board or people on board looking to get off can leave
        this._paintOpeningElevator(this.floorObjects[floorNumber], floorNumber, function() {
            window.console.log('elevator opened');
            personManager.emit('elev-opened', floorNumber);
        });
    };

    this.elevBoarded = function(floorNumber, direction) {
        this._paintClosingElevator(this.floorObjects[floorNumber], floorNumber, function() {
            window.console.log('Elevator boarded at floor ' + floorNumber + ' in direction ' + direction);
            // once the elevator has closed, people in the elevator can click buttons to request a floor
            personManager.emit('elev-closed', floorNumber);

            // whether or not there are people in the elevator or not and whether they have pressed the request buttons or not, tell the controller that the elevator is now closed
            controller.emit('elev-closed');
        });
    };

    this.travelToFloor = function(_fromFloor, _toFloor) {
        // when called from the controller, the floor numbering is 1 greater than how floor manager considers it
        fromFloor = _fromFloor-1;
        toFloor = _toFloor-1;

        // we have been asked to show the elevator moving from one floor to another
        // now increment the floor of the elevator by 1 or -1 (depending on direction) in a timed interval
        // at each floor, change the floor indicator to new floor on all floors
        // also, move the sprite for all persons on the elevator by 1 floor height
        incr = toFloor > fromFloor ? 1 : -1;
        direction = toFloor > fromFloor ? "up" : "down";

        window.console.log('Traveling from ' + fromFloor + ' to ' + toFloor);

        this.timer1 = {id:_.uniqueId(), type:'loop', ctx:{callCounter:0, cf:fromFloor, incr:incr, tf:toFloor, dir:direction}, callback:function(m, ctx) {
            if ((ctx.callCounter++)%50 != 49) return true; // move the elevator slowly, dont process every callback

            ctx.cf += ctx.incr;
            m.floorsTraveled++;

            _.each(floorManager.floorObjects, function(floorObject,i) {
                if (floorObject.elevAtSprite !== null) floorObject.elevAtSprite.destroy();
                floorObject.elevAtSprite = game.add.sprite(__c_.maxX-155, __c_.maxY()-(__c_.tilePix*__c_.floorH*(i+1))+36, '' + (ctx.cf+1) + ''); // floor number displayed is US convention, 1 for ground floor
                floorManager.spriteGroup.add(floorObject.elevAtSprite);
            });

            if ((ctx.cf == ctx.tf) || (ctx.cf == __c_.numFloors-1 && ctx.incr == 1) || (ctx.cf === 0 && ctx.incr == -1)) {
                // we have reached the top going up or bottom coming down or we have reached the requested floor, just stop the elevator
                controller.emit('floor-arrived', ctx.cf+1, ctx.dir);
                return false; // we have reached the destination, no more callbacks required
            }

            controller.emit('interim-floor-arrived', ctx.cf+1); // inform that we are on the way and reached one floor

            return true; // still on our way, continue callbacks
        }};
    };
};

// allow for event processing for this object
FloorManager.prototype = new EventEmitter();

var floorManager = new FloorManager();
<|endoftext|>


<commit_before>/**
 * @Description: GridGallery is a tiny package that will generate a grid depending on
 *               the elements height. It is not required to have the same height
 *
 * @Author:      Mohamed Hassan
 * @Author Url:  http://mohamedhassan.me
 *
 * @License:     under MIT
 *               https://github.com/pencilpix/grid-gallery/blob/master/LICENSE
 */
((name, definition) => {
  let theModule  = definition(),
      hasDefine  = typeof define === 'function' && define.amd,
      hasExports = typeof module !== 'undefined' && module.exports;

  if(hasDefine)
    define(theModule);
  else if(hasExports)
    module.exports = theModule;
  else
    window[name] = theModule;

})('GridGallery', () => {
  const VERSION = '1.0.1';

  const DEFAULTS = {
    direction: 'left',
  };

  class GridGallery {
    constructor( element, options ) {
      this.options = Object.assign({}, GridGallery.DEFAULTS, options);
    }

    static get DEFAULTS () {
      if(document.documentElement.dir === 'rtl')
        DEFAULTS.direction = 'right';
      else
        DEFAULTS.direction = 'left';

      return DEFAULTS;
    }
  }

  return GridGallery;
});

<commit_msg>Assign element to the instance<commit_after>/**
 * @Description: GridGallery is a tiny package that will generate a grid depending on
 *               the elements height. It is not required to have the same height
 *
 * @Author:      Mohamed Hassan
 * @Author Url:  http://mohamedhassan.me
 *
 * @License:     under MIT
 *               https://github.com/pencilpix/grid-gallery/blob/master/LICENSE
 */
((name, definition) => {
  let theModule  = definition(),
      hasDefine  = typeof define === 'function' && define.amd,
      hasExports = typeof module !== 'undefined' && module.exports;

  if(hasDefine)
    define(theModule);
  else if(hasExports)
    module.exports = theModule;
  else
    window[name] = theModule;

})('GridGallery', () => {
  const VERSION = '1.0.1';

  const DEFAULTS = {
    direction: 'left',
  };

  class GridGallery {
    constructor( element, options ) {
      this.element = element;
      this.options = Object.assign({}, GridGallery.DEFAULTS, options);
    }

    static get DEFAULTS () {
      if(document.documentElement.dir === 'rtl')
        DEFAULTS.direction = 'right';
      else
        DEFAULTS.direction = 'left';

      return DEFAULTS;
    }
  }

  return GridGallery;
});

<|endoftext|>


<commit_before>/*eslint-disable no-alert */
var Util = {
    alert: function (msg) {
      return window.alert(msg);
    },
    confirm: function (msg) {
      return window.confirm(msg);
    },
    prompt: function (msg) {
      return window.prompt(msg);
    }
};
/*eslint-enable no-alert */

module.exports = Util;
<commit_msg>Refactor Util for readability<commit_after>var Util = {
  alert: function (msg) {
    /*eslint-disable no-alert */
    return global.alert(msg);
    /*eslint-enable no-alert */
  },
  confirm: function (msg) {
    /*eslint-disable no-alert */
    return global.confirm(msg);
    /*eslint-enable no-alert */
  },
  prompt: function (msg) {
    /*eslint-disable no-alert */
    return global.prompt(msg);
    /*eslint-enable no-alert */
  }
};

module.exports = Util;
<|endoftext|>


<commit_before>console.log('interactions.js');

/**
 * A single interaction
 */
window.Interaction = Backbone.Model.extend({

  // HACK: Actions from the API don't have an id. This will not work if there are more than one interaction in any givin second
  idAttribute: 'event_date',

  initialize: function() {
    _.bindAll(this);
  },

  url: 'https://alpha-api.app.net/stream/0/posts',

  validate: function(attributes) {
    if (attributes.text.length > 256) {
      return 'text is too long';
    }
  },

  save: function() {
    if (this.get('text') && this.get('text').length < 256) {
      // TODO: for some reason this is not adding the response to the model
      var jqXHR = $.post(this.url, this.attributes)
          .success(this.success)
          .error(this.error);
    } else {
      return false;
    }
  },

  success: function(response, textStatus, jqXHR) {
    var notification = new TextNotificationView({
      title: 'Successfully posted to App.net',
      body: response.text,
      image: response.user.avatar_image.url,
      url: 'https://alpha.app.net/' + response.user.username + '/post/' + response.id,
      timeout: 5 * 1000,
      type: 'PostSuccess'
    });
    notification.render();
  },

  error: function() {
    var notification = new TextNotificationView({
      image: chrome.extension.getURL('/img/angle.png'),
      title: 'Posting to App.net failed',
      body: 'Please try agian. This notification will close in 10 seconds.',
      timeout: 10 * 1000,
      type: 'PostError'
    });
    notification.render();
  }

});

/**
 * This is left over from a previous design choice. It may be going away.
 */
var Polling = Backbone.Collection.extend({

  initialize: function(options) {
    _.bindAll(this, 'error');
    _.extend(this, options);
  },

  // update: function() {
  //   if (window.account && window.account.get('accessToken') && config.get(this.configName)) {
  //     this.fetch({ error: this.error });
  //   }
  //   return this;
  // },

  error: function(collection, response, options) {
    // TODO: update copy of notifications
    if (response.status === 401) {
      console.log('Invalid access_token');
      var notification = new TextNotificationView({
        image: chrome.extension.getURL('/img/angle.png'),
        title: 'Authentication failed',
        body: 'Click here to sign in to App.net again.',
        url: chrome.extension.getURL('/options.html'),
        type: 'AuthError'
      });
      notification.render();
      // TODO: update this to support multiple accounts
      accounts.remove(accounts.at(0));
    } else {
      console.log('Unkown error');
      var notification = new TextNotificationView({
        image: chrome.extension.getURL('/img/angle.png'),
        title: 'Unkown error checking for posts',
        body: 'If you get this a lot please ping @abraham',
        url: 'https://alpha.app.net/abraham',
        type: 'UnknownError'
      });
      notification.render();
    }
    return this;
  }

});

/**
 * A collection of interactions
 */
var Interactions = Polling.extend({

  model: Interaction,

  url: 'https://alpha-api.app.net/stream/0/users/me/interactions',

  initialize: function() {
    _.bindAll(this, 'checkForNew');
  },

  checkForNew: function() {
    if (accounts.length === 0) {
      return false;
    }
    this.fetch({
      error: this.error,
      update: true,
      data: {
        count: 2 // TODO: start using since_id
      },
      headers: {
        'Authorization': 'Bearer ' + accounts.at(0).get('access_token'),
        // HACK: should be applied globally
        'X-ADN-Migration-Overrides': 'response_envelope=1&disable_min_max_id=1&follow_pagination=1&pagination_ids=1'
      }
    });
  },

  parse: function(response) {
    return response.data;
  },

  // renderNotification: function(model, index, array) {
  //   if (this.notificationType === 'mentions') {
  //     this.renderMentionNotification(model);
  //   } else {
  //     console.log('this.notificationType', this.notificationType);
  //   }
  // },
  // renderMentionNotification: function(model) {
  //   var notification = new TextNotificationView({
  //     image: model.get('user').avatar_image.url,
  //     title: 'Mentioned by @' + model.get('user').username + ' on ADN',
  //     body: model.get('text'),
  //     url: 'https://alpha.app.net/' + model.get('user').username + '/post/' + model.get('id'),
  //     type: 'Mention'
  //   });
  //   notification.render();
  // },
  // filterNewPosts: function() {
  //   var models = [];
  //   var lastCreatedAt = localStorage.getItem(this.configName + '_lastCreatedAt');
  //   // Don't notify for new polling channels
  //   if (!lastCreatedAt) {
  //     localStorage.setItem(this.configName + '_lastCreatedAt', (new Date()).getTime());
  //     return this;
  //   }
  //
  //   // Reject posts by authenticated account
  //   models = this.reject(function(post){ return post.get('user').id === account.get('id'); });
  //   // Filter out old posts
  //   models = models.filter(function(post){ return (new Date(post.get('created_at'))).getTime() > parseInt(lastCreatedAt); });
  //
  //   // Store latest created_at date for future matches
  //   if (_.first(models)) {
  //     localStorage.setItem(this.configName + '_lastCreatedAt', (new Date(_.first(models).get('created_at'))).getTime());
  //   }
  //   _.each(models, this.renderNotification);
  // }

});

window.interactions = new Interactions();<commit_msg>automatically add view to each interaction model<commit_after>console.log('interactions.js');

/**
 * A single interaction
 */
window.Interaction = Backbone.Model.extend({

  // HACK: Actions from the API don't have an id. This will not work if there are more than one interaction in any givin second
  idAttribute: 'event_date',

  initialize: function() {
    _.bindAll(this);
    this.view = new TextNotificationView({ model: this });
  },

  url: 'https://alpha-api.app.net/stream/0/posts',

  validate: function(attributes) {
    if (attributes.text.length > 256) {
      return 'text is too long';
    }
  },

  save: function() {
    if (this.get('text') && this.get('text').length < 256) {
      // TODO: for some reason this is not adding the response to the model
      var jqXHR = $.post(this.url, this.attributes)
          .success(this.success)
          .error(this.error);
    } else {
      return false;
    }
  },

  success: function(response, textStatus, jqXHR) {
    var notification = new TextNotificationView({
      title: 'Successfully posted to App.net',
      body: response.text,
      image: response.user.avatar_image.url,
      url: 'https://alpha.app.net/' + response.user.username + '/post/' + response.id,
      timeout: 5 * 1000,
      type: 'PostSuccess'
    });
    notification.render();
  },

  error: function() {
    var notification = new TextNotificationView({
      image: chrome.extension.getURL('/img/angle.png'),
      title: 'Posting to App.net failed',
      body: 'Please try agian. This notification will close in 10 seconds.',
      timeout: 10 * 1000,
      type: 'PostError'
    });
    notification.render();
  }

});

/**
 * This is left over from a previous design choice. It may be going away.
 */
var Polling = Backbone.Collection.extend({

  initialize: function(options) {
    _.bindAll(this, 'error');
    _.extend(this, options);
  },

  // update: function() {
  //   if (window.account && window.account.get('accessToken') && config.get(this.configName)) {
  //     this.fetch({ error: this.error });
  //   }
  //   return this;
  // },

  error: function(collection, response, options) {
    // TODO: update copy of notifications
    if (response.status === 401) {
      console.log('Invalid access_token');
      var notification = new TextNotificationView({
        image: chrome.extension.getURL('/img/angle.png'),
        title: 'Authentication failed',
        body: 'Click here to sign in to App.net again.',
        url: chrome.extension.getURL('/options.html'),
        type: 'AuthError'
      });
      notification.render();
      // TODO: update this to support multiple accounts
      accounts.remove(accounts.at(0));
    } else {
      console.log('Unkown error');
      var notification = new TextNotificationView({
        image: chrome.extension.getURL('/img/angle.png'),
        title: 'Unkown error checking for posts',
        body: 'If you get this a lot please ping @abraham',
        url: 'https://alpha.app.net/abraham',
        type: 'UnknownError'
      });
      notification.render();
    }
    return this;
  }

});

/**
 * A collection of interactions
 */
var Interactions = Polling.extend({

  model: Interaction,

  url: 'https://alpha-api.app.net/stream/0/users/me/interactions',

  initialize: function() {
    _.bindAll(this, 'checkForNew');
  },

  checkForNew: function() {
    if (accounts.length === 0) {
      return false;
    }
    this.fetch({
      error: this.error,
      update: true,
      data: {
        count: 2 // TODO: start using since_id
      },
      headers: {
        'Authorization': 'Bearer ' + accounts.at(0).get('access_token'),
        // HACK: should be applied globally
        'X-ADN-Migration-Overrides': 'response_envelope=1&disable_min_max_id=1&follow_pagination=1&pagination_ids=1'
      }
    });
  },

  parse: function(response) {
    return response.data;
  },

  // renderNotification: function(model, index, array) {
  //   if (this.notificationType === 'mentions') {
  //     this.renderMentionNotification(model);
  //   } else {
  //     console.log('this.notificationType', this.notificationType);
  //   }
  // },
  // renderMentionNotification: function(model) {
  //   var notification = new TextNotificationView({
  //     image: model.get('user').avatar_image.url,
  //     title: 'Mentioned by @' + model.get('user').username + ' on ADN',
  //     body: model.get('text'),
  //     url: 'https://alpha.app.net/' + model.get('user').username + '/post/' + model.get('id'),
  //     type: 'Mention'
  //   });
  //   notification.render();
  // },
  // filterNewPosts: function() {
  //   var models = [];
  //   var lastCreatedAt = localStorage.getItem(this.configName + '_lastCreatedAt');
  //   // Don't notify for new polling channels
  //   if (!lastCreatedAt) {
  //     localStorage.setItem(this.configName + '_lastCreatedAt', (new Date()).getTime());
  //     return this;
  //   }
  //
  //   // Reject posts by authenticated account
  //   models = this.reject(function(post){ return post.get('user').id === account.get('id'); });
  //   // Filter out old posts
  //   models = models.filter(function(post){ return (new Date(post.get('created_at'))).getTime() > parseInt(lastCreatedAt); });
  //
  //   // Store latest created_at date for future matches
  //   if (_.first(models)) {
  //     localStorage.setItem(this.configName + '_lastCreatedAt', (new Date(_.first(models).get('created_at'))).getTime());
  //   }
  //   _.each(models, this.renderNotification);
  // }

});

window.interactions = new Interactions();<|endoftext|>


<commit_before>'use strict'

let TicketApi = require('resource-management-framework')
	.TicketApi;

class Ticket {
	constructor() {
		this.emitter = message_bus;
	}

	init() {
		this.iris = new TicketApi();
		this.iris.initContent();
	}

	launch() {
			this.emitter.on('ticket.emit.state', ({
				ticket,
				org_addr,
				workstation,
				event_name
			}) => {
				let to_join = ['ticket', event_name, org_addr, workstation];
				console.log("EMITTING TICKSTATE", _.join(to_join, "."));
				this.emitter.emit('broadcast', {
					event: _.join(to_join, "."),
					data: ticket
				});
			});
			return Promise.resolve(true);
		}
		//API
	actionTicket({
		query,
		keys
	}) {
		return this.iris.getTicket({
			query,
			keys
		});
	}

	actionBasicPriorities() {
		return this.iris.getBasicPriorities();
	}

	actionCanChangeState({
		from,
		to
	}) {
		let allowed_transform = {
			"registered=>called": true,
			"registered=>removed": true,
			"registered=>registered": true,
			"postponed=>called": true,
			"postponed=>registered": true,
			"booked=>registered": true,
			"booked=>removed": true,
			"booked=>expired": true,
			"called=>removed": true,
			"called=>registered": true,
			"called=>expired": true,
			"called=>processing": true,
			"called=>postponed": true,
			"processing=>closed": true,
			"processing=>postponed": true,
			"processing=>registered": true
		};

		return !!allowed_transform[_.join([from, to], "=>")] && !allowed_transform[_.join(['*', to], "=>")];
	}

	actionChangeState({
		ticket,
		state,
		fields = {},
		unset = []
	}) {

		let tick_data;
		return this.iris.getTicket({
				keys: ticket
			})
			.then((tick) => {
				tick_data = _.find(tick, (t) => (t.id == ticket));
				let old_state = tick_data.state;
				if (!this.actionCanChangeState({
						from: old_state,
						to: state
					}))
					return Promise.reject(new Error(`State change not allowed: ${old_state} => ${state}.`));
				tick_data.state = state;
				_.map(unset, v => {
					let lock = _.get(tick_data, ['locked_fields', v], false);
					if (!lock) {
						_.unset(tick_data, v);
					} else {
						tick_data[v] = lock;
					}
				});
				tick_data = _.merge(tick_data, fields);
				return this.iris.setTicket(tick_data);
			})
			.then((res) => {
				// console.log("SET TICK RES", res);
				if (!res[tick_data.id])
					return Promise.reject(new Error(`Failed to change state: cannot save ticket.`));
				tick_data.cas = res[tick_data.id].cas;
				return {
					ticket: tick_data,
					success: true
				};
			})
			.catch((err) => {
				console.log("ERR CHANGE STATE", err.message);
				return {
					success: false,
					ticket: tick_data,
					reason: err.message
				};
			});
	}

	actionByCode({
		code
	}) {
		return this.iris.getCodeLookup(code)
			.then((res) => {
				console.log(res);
				if (!res) {
					return Promise.resolve({});
				}
				return this.iris.getTicket({
					keys: res
				});
			})
			.then(tickets => {
				let ticket = _.head(_.values(tickets));
				if (!ticket) {
					return {
						success: false,
						reason: "Ticket not found"
					};
				}

				return {
					ticket: ticket,
					success: true
				}
			});
	}

	actionById({
		ticket
	}) {
		return this.iris.getTicket({
				keys: ticket
			})
			.then((res) => _.values(res));
	}

	actionHistory({
		ticket
	}) {
		let events = ['call', 'register', 'book', 'remove', 'restore', 'closed', 'postpone', 'expire', 'processing'];
		return this.iris.getTicket({
				keys: ticket
			})
			.then((res) => {
				return _.filter(_.get(res, `${ticket}.history`), (ent) => !!~_.indexOf(events, ent.event_name));
			});
	}

	actionSetTicket({
		ticket
	}) {
		return this.iris.setTicket(ticket)
			.then((res) => {
				if (!res[ticket.id] || !res[ticket.id].cas)
					return Promise.reject(new Error("Failed to set ticket."));
				return {
					success: true,
					ticket
				};
			})
			.catch((err) => {
				global.logger && logger.error(
					err, {
						module: 'ticket',
						method: 'set-ticket',
						ticket
					});
				return {
					success: false,
					reason: err.message
				}
			});
	}

	setRoute(route) {}
}

module.exports = Ticket;<commit_msg>State change check<commit_after>'use strict'

let TicketApi = require('resource-management-framework')
	.TicketApi;

class Ticket {
	constructor() {
		this.emitter = message_bus;
	}

	init() {
		this.iris = new TicketApi();
		this.iris.initContent();
	}

	launch() {
			this.emitter.on('ticket.emit.state', ({
				ticket,
				org_addr,
				workstation,
				event_name
			}) => {
				let to_join = ['ticket', event_name, org_addr, workstation];
				console.log("EMITTING TICKSTATE", _.join(to_join, "."));
				this.emitter.emit('broadcast', {
					event: _.join(to_join, "."),
					data: ticket
				});
			});
			return Promise.resolve(true);
		}
		//API
	actionTicket({
		query,
		keys
	}) {
		return this.iris.getTicket({
			query,
			keys
		});
	}

	actionBasicPriorities() {
		return this.iris.getBasicPriorities();
	}

	actionCanChangeState({
		from,
		to,
		operation
	}) {
		let allowed_transform = {
			"registered=>called": true,
			"registered=>removed": true,
			"registered=>registered": true,
			"postponed=>called": true,
			"postponed=>registered": true,
			"booked=>registered": true,
			"booked=>removed": true,
			"booked=>expired": true,
			"called=>removed": true,
			"called=>registered": true,
			"called=>expired": true,
			"called=>processing": true,
			"called=>postponed": true,
			"processing=>closed": true,
			"processing=>postponed": true,
			"processing=>registered": true
		};
		if (operation == 'activate') {
			if (from == 'processing' || from == 'called' || from == 'postponed')
				return false;
		}
		return !!allowed_transform[_.join([from, to], "=>")] && !allowed_transform[_.join(['*', to], "=>")];
	}

	actionChangeState({
		ticket,
		state,
		fields = {},
		unset = [],
		unlock = []
	}) {

		let tick_data;
		return this.iris.getTicket({
				keys: ticket
			})
			.then((tick) => {
				tick_data = _.find(tick, (t) => (t.id == ticket));
				let old_state = tick_data.state;
				if (!this.actionCanChangeState({
						from: old_state,
						to: state
					}))
					return Promise.reject(new Error(`State change not allowed: ${old_state} => ${state}.`));
				tick_data.state = state;
				_.map(unlock, nm => {
					_.unset(tick_data, ['locked_fields', nm]);
				});
				_.map(unset, v => {
					let lock = _.get(tick_data, ['locked_fields', v], false);
					if (!lock) {
						_.unset(tick_data, v);
					} else {
						tick_data[v] = lock;
					}
				});
				tick_data = _.merge(tick_data, fields);
				return this.iris.setTicket(tick_data);
			})
			.then((res) => {
				// console.log("SET TICK RES", res);
				if (!res[tick_data.id])
					return Promise.reject(new Error(`Failed to change state: cannot save ticket.`));
				tick_data.cas = res[tick_data.id].cas;
				return {
					ticket: tick_data,
					success: true
				};
			})
			.catch((err) => {
				console.log("ERR CHANGE STATE", err.message);
				return {
					success: false,
					ticket: tick_data,
					reason: err.message
				};
			});
	}

	actionByCode({
		code
	}) {
		return this.iris.getCodeLookup(code)
			.then((res) => {
				console.log(res);
				if (!res) {
					return Promise.resolve({});
				}
				return this.iris.getTicket({
					keys: res
				});
			})
			.then(tickets => {
				let ticket = _.head(_.values(tickets));
				if (!ticket) {
					return {
						success: false,
						reason: "Ticket not found"
					};
				}

				return {
					ticket: ticket,
					success: true
				}
			});
	}

	actionById({
		ticket
	}) {
		return this.iris.getTicket({
				keys: ticket
			})
			.then((res) => _.values(res));
	}

	actionHistory({
		ticket
	}) {
		let events = ['call', 'register', 'book', 'remove', 'restore', 'closed', 'postpone', 'expire', 'processing'];
		return this.iris.getTicket({
				keys: ticket
			})
			.then((res) => {
				return _.filter(_.get(res, `${ticket}.history`), (ent) => !!~_.indexOf(events, ent.event_name));
			});
	}

	actionSetTicket({
		ticket
	}) {
		return this.iris.setTicket(ticket)
			.then((res) => {
				if (!res[ticket.id] || !res[ticket.id].cas)
					return Promise.reject(new Error("Failed to set ticket."));
				return {
					success: true,
					ticket
				};
			})
			.catch((err) => {
				global.logger && logger.error(
					err, {
						module: 'ticket',
						method: 'set-ticket',
						ticket
					});
				return {
					success: false,
					reason: err.message
				}
			});
	}

	setRoute(route) {}
}

module.exports = Ticket;<|endoftext|>