<commit_before>/*
 * Copyright 2012 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var Aria = require("../../Aria");
var ariaWidgetsFormDropDownListTrait = require("./DropDownListTrait");
var ariaWidgetsControllersAutoCompleteController = require("../controllers/AutoCompleteController");
var ariaUtilsEvent = require("../../utils/Event");
var ariaUtilsFunction = require("../../utils/Function");
var ariaWidgetsFormAutoCompleteStyle = require("./AutoCompleteStyle.tpl.css");
var ariaWidgetsFormListListStyle = require("./list/ListStyle.tpl.css");
var ariaWidgetsContainerDivStyle = require("../container/DivStyle.tpl.css");
var ariaWidgetsFormDropDownTextInput = require("./DropDownTextInput");
var ariaCoreBrowser = require("../../core/Browser");

/**
 * AutoComplete widget
 */
module.exports = Aria.classDefinition({
    $classpath : "aria.widgets.form.AutoComplete",
    $extends : ariaWidgetsFormDropDownTextInput,
    $css : [ariaWidgetsFormAutoCompleteStyle, ariaWidgetsFormListListStyle,
            ariaWidgetsContainerDivStyle],
    /**
     * AutoComplete constructor
     * @param {aria.widgets.CfgBeans:AutoCompleteCfg} cfg the widget configuration
     * @param {aria.templates.TemplateCtxt} ctxt template context
     * @param {Number} lineNumber Line number corresponding in the .tpl file where the widget is created
     * @param {Number} controller the data controller object
     */
    $constructor : function (cfg, ctxt, lineNumber, controller) {
        var controllerInstance = controller || new ariaWidgetsControllersAutoCompleteController();

        if (!cfg.expandButton && cfg.bind) {
            delete cfg.bind.popupOpen;
        }

        this.$DropDownTextInput.constructor.call(this, cfg, ctxt, lineNumber, controllerInstance);

        if (!cfg.expandButton) {
            /**
             * Array of icon names which need to be hidden.
             * @type Array
             * @protected
             * @override
             */
            this._hideIconNames = ["dropdown"];
        }

        try {
            controllerInstance.setResourcesHandler(cfg.resourcesHandler);
        } catch (e) {
            this.$logError(this.WIDGET_AUTOCOMPLETE_INVALID_HANDLER, [cfg.resourcesHandler], e);
        }
        controllerInstance.autoFill = cfg.autoFill;
        controllerInstance.freeText = cfg.freeText;
        controllerInstance.maxlength = cfg.maxlength;
        controllerInstance.expandButton = cfg.expandButton;
        controllerInstance.selectionKeys = cfg.selectionKeys;
        controllerInstance.preselect = cfg.preselect;
        if (cfg.waiAria && cfg.waiSuggestionAriaLabelGetter) {
            controllerInstance.waiSuggestionAriaLabelGetter = ariaUtilsFunction.bind(this._waiSuggestionAriaLabelGetter, this);
        }

        /**
         * Whether the width of the popup can be smaller than the field, when configured to be so. If false, the
         * popupWidth property will be overridden when it is smaller than the field width
         * @type Boolean
         * @protected
         */
        this._freePopupWidth = false;
        this._waiSuggestionsChangedListener = null;
    },
    $destructor : function () {
        this._removeWaiSuggestionsChangedListener();

        // The dropdown might still be open when we destroy the widget, destroy it now
        if (this._dropdownPopup) {
            this._dropdownPopup.$removeListeners({
                "onBeforeClose" : this._beforeDropdownClose,
                scope : this
            });
        }

        // There might be some events
        if (this._initDone) {
            this._removeEvents();
        }

        this.$DropDownTextInput.$destructor.call(this);
    },
    $statics : {
        // ERROR MESSAGE:
        WIDGET_AUTOCOMPLETE_INVALID_HANDLER : "%1Could not create resources handler %2: dependency on this handler is missing."
    },
    $prototype : {
        /**
         * Skinnable class to use for this widget.
         * @protected
         * @type String
         */
        _skinnableClass : "AutoComplete",

        /**
         * Prototype init method called at prototype creation time Allows to store class-level objects that are shared
         * by all instances
         * @param {Object} p the prototype object being built
         */
        $init : function (p) {
            var src = ariaWidgetsFormDropDownListTrait.prototype;
            for (var key in src) {
                if (src.hasOwnProperty(key) && !p.hasOwnProperty(key)) {
                    // copy methods which are not already on this object (this avoids copying $classpath and
                    // $destructor)
                    p[key] = src[key];
                }
            }
        },

        /**
         * Internal function to render the content of the dropdown div
         * @protected
         * @param {aria.templates.MarkupWriter} out Markup writer which should receive the content of the popup.
         * @param {Object} arg Optional parameters
         */
        _renderDropdownContent : function (out, options) {
            options = options || {};
            var cfg = this._cfg;
            if (!("defaultTemplate" in options)) {
                if (cfg.suggestionsTemplate) {
                    options.defaultTemplate = cfg.suggestionsTemplate;
                } else {
                    options.defaultTemplate = this.controller.getDefaultTemplate();
                }
            }
            if (!("minWidth" in options)) {
                var inputMarkupWidth = this._inputMarkupWidth;
                // set a minimum size for the autocomplete content
                inputMarkupWidth = (inputMarkupWidth < 150) ? 150 : inputMarkupWidth;
                options.minWidth = inputMarkupWidth + this._skinObj.offsetRight;
            }
            options.maxHeight = this._cfg.popupMaxHeight || 210;
            if (cfg.waiAria) {
                options.onchange = {
                    scope: this,
                    fn: this._updateAriaActiveDescendant,
                    args: options.onchange
                };
            }
            this.$DropDownListTrait._renderDropdownContent.call(this, out, options);
        },

        /**
         * Override $DropDownTextInput._reactToControllerReport
         * @protected
         * @param {aria.widgets.controllers.reports.DropDownControllerReport} report
         * @param {Object} arg Optional parameters
         */
        _reactToControllerReport : function (report, arg) {
            // To reset focus if there is no suggestion dropdown
            if (!this.controller._resetFocus) {
                this._keepFocus = false;
            }
            this.$DropDownTextInput._reactToControllerReport.call(this, report, arg);
        },

        /**
         * DOM callback function called when the focus is taken off the input. The onBlur event is available on the
         * input that sits inside a span.
         * @param {aria.DomEvent} event Blur event
         * @protected
         */
        _dom_onblur : function (event, avoidCallback) {
            this._keepFocus = false;
            this.$DropDownTextInput._dom_onblur.call(this, event, avoidCallback);
        },

        _dom_onkeydown : function (event) {
            // On Shift+F10, when AutoComplete has no expand button enabled, we don't open the dropdown, we only close it
            if (this._isShiftF10Pressed(event) && !this._cfg.expandButton && this._dropdownPopup == null) {
                return;
            }

            this.$DropDownTextInput._dom_onkeydown.apply(this, arguments);
        },

         /**
         * Internal method to handle the onkeyup event. This is called to set the value property in the data model
         * through the setProperty method that also handles all other widgets bound to this value.
         * @protected
         */
        _dom_onkeyup : function (event) {
            this.$DropDownTextInput._dom_onkeyup.call(this, event);
            if (event.keyCode == event.KC_ENTER) {
                this.checkValue();
            }
        },

        /**
         * React to a dropdown close. If the widget is using autofill we want to select the pre-selected value in the
         * datamodel and report it to the input field also when the user clicks away from the field instead of
         * navigating through TAB or selecting an item from the dropdown. This function is called any time we close the
         * dropdown, even when typing there are no results.
         * @param {Object} event Event that triggered this callback
         * @private
         */
        _beforeDropdownClose : function (event) {
            if (this._cfg.autoFill && event.domEvent) {
                // Closing the dropdown after typing is not a domEvent
                var value = this.controller.getDataModel().value;
                if (value != null) {
                    var report = this.controller.checkDropdownValue(value);
                    this._reactToControllerReport(report);
                }
            }
        },

        /**
         * Updates the aria-activedescendant attribute on the input DOM element.
         * This method supposes that the popup is open.
         * This method is registered as the onchange callback for the list widget,
         * if accessibility is enabled. It is also called from _afterDropdownOpen.
         */
        _updateAriaActiveDescendant : function(event, cb) {
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                var listWidget = this.controller.getListWidget();
                var ariaActiveDescendant = listWidget ? listWidget.getSelectedOptionDomId() : null;
                if (ariaActiveDescendant != null) {
                    field.setAttribute("aria-activedescendant", ariaActiveDescendant);
                } else {
                    field.removeAttribute("aria-activedescendant");
                }
            }
            if (cb) {
                this.$callback(cb, event);
            }
        },

        /**
         * Callback for the event onAfterOpen raised by the popup.
         * @override
         */
        _afterDropdownOpen : function () {
            this.$DropDownListTrait._afterDropdownOpen.apply(this, arguments);
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                var listWidget = this.controller.getListWidget();
                field.setAttribute("aria-owns", listWidget.getListDomId());
                var dropDownIcon = this._getDropdownIcon();
                if (dropDownIcon) {
                    dropDownIcon.setAttribute("aria-expanded", "true");
                }
                listWidget.afterWidgetLoaded({
                    fn: this._updateAriaActiveDescendant,
                    scope: this
                });
            }
        },
        /**
         * @override
         */
        _waiSuggestionsChanged : function () {
            var self = this;
            setTimeout(function () {
                self._updateAriaActiveDescendant();
            }, 0);
            this.$DropDownListTrait._waiSuggestionsChanged.apply(this, arguments);
        },

        /**
         * Called after the dropdown is closed.
         * @override
         */
        _afterDropdownClose : function () {
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                field.removeAttribute("aria-owns");

                var dropDownIcon = this._getDropdownIcon();
                if (dropDownIcon) {
                    dropDownIcon.setAttribute("aria-expanded", "false");
                }
           }
            this.$DropDownListTrait._afterDropdownClose.apply(this, arguments);
        },

        /**
         * Override _init function to add event listeners that cannot be delegated on IE
         * @method
         * @override
         */
        _init : ariaCoreBrowser.isOldIE ? function () {
            this.$DropDownTextInput._init.call(this);

            var field = this.getTextInputField();
            ariaUtilsEvent.addListener(field, "paste", {
                fn : this._dom_onpaste,
                scope : this
            });
            ariaUtilsEvent.addListener(field, "cut", {
                fn : this._dom_oncut,
                scope : this
            });
        } : function () {
            this.$DropDownTextInput._init.call(this);
        },

        /**
         * Remove event listeners added during init. This is done for IE in order to support copy/paste
         * @method
         * @protected
         */
        _removeEvents : ariaCoreBrowser.isOldIE ? function () {
            var field = this.getTextInputField();
            ariaUtilsEvent.removeListener(field, "paste");
            ariaUtilsEvent.removeListener(field, "cut");
        } : function () {},

        /**
         * Internal method to handle the paste event. This event is converted as a Ctrl+v and handled as a keydown in
         * order to set the datamodel and open the dropdown
         * @param {aria.DomEvent} event Event object
         * @protected
         */
        _dom_onpaste : function (event) {
            this.__propagateKeyDown(event);
        },

        /**
         * Internal method to handle the cut event. This event is converted as a Ctrl+x and handled as a keydown in
         * order to set the datamodel and close the dropdown.
         * @param {aria.DomEvent} event Event object
         * @protected
         */
        _dom_oncut : function (event) {
            this.__propagateKeyDown(event);
        },

        /**
         * Convert mouse event and browser copy/paste (contextual menu event) into a keydown event that can be handled
         * by the controller
         * @param {aria.DomEvent} event Event object, in IE this is an instance of HTMLElement
         * @private
         */
        __propagateKeyDown : function (event) {
            var toBeDisposed = false;
            if (!event.$DomEvent) {
                event = new aria.DomEvent(event);
                toBeDisposed = true;
            }

            event.isSpecialKey = true;
            event.ctrlKey = true;
            event.charCode = 0;

            this._dom_onkeydown.call(this, event);

            if (toBeDisposed) {
                event.$dispose();
            }
        },
        /**
         * Internal method called when one of the model property that the widget is bound to has changed Must be
         * overridden by sub-classes defining bindable properties
         * @param {String} propertyName the property name
         * @param {Object} newValue the new value
         * @param {Object} oldValue the old property value
         * @protected
         */
        _onBoundPropertyChange : function (propertyName, newValue, oldValue) {
            if (propertyName != "popupOpen" || this._cfg.expandButton) {
                this.$DropDownTextInput._onBoundPropertyChange.apply(this, arguments);
            }
        },

        /**
         * Initialization method called by the delegate engine when the DOM is loaded
         */
        initWidget : function () {
            if (!this._cfg.expandButton && this._cfg.popupOpen) {
                this._cfg.popupOpen = false;
            }
            this.$DropDownTextInput.initWidget.call(this);
        }
    }
});
<commit_msg>fix: Jaws 2021 not reading updated autocomplete suggestion with Edge<commit_after>/*
 * Copyright 2012 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var Aria = require("../../Aria");
var ariaWidgetsFormDropDownListTrait = require("./DropDownListTrait");
var ariaWidgetsControllersAutoCompleteController = require("../controllers/AutoCompleteController");
var ariaUtilsEvent = require("../../utils/Event");
var ariaUtilsFunction = require("../../utils/Function");
var ariaWidgetsFormAutoCompleteStyle = require("./AutoCompleteStyle.tpl.css");
var ariaWidgetsFormListListStyle = require("./list/ListStyle.tpl.css");
var ariaWidgetsContainerDivStyle = require("../container/DivStyle.tpl.css");
var ariaWidgetsFormDropDownTextInput = require("./DropDownTextInput");
var ariaCoreBrowser = require("../../core/Browser");

/**
 * AutoComplete widget
 */
module.exports = Aria.classDefinition({
    $classpath : "aria.widgets.form.AutoComplete",
    $extends : ariaWidgetsFormDropDownTextInput,
    $css : [ariaWidgetsFormAutoCompleteStyle, ariaWidgetsFormListListStyle,
            ariaWidgetsContainerDivStyle],
    /**
     * AutoComplete constructor
     * @param {aria.widgets.CfgBeans:AutoCompleteCfg} cfg the widget configuration
     * @param {aria.templates.TemplateCtxt} ctxt template context
     * @param {Number} lineNumber Line number corresponding in the .tpl file where the widget is created
     * @param {Number} controller the data controller object
     */
    $constructor : function (cfg, ctxt, lineNumber, controller) {
        var controllerInstance = controller || new ariaWidgetsControllersAutoCompleteController();

        if (!cfg.expandButton && cfg.bind) {
            delete cfg.bind.popupOpen;
        }

        this.$DropDownTextInput.constructor.call(this, cfg, ctxt, lineNumber, controllerInstance);

        if (!cfg.expandButton) {
            /**
             * Array of icon names which need to be hidden.
             * @type Array
             * @protected
             * @override
             */
            this._hideIconNames = ["dropdown"];
        }

        try {
            controllerInstance.setResourcesHandler(cfg.resourcesHandler);
        } catch (e) {
            this.$logError(this.WIDGET_AUTOCOMPLETE_INVALID_HANDLER, [cfg.resourcesHandler], e);
        }
        controllerInstance.autoFill = cfg.autoFill;
        controllerInstance.freeText = cfg.freeText;
        controllerInstance.maxlength = cfg.maxlength;
        controllerInstance.expandButton = cfg.expandButton;
        controllerInstance.selectionKeys = cfg.selectionKeys;
        controllerInstance.preselect = cfg.preselect;
        if (cfg.waiAria && cfg.waiSuggestionAriaLabelGetter) {
            controllerInstance.waiSuggestionAriaLabelGetter = ariaUtilsFunction.bind(this._waiSuggestionAriaLabelGetter, this);
        }

        /**
         * Whether the width of the popup can be smaller than the field, when configured to be so. If false, the
         * popupWidth property will be overridden when it is smaller than the field width
         * @type Boolean
         * @protected
         */
        this._freePopupWidth = false;
        this._waiSuggestionsChangedListener = null;
    },
    $destructor : function () {
        this._removeWaiSuggestionsChangedListener();

        // The dropdown might still be open when we destroy the widget, destroy it now
        if (this._dropdownPopup) {
            this._dropdownPopup.$removeListeners({
                "onBeforeClose" : this._beforeDropdownClose,
                scope : this
            });
        }

        // There might be some events
        if (this._initDone) {
            this._removeEvents();
        }

        this.$DropDownTextInput.$destructor.call(this);
    },
    $statics : {
        // ERROR MESSAGE:
        WIDGET_AUTOCOMPLETE_INVALID_HANDLER : "%1Could not create resources handler %2: dependency on this handler is missing."
    },
    $prototype : {
        /**
         * Skinnable class to use for this widget.
         * @protected
         * @type String
         */
        _skinnableClass : "AutoComplete",

        /**
         * Prototype init method called at prototype creation time Allows to store class-level objects that are shared
         * by all instances
         * @param {Object} p the prototype object being built
         */
        $init : function (p) {
            var src = ariaWidgetsFormDropDownListTrait.prototype;
            for (var key in src) {
                if (src.hasOwnProperty(key) && !p.hasOwnProperty(key)) {
                    // copy methods which are not already on this object (this avoids copying $classpath and
                    // $destructor)
                    p[key] = src[key];
                }
            }
        },

        /**
         * Internal function to render the content of the dropdown div
         * @protected
         * @param {aria.templates.MarkupWriter} out Markup writer which should receive the content of the popup.
         * @param {Object} arg Optional parameters
         */
        _renderDropdownContent : function (out, options) {
            options = options || {};
            var cfg = this._cfg;
            if (!("defaultTemplate" in options)) {
                if (cfg.suggestionsTemplate) {
                    options.defaultTemplate = cfg.suggestionsTemplate;
                } else {
                    options.defaultTemplate = this.controller.getDefaultTemplate();
                }
            }
            if (!("minWidth" in options)) {
                var inputMarkupWidth = this._inputMarkupWidth;
                // set a minimum size for the autocomplete content
                inputMarkupWidth = (inputMarkupWidth < 150) ? 150 : inputMarkupWidth;
                options.minWidth = inputMarkupWidth + this._skinObj.offsetRight;
            }
            options.maxHeight = this._cfg.popupMaxHeight || 210;
            if (cfg.waiAria) {
                options.onchange = {
                    scope: this,
                    fn: this._updateAriaActiveDescendant,
                    args: options.onchange
                };
            }
            this.$DropDownListTrait._renderDropdownContent.call(this, out, options);
        },

        /**
         * Override $DropDownTextInput._reactToControllerReport
         * @protected
         * @param {aria.widgets.controllers.reports.DropDownControllerReport} report
         * @param {Object} arg Optional parameters
         */
        _reactToControllerReport : function (report, arg) {
            // To reset focus if there is no suggestion dropdown
            if (!this.controller._resetFocus) {
                this._keepFocus = false;
            }
            this.$DropDownTextInput._reactToControllerReport.call(this, report, arg);
        },

        /**
         * DOM callback function called when the focus is taken off the input. The onBlur event is available on the
         * input that sits inside a span.
         * @param {aria.DomEvent} event Blur event
         * @protected
         */
        _dom_onblur : function (event, avoidCallback) {
            this._keepFocus = false;
            this.$DropDownTextInput._dom_onblur.call(this, event, avoidCallback);
        },

        _dom_onkeydown : function (event) {
            // On Shift+F10, when AutoComplete has no expand button enabled, we don't open the dropdown, we only close it
            if (this._isShiftF10Pressed(event) && !this._cfg.expandButton && this._dropdownPopup == null) {
                return;
            }

            this.$DropDownTextInput._dom_onkeydown.apply(this, arguments);
        },

         /**
         * Internal method to handle the onkeyup event. This is called to set the value property in the data model
         * through the setProperty method that also handles all other widgets bound to this value.
         * @protected
         */
        _dom_onkeyup : function (event) {
            this.$DropDownTextInput._dom_onkeyup.call(this, event);
            if (event.keyCode == event.KC_ENTER) {
                this.checkValue();
            }
        },

        /**
         * React to a dropdown close. If the widget is using autofill we want to select the pre-selected value in the
         * datamodel and report it to the input field also when the user clicks away from the field instead of
         * navigating through TAB or selecting an item from the dropdown. This function is called any time we close the
         * dropdown, even when typing there are no results.
         * @param {Object} event Event that triggered this callback
         * @private
         */
        _beforeDropdownClose : function (event) {
            if (this._cfg.autoFill && event.domEvent) {
                // Closing the dropdown after typing is not a domEvent
                var value = this.controller.getDataModel().value;
                if (value != null) {
                    var report = this.controller.checkDropdownValue(value);
                    this._reactToControllerReport(report);
                }
            }
        },

        /**
         * Updates the aria-activedescendant attribute on the input DOM element.
         * This method supposes that the popup is open.
         * This method is registered as the onchange callback for the list widget,
         * if accessibility is enabled. It is also called from _afterDropdownOpen.
         */
        _updateAriaActiveDescendant : function(event, cb) {
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                var listWidget = this.controller.getListWidget();
                var ariaActiveDescendant = listWidget ? listWidget.getSelectedOptionDomId() : null;
                if (ariaActiveDescendant != null) {
                    field.setAttribute("aria-activedescendant", ariaActiveDescendant);
                } else {
                    field.removeAttribute("aria-activedescendant");
                }
            }
            if (cb) {
                this.$callback(cb, event);
            }
        },

        /**
         * Callback for the event onAfterOpen raised by the popup.
         * @override
         */
        _afterDropdownOpen : function () {
            this.$DropDownListTrait._afterDropdownOpen.apply(this, arguments);
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                var listWidget = this.controller.getListWidget();
                field.setAttribute("aria-owns", listWidget.getListDomId());
                var dropDownIcon = this._getDropdownIcon();
                if (dropDownIcon) {
                    dropDownIcon.setAttribute("aria-expanded", "true");
                }
                listWidget.afterWidgetLoaded({
                    fn: this._updateAriaActiveDescendant,
                    scope: this
                });
            }
        },
        /**
         * @override
         */
        _waiSuggestionsChanged : function () {
            var self = this;
            this.getTextInputField().removeAttribute("aria-activedescendant");
            setTimeout(function () {
                self._updateAriaActiveDescendant();
            }, 0);
            this.$DropDownListTrait._waiSuggestionsChanged.apply(this, arguments);
        },

        /**
         * Called after the dropdown is closed.
         * @override
         */
        _afterDropdownClose : function () {
            if (this._cfg.waiAria) {
                var field = this.getTextInputField();
                field.removeAttribute("aria-owns");

                var dropDownIcon = this._getDropdownIcon();
                if (dropDownIcon) {
                    dropDownIcon.setAttribute("aria-expanded", "false");
                }
           }
            this.$DropDownListTrait._afterDropdownClose.apply(this, arguments);
        },

        /**
         * Override _init function to add event listeners that cannot be delegated on IE
         * @method
         * @override
         */
        _init : ariaCoreBrowser.isOldIE ? function () {
            this.$DropDownTextInput._init.call(this);

            var field = this.getTextInputField();
            ariaUtilsEvent.addListener(field, "paste", {
                fn : this._dom_onpaste,
                scope : this
            });
            ariaUtilsEvent.addListener(field, "cut", {
                fn : this._dom_oncut,
                scope : this
            });
        } : function () {
            this.$DropDownTextInput._init.call(this);
        },

        /**
         * Remove event listeners added during init. This is done for IE in order to support copy/paste
         * @method
         * @protected
         */
        _removeEvents : ariaCoreBrowser.isOldIE ? function () {
            var field = this.getTextInputField();
            ariaUtilsEvent.removeListener(field, "paste");
            ariaUtilsEvent.removeListener(field, "cut");
        } : function () {},

        /**
         * Internal method to handle the paste event. This event is converted as a Ctrl+v and handled as a keydown in
         * order to set the datamodel and open the dropdown
         * @param {aria.DomEvent} event Event object
         * @protected
         */
        _dom_onpaste : function (event) {
            this.__propagateKeyDown(event);
        },

        /**
         * Internal method to handle the cut event. This event is converted as a Ctrl+x and handled as a keydown in
         * order to set the datamodel and close the dropdown.
         * @param {aria.DomEvent} event Event object
         * @protected
         */
        _dom_oncut : function (event) {
            this.__propagateKeyDown(event);
        },

        /**
         * Convert mouse event and browser copy/paste (contextual menu event) into a keydown event that can be handled
         * by the controller
         * @param {aria.DomEvent} event Event object, in IE this is an instance of HTMLElement
         * @private
         */
        __propagateKeyDown : function (event) {
            var toBeDisposed = false;
            if (!event.$DomEvent) {
                event = new aria.DomEvent(event);
                toBeDisposed = true;
            }

            event.isSpecialKey = true;
            event.ctrlKey = true;
            event.charCode = 0;

            this._dom_onkeydown.call(this, event);

            if (toBeDisposed) {
                event.$dispose();
            }
        },
        /**
         * Internal method called when one of the model property that the widget is bound to has changed Must be
         * overridden by sub-classes defining bindable properties
         * @param {String} propertyName the property name
         * @param {Object} newValue the new value
         * @param {Object} oldValue the old property value
         * @protected
         */
        _onBoundPropertyChange : function (propertyName, newValue, oldValue) {
            if (propertyName != "popupOpen" || this._cfg.expandButton) {
                this.$DropDownTextInput._onBoundPropertyChange.apply(this, arguments);
            }
        },

        /**
         * Initialization method called by the delegate engine when the DOM is loaded
         */
        initWidget : function () {
            if (!this._cfg.expandButton && this._cfg.popupOpen) {
                this._cfg.popupOpen = false;
            }
            this.$DropDownTextInput.initWidget.call(this);
        }
    }
});
<|endoftext|>


<commit_before>angular.module('proximate.services', [])

.factory('PubNub', function(pubNubKeys) {
  var pubNub = PUBNUB.init({
    publish_key: pubNubKeys.pub,
    subscribe_key: pubNubKeys.sub
  });

  var subscribe = function(channel, callback) {
    pubNub.subscribe({
      channel: channel,
      callback: callback
    });
  };

  var publish = function(channel, message) {
    var info = {
      channel: channel,
      message: message
    };

    pubNub.publish(info);
  };

  return {
    subscribe: subscribe,
    publish: publish
  };

})

.factory('Participant', function($http, $rootScope) {

  var getParticipantInfoFromId = function(participantId) {
    var url = '/api/participants/' + participantId;
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting participant information');
    });
  };

  var getHistoryByParticipantId = function(participantId) {
    var url = '/api/participants/' + participantId + '/history';
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting participant history');
    });
  };

  return {
    getParticipantInfoFromId: getParticipantInfoFromId,
    getHistoryByParticipantId: getHistoryByParticipantId
  };
})

.factory('Populate', function($http) {

  var adminId = 1;
  // get event participants for a given eventID
  var getParticipants = function(eventID) {
    var url = 'api/events/' + eventID + '/participants';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting event participants');
    });
  };

  // get current event ID
  var getCurrentEvent = function(adminId) {
    var url = '/api/admins/' + adminId + '/events/current';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting current event');
    });
  };

  var getAdminName = function(adminId) {
    var url = '/api/admins/' + adminId;
    return $http({
      method: 'GET',
      url: url,
    });
  };

  var getEventsByAdminId = function(adminId) {
    var url = 'api/admins/' + adminId + '/events';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting events');
    });
  };

  var getBeaconsByAdminId = function(adminId) {
    var url = '/api/admins/' + adminId + '/beacons';
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting beacons');
    });
  };

  var postNewBeacon = function(adminId, identifier, uuid, major, minor) {
    return $http({
      method: 'POST',
      url: '/api/beacon/upsert',
      data: {
        adminId: adminId,
        identifier: identifier,
        uuid: uuid,
        major: major,
        minor: minor
      },
    })
    .then(function(res) {
      if (res.status === 201) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error adding new beacon');
    });
  };

  var updateParticipantStatus = function(participantId, eventId, status) {
    return $http({
      method: 'Post',
      url: '/api/participant/updateStatus',
      data: {
        participantId: participantId,
        eventId: eventId,
        status: status
      },
    })
    .then(function(res) {
      if (res.status === 201) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error updating participant status');
    });
  };

  return {
    adminId: adminId,
    getParticipants: getParticipants,
    getCurrentEvent: getCurrentEvent,
    getAdminName: getAdminName,
    getEventsByAdminId: getEventsByAdminId,
    getBeaconsByAdminId: getBeaconsByAdminId,
    postNewBeacon: postNewBeacon,
    updateParticipantStatus: updateParticipantStatus
  };

})

.filter('fromNow', function($rootScope, $scope) {
  return function(startTime, endTime) {
    $scope.timeDiffLessThanHourBefore = false;
    $scope.timeDiffMoreThanHourBefore = false;
    $scope.timeDiffAfter = false;
    a = moment(startTime);
    b = moment(endTime);
    if (Math.abs(a.diff(b, 'minutes')) <= 60 && a.diff(b, 'minutes') > 0) {
      $scope.timeDiffLessThanHourBefore = true;
    } else if (Math.abs(a.diff(b, 'minutes')) > 60 && a.diff(b, 'minutes') > 0) {
      $scope.timeDiffMoreThanHourBefore = true;
    } else {
      $scope.timeDiffAfter = true;
    }
    return moment.duration(a - b).humanize(true);
  };
})

.filter('CurrentEvents', function($rootScope) {
  return function(events, current) {
    var filteredResults = [];
    var now = moment();
    if (events) {
      events.forEach(function(event) {
        if (current) {
          if (moment(event.start_time).diff(now) < 0) {
            return;
          }
        } else {
          if (moment(event.start_time).diff(now) >= 0) {
            return;
          }
        }
        filteredResults.push(event);
      });
    }
    return filteredResults;
  };
})

.filter('removeArrivedParticipants', function($rootScope) {
  return function(participants) {
    var filteredResults = [];
    if (participants) {
      participants.forEach(function(participant) {
        if (participant._pivot_status === 'ontime') {
          return;
        }
        filteredResults.push(participant);
      });
    }
    return filteredResults;
  };
});
<commit_msg>Fixed projector view<commit_after>angular.module('proximate.services', [])

.factory('PubNub', function(pubNubKeys) {
  var pubNub = PUBNUB.init({
    publish_key: pubNubKeys.pub,
    subscribe_key: pubNubKeys.sub
  });

  var subscribe = function(channel, callback) {
    pubNub.subscribe({
      channel: channel,
      callback: callback
    });
  };

  var publish = function(channel, message) {
    var info = {
      channel: channel,
      message: message
    };

    pubNub.publish(info);
  };

  return {
    subscribe: subscribe,
    publish: publish
  };

})

.factory('Participant', function($http, $rootScope) {

  var getParticipantInfoFromId = function(participantId) {
    var url = '/api/participants/' + participantId;
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting participant information');
    });
  };

  var getHistoryByParticipantId = function(participantId) {
    var url = '/api/participants/' + participantId + '/history';
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting participant history');
    });
  };

  return {
    getParticipantInfoFromId: getParticipantInfoFromId,
    getHistoryByParticipantId: getHistoryByParticipantId
  };
})

.factory('Populate', function($http) {

  var adminId = 1;
  // get event participants for a given eventID
  var getParticipants = function(eventID) {
    var url = 'api/events/' + eventID + '/participants';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting event participants');
    });
  };

  // get current event ID
  var getCurrentEvent = function(adminId) {
    var url = '/api/admins/' + adminId + '/events/current';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting current event');
    });
  };

  var getAdminName = function(adminId) {
    var url = '/api/admins/' + adminId;
    return $http({
      method: 'GET',
      url: url,
    });
  };

  var getEventsByAdminId = function(adminId) {
    var url = 'api/admins/' + adminId + '/events';
    return $http({
      method: 'GET',
      url: url,
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting events');
    });
  };

  var getBeaconsByAdminId = function(adminId) {
    var url = '/api/admins/' + adminId + '/beacons';
    return $http({
      method: 'GET',
      url: url
    })
    .then(function(res) {
      if (res.status === 200) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error getting beacons');
    });
  };

  var postNewBeacon = function(adminId, identifier, uuid, major, minor) {
    return $http({
      method: 'POST',
      url: '/api/beacon/upsert',
      data: {
        adminId: adminId,
        identifier: identifier,
        uuid: uuid,
        major: major,
        minor: minor
      },
    })
    .then(function(res) {
      if (res.status === 201) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error adding new beacon');
    });
  };

  var updateParticipantStatus = function(participantId, eventId, status) {
    return $http({
      method: 'Post',
      url: '/api/participant/updateStatus',
      data: {
        participantId: participantId,
        eventId: eventId,
        status: status
      },
    })
    .then(function(res) {
      if (res.status === 201) {
        return res;
      }
    })
    .catch(function(error) {
      console.log('Error updating participant status');
    });
  };

  return {
    adminId: adminId,
    getParticipants: getParticipants,
    getCurrentEvent: getCurrentEvent,
    getAdminName: getAdminName,
    getEventsByAdminId: getEventsByAdminId,
    getBeaconsByAdminId: getBeaconsByAdminId,
    postNewBeacon: postNewBeacon,
    updateParticipantStatus: updateParticipantStatus
  };

})

.filter('fromNow', function($rootScope) {
  return function(startTime, endTime) {
    $rootScope.timeDiffLessThanHourBefore = false;
    $rootScope.timeDiffMoreThanHourBefore = false;
    $rootScope.timeDiffAfter = false;
    a = moment(startTime);
    b = moment(endTime);
    if (Math.abs(a.diff(b, 'minutes')) <= 60 && a.diff(b, 'minutes') > 0) {
      $rootScope.timeDiffLessThanHourBefore = true;
    } else if (Math.abs(a.diff(b, 'minutes')) > 60 && a.diff(b, 'minutes') > 0) {
      $rootScope.timeDiffMoreThanHourBefore = true;
    } else {
      $rootScope.timeDiffAfter = true;
    }
    return moment.duration(a - b).humanize(true);
  };
})

.filter('CurrentEvents', function($rootScope) {
  return function(events, current) {
    var filteredResults = [];
    var now = moment();
    if (events) {
      events.forEach(function(event) {
        if (current) {
          if (moment(event.start_time).diff(now) < 0) {
            return;
          }
        } else {
          if (moment(event.start_time).diff(now) >= 0) {
            return;
          }
        }
        filteredResults.push(event);
      });
    }
    return filteredResults;
  };
})

.filter('removeArrivedParticipants', function($rootScope) {
  return function(participants) {
    var filteredResults = [];
    if (participants) {
      participants.forEach(function(participant) {
        if (participant._pivot_status === 'ontime') {
          return;
        }
        filteredResults.push(participant);
      });
    }
    return filteredResults;
  };
});
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */
import { validationStrings } from '../localization';
export class DemoSiteRequest {
  async createActivationURL(username, email, password, repeatPassword) {
    // Client side validation
    if (email.length === 0) throw new Error(validationStrings.email.required);
    if (!this.validateEmail(email)) throw new Error(validationStrings.email.valid);
    if (username.length === 0) throw new Error(validationStrings.username.required);
    if (password.length === 0) throw new Error(validationStrings.password.required);
    if (repeatPassword.length === 0) throw new Error(validationStrings.repeatPassword.required);
    if (password !== repeatPassword) throw new Error(validationStrings.password.matchRepeat);
    if (this.textLengthInvalid(password)) throw new Error(validationStrings.password.lengthInvalid);
    if (this.textContainsSpaces(password)) {
      throw new Error(validationStrings.password.containsSpaces);
    }

    // Need proper demo server URL in 4D to work
    // 83.96.252.40 2048
    const domainName = 'https://demo.msupply.org';
    const URL = `${domainName}/api/v4/mobile/requestDemo`;

    let responseJson;
    try {
      const response = await fetch(URL, {
        method: 'POST',
        body: JSON.stringify({ username, email, password }),
      });
      responseJson = await response.json();

      if (responseJson.error) {
        throw new Error(responseJson.error);
      }
      return responseJson;
    } catch (error) {
      // Pass error up
      throw error;
    }
  }

  // Validation methods
  // TODO: Could be extracted to a helper file to be used elsewhere
  validateEmail(text) {
    const reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    return reg.test(text);
  }

  textContainsSpaces(text) {
    const reg = /^\S*$/;
    return !(reg.test(text));
  }

  textLengthInvalid(text, lessThen = 8, greaterThen = 32) {
    return text.length < lessThen || text.length > greaterThen;
  }
}
<commit_msg>Move demo site url to a constant outside the class<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */
import { validationStrings } from '../localization';

const DEMO_SITE_URL = 'https://demo.msupply.org/api/v4/mobile/requestDemo';
export class DemoSiteRequest {
  async createActivationURL(username, email, password, repeatPassword) {
    // Client side validation
    if (email.length === 0) throw new Error(validationStrings.email.required);
    if (!this.validateEmail(email)) throw new Error(validationStrings.email.valid);
    if (username.length === 0) throw new Error(validationStrings.username.required);
    if (password.length === 0) throw new Error(validationStrings.password.required);
    if (repeatPassword.length === 0) throw new Error(validationStrings.repeatPassword.required);
    if (password !== repeatPassword) throw new Error(validationStrings.password.matchRepeat);
    if (this.textLengthInvalid(password)) throw new Error(validationStrings.password.lengthInvalid);
    if (this.textContainsSpaces(password)) {
      throw new Error(validationStrings.password.containsSpaces);
    }

    let responseJson;
    try {
      const response = await fetch(DEMO_SITE_URL, {
        method: 'POST',
        body: JSON.stringify({ username, email, password }),
      });
      responseJson = await response.json();

      if (responseJson.error) {
        throw new Error(responseJson.error);
      }
      return responseJson;
    } catch (error) {
      // Pass error up
      throw error;
    }
  }

  // Validation methods
  // TODO: Could be extracted to a helper file to be used elsewhere
  validateEmail(text) {
    const reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    return reg.test(text);
  }

  textContainsSpaces(text) {
    const reg = /^\S*$/;
    return !(reg.test(text));
  }

  textLengthInvalid(text, lessThen = 8, greaterThen = 32) {
    return text.length < lessThen || text.length > greaterThen;
  }
}
<|endoftext|>


<commit_before>
"use strict";

import {Model} from '../classes/Model';
import {Collection} from '../classes/Collection';

let http;
let cache;

function appendTransform(transform) {
    let defaults = http.defaults.transformResponse;
    defaults = angular.isArray(defaults) ? defaults : [defaults];
    return defaults.concat(transform);
};

class Manager {

    constructor($http, $cacheFactory) {
        http = $http;
        if (cache === undefined) {
            cache = $cacheFactory('monad-managers');
        }
        // Set this per-manager where needed:
        this.model = Model;
        this.collection = new Collection();
        this.filter = {};
    }

    applyFilter(params) {
        if (this.filter) {
            for (let p in this.filter) {
                if (this.filter[p] !== false) {
                    params[p] = this.filter[p];
                }
            }
        }
        return params;
    }

    get count() {
        return 0;
    }

    list(url) {
        return http({
            url,
            method: 'GET',
            transformResponse: appendTransform(values => {
                while (this.collection.length) {
                    this.collection.pop();
                }
                values.map(value => this.collection.push((new this.model()).$load(value)));
                return this.collection;
            }),
            cache
        });
    }

    find(url) {
        return http({
            url,
            method: 'GET',
            transformResponse: appendTransform(item => (new this.model()).$load(item)),
            cache
        });
    }

    save(model) {
        if (model.$new) {
            return this.create(model).success(() => model.$initial = model.$data);
        } else if (model.$deleted) {
            return this['delete'](model).success(() => {
                model.$data = {};
                model.$initial = undefined;
            });
        } else if (model.$dirty) {
            return this.update(model).success(() => model.$initial = model.$data);
        }
        return {};
    }

    /**
     * API interface. These should be overridden by a custom implementation,
     * since we have no way to guesstimate how your API works. Hence, these
     * throw an error as a friendly reminder :)
     *
     * The actual implementations should return promises.
     */
    create(model) {
        throw "Manager.create must use a custom implementation.";
    }

    update(model) {
        throw "Manager.update must use a custom implementation.";
    }

    ['delete'](model) {
        throw "Manager.delete must use a custom implementation.";
    }

    get http() {
        return http;
    }

};

Manager.$inject = ['$http', '$cacheFactory'];

export {Manager};

<commit_msg>expose cache so custom managers can also use it<commit_after>
"use strict";

import {Model} from '../classes/Model';
import {Collection} from '../classes/Collection';

let http;
let cache;

function appendTransform(transform) {
    let defaults = http.defaults.transformResponse;
    defaults = angular.isArray(defaults) ? defaults : [defaults];
    return defaults.concat(transform);
};

class Manager {

    constructor($http, $cacheFactory) {
        http = $http;
        if (cache === undefined) {
            cache = $cacheFactory('monad-managers');
        }
        // Set this per-manager where needed:
        this.model = Model;
        this.collection = new Collection();
        this.filter = {};
    }

    applyFilter(params) {
        if (this.filter) {
            for (let p in this.filter) {
                if (this.filter[p] !== false) {
                    params[p] = this.filter[p];
                }
            }
        }
        return params;
    }

    get cache() {
        return cache;
    }

    get count() {
        return 0;
    }

    list(url) {
        return http({
            url,
            method: 'GET',
            transformResponse: appendTransform(values => {
                while (this.collection.length) {
                    this.collection.pop();
                }
                values.map(value => this.collection.push((new this.model()).$load(value)));
                return this.collection;
            }),
            cache
        });
    }

    find(url) {
        return http({
            url,
            method: 'GET',
            transformResponse: appendTransform(item => (new this.model()).$load(item)),
            cache
        });
    }

    save(model) {
        if (model.$new) {
            return this.create(model).success(() => model.$initial = model.$data);
        } else if (model.$deleted) {
            return this['delete'](model).success(() => {
                model.$data = {};
                model.$initial = undefined;
            });
        } else if (model.$dirty) {
            return this.update(model).success(() => model.$initial = model.$data);
        }
        return {};
    }

    /**
     * API interface. These should be overridden by a custom implementation,
     * since we have no way to guesstimate how your API works. Hence, these
     * throw an error as a friendly reminder :)
     *
     * The actual implementations should return promises.
     */
    create(model) {
        throw "Manager.create must use a custom implementation.";
    }

    update(model) {
        throw "Manager.update must use a custom implementation.";
    }

    ['delete'](model) {
        throw "Manager.delete must use a custom implementation.";
    }

    get http() {
        return http;
    }

};

Manager.$inject = ['$http', '$cacheFactory'];

export {Manager};

<|endoftext|>


<commit_before>angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI'])
.factory('spyAPI', function($rootScope, $timeout, $window, $document, scrollContainerAPI, duScrollGreedy, duScrollSpyWait, duScrollBottomSpy, duScrollActiveClass) {
  'use strict';

  var createScrollHandler = function(context) {
    var timer = false, queued = false;
    var handler = function() {
      queued = false;
      var container = context.container,
          containerEl = container[0],
          containerOffset = 0,
          bottomReached;

      if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) {
        containerOffset = containerEl.getBoundingClientRect().top;
        bottomReached = Math.round(containerEl.scrollTop + containerEl.clientHeight) >= containerEl.scrollHeight;
      } else {
        bottomReached = Math.round($window.pageYOffset + $window.innerHeight) >= $document[0].body.scrollHeight;
      }
      var compareProperty = (duScrollBottomSpy && bottomReached ? 'bottom' : 'top');

      var i, currentlyActive, toBeActive, spies, spy, pos;
      spies = context.spies;
      currentlyActive = context.currentlyActive;
      toBeActive = undefined;

      for(i = 0; i < spies.length; i++) {
        spy = spies[i];
        pos = spy.getTargetPosition();
        if (!pos) continue;

        if((duScrollBottomSpy && bottomReached) || (pos.top + spy.offset - containerOffset < 20 && (duScrollGreedy || pos.top*-1 + containerOffset) < pos.height)) {
          //Find the one closest the viewport top or the page bottom if it's reached
          if(!toBeActive || toBeActive[compareProperty] < pos[compareProperty]) {
            toBeActive = {
              spy: spy
            };
            toBeActive[compareProperty] = pos[compareProperty];
          }
        }
      }

      if(toBeActive) {
        toBeActive = toBeActive.spy;
      }
      if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return;
      if(currentlyActive) {
        currentlyActive.$element.removeClass(duScrollActiveClass);
        $rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
      }
      if(toBeActive) {
        toBeActive.$element.addClass(duScrollActiveClass);
        $rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
      }
      context.currentlyActive = toBeActive;
    };

    if(!duScrollSpyWait) {
      return handler;
    }

    //Debounce for potential performance savings
    return function() {
      if(!timer) {
        handler();
        timer = $timeout(function() {
          timer = false;
          if(queued) {
            handler();
          }
        }, duScrollSpyWait, false);
      } else {
        queued = true;
      }
    };
  };

  var contexts = {};

  var createContext = function($scope) {
    var id = $scope.$id;
    var context = {
      spies: []
    };

    context.handler = createScrollHandler(context);
    contexts[id] = context;

    $scope.$on('$destroy', function() {
      destroyContext($scope);
    });

    return id;
  };

  var destroyContext = function($scope) {
    var id = $scope.$id;
    var context = contexts[id], container = context.container;
    if(container) {
      container.off('scroll', context.handler);
    }
    delete contexts[id];
  };

  var defaultContextId = createContext($rootScope);

  var getContextForScope = function(scope) {
    if(contexts[scope.$id]) {
      return contexts[scope.$id];
    }
    if(scope.$parent) {
      return getContextForScope(scope.$parent);
    }
    return contexts[defaultContextId];
  };

  var getContextForSpy = function(spy) {
    var context, contextId, scope = spy.$scope;
    if(scope) {
      return getContextForScope(scope);
    }
    //No scope, most likely destroyed
    for(contextId in contexts) {
      context = contexts[contextId];
      if(context.spies.indexOf(spy) !== -1) {
        return context;
      }
    }
  };

  var isElementInDocument = function(element) {
    while (element.parentNode) {
      element = element.parentNode;
      if (element === document) {
        return true;
      }
    }
    return false;
  };

  var addSpy = function(spy) {
    var context = getContextForSpy(spy);
    if (!context) return;
    context.spies.push(spy);
    if (!context.container || !isElementInDocument(context.container)) {
      if(context.container) {
        context.container.off('scroll', context.handler);
      }
      context.container = scrollContainerAPI.getContainer(spy.$scope);
      context.container.on('scroll', context.handler).triggerHandler('scroll');
    }
  };

  var removeSpy = function(spy) {
    var context = getContextForSpy(spy);
    if(spy === context.currentlyActive) {
      context.currentlyActive = null;
    }
    var i = context.spies.indexOf(spy);
    if(i !== -1) {
      context.spies.splice(i, 1);
    }
		spy.$element = null;
  };

  return {
    addSpy: addSpy,
    removeSpy: removeSpy,
    createContext: createContext,
    destroyContext: destroyContext,
    getContextForScope: getContextForScope
  };
});
<commit_msg>Trigger becameInactive event when active spy destroyed.<commit_after>angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI'])
.factory('spyAPI', function($rootScope, $timeout, $window, $document, scrollContainerAPI, duScrollGreedy, duScrollSpyWait, duScrollBottomSpy, duScrollActiveClass) {
  'use strict';

  var createScrollHandler = function(context) {
    var timer = false, queued = false;
    var handler = function() {
      queued = false;
      var container = context.container,
          containerEl = container[0],
          containerOffset = 0,
          bottomReached;

      if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) {
        containerOffset = containerEl.getBoundingClientRect().top;
        bottomReached = Math.round(containerEl.scrollTop + containerEl.clientHeight) >= containerEl.scrollHeight;
      } else {
        bottomReached = Math.round($window.pageYOffset + $window.innerHeight) >= $document[0].body.scrollHeight;
      }
      var compareProperty = (duScrollBottomSpy && bottomReached ? 'bottom' : 'top');

      var i, currentlyActive, toBeActive, spies, spy, pos;
      spies = context.spies;
      currentlyActive = context.currentlyActive;
      toBeActive = undefined;

      for(i = 0; i < spies.length; i++) {
        spy = spies[i];
        pos = spy.getTargetPosition();
        if (!pos) continue;

        if((duScrollBottomSpy && bottomReached) || (pos.top + spy.offset - containerOffset < 20 && (duScrollGreedy || pos.top*-1 + containerOffset) < pos.height)) {
          //Find the one closest the viewport top or the page bottom if it's reached
          if(!toBeActive || toBeActive[compareProperty] < pos[compareProperty]) {
            toBeActive = {
              spy: spy
            };
            toBeActive[compareProperty] = pos[compareProperty];
          }
        }
      }

      if(toBeActive) {
        toBeActive = toBeActive.spy;
      }
      if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return;
      if(currentlyActive) {
        currentlyActive.$element.removeClass(duScrollActiveClass);
        $rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
      }
      if(toBeActive) {
        toBeActive.$element.addClass(duScrollActiveClass);
        $rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
      }
      context.currentlyActive = toBeActive;
    };

    if(!duScrollSpyWait) {
      return handler;
    }

    //Debounce for potential performance savings
    return function() {
      if(!timer) {
        handler();
        timer = $timeout(function() {
          timer = false;
          if(queued) {
            handler();
          }
        }, duScrollSpyWait, false);
      } else {
        queued = true;
      }
    };
  };

  var contexts = {};

  var createContext = function($scope) {
    var id = $scope.$id;
    var context = {
      spies: []
    };

    context.handler = createScrollHandler(context);
    contexts[id] = context;

    $scope.$on('$destroy', function() {
      destroyContext($scope);
    });

    return id;
  };

  var destroyContext = function($scope) {
    var id = $scope.$id;
    var context = contexts[id], container = context.container;
    if(container) {
      container.off('scroll', context.handler);
    }
    delete contexts[id];
  };

  var defaultContextId = createContext($rootScope);

  var getContextForScope = function(scope) {
    if(contexts[scope.$id]) {
      return contexts[scope.$id];
    }
    if(scope.$parent) {
      return getContextForScope(scope.$parent);
    }
    return contexts[defaultContextId];
  };

  var getContextForSpy = function(spy) {
    var context, contextId, scope = spy.$scope;
    if(scope) {
      return getContextForScope(scope);
    }
    //No scope, most likely destroyed
    for(contextId in contexts) {
      context = contexts[contextId];
      if(context.spies.indexOf(spy) !== -1) {
        return context;
      }
    }
  };

  var isElementInDocument = function(element) {
    while (element.parentNode) {
      element = element.parentNode;
      if (element === document) {
        return true;
      }
    }
    return false;
  };

  var addSpy = function(spy) {
    var context = getContextForSpy(spy);
    if (!context) return;
    context.spies.push(spy);
    if (!context.container || !isElementInDocument(context.container)) {
      if(context.container) {
        context.container.off('scroll', context.handler);
      }
      context.container = scrollContainerAPI.getContainer(spy.$scope);
      context.container.on('scroll', context.handler).triggerHandler('scroll');
    }
  };

  var removeSpy = function(spy) {
    var context = getContextForSpy(spy);
    if(spy === context.currentlyActive) {
      $rootScope.$broadcast('duScrollspy:becameInactive', context.currentlyActive.$element);
      context.currentlyActive = null;
    }
    var i = context.spies.indexOf(spy);
    if(i !== -1) {
      context.spies.splice(i, 1);
    }
		spy.$element = null;
  };

  return {
    addSpy: addSpy,
    removeSpy: removeSpy,
    createContext: createContext,
    destroyContext: destroyContext,
    getContextForScope: getContextForScope
  };
});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash')
var React = require('react')
var Fluxxor = require('fluxxor')
var FluxChildMixin = Fluxxor.FluxChildMixin(React)

var Course = require('./course')
var Semester = require('./semester')

var findFirstAvailableSemester = require('../helpers/findFirstAvailableSemester')
var calculateNextScheduleId = require('../helpers/calculateNextScheduleId')

var isCurrentYearSchedule = _.curry(function(year, schedule) {
	return (schedule.year === year)
})

var Year = React.createClass({
	mixins: [FluxChildMixin],
	canAddSemester: function() {
		return findFirstAvailableSemester(this.props.schedules, this.props.year) <= 5
	},
	addSemester: function() {
		var nextAvailableSemester = findFirstAvailableSemester(this.props.schedules, this.props.year)

		this.getFlux().actions.createSchedule(this.props.studentId, {
			year: this.props.year, semester: nextAvailableSemester,
			sequence: 1, active: true,
		})
	},
	removeYear: function() {
		var currentYearSchedules = _.filter(this.props.schedules, isCurrentYearSchedule(this.props.year))
		console.log('called removeYear', currentYearSchedules)
		var scheduleIds = _.pluck(currentYearSchedules, 'id')
		console.log('removing', scheduleIds, 'from', this.props.studentId)
		this.getFlux().actions.destroyMultipleSchedules(this.props.studentId, scheduleIds)
	},
	render: function() {
		var schedules = _.filter(this.props.schedules, {year: this.props.year})

		var terms = _.map(_.groupBy(schedules, 'semester'), function(schedule, semester) {
			semester = parseInt(semester, 10)
			return Semester({
				key: semester,
				ref: semester,
				semester: parseInt(semester, 10),
				year: this.props.year,
				schedules: this.props.schedules,
				studentId: this.props.studentId,
			})
		}, this)

		return React.DOM.div({className: 'year'},
			React.DOM.header({className: 'year-title'},
				React.DOM.h1(null, this.props.year + ' + ' + (this.props.year + 1)),
				React.DOM.button({
					className: 'remove-year',
					title: 'Remove the year ' + this.props.year,
					onClick: this.removeYear,
				})
			),
			React.DOM.div({className: 'semester-list'}, terms),
			React.DOM.button({
				className: 'add-semester',
				title: 'Add Semester',
				disabled: !this.canAddSemester(),
				onClick: this.addSemester,
			})
		)
	}
})

module.exports = Year
<commit_msg>Add spaces to Year<commit_after>'use strict';

var _ = require('lodash')
var React = require('react')
var Fluxxor = require('fluxxor')
var FluxChildMixin = Fluxxor.FluxChildMixin(React)

var Course = require('./course')
var Semester = require('./semester')

var findFirstAvailableSemester = require('../helpers/findFirstAvailableSemester')
var calculateNextScheduleId = require('../helpers/calculateNextScheduleId')

var isCurrentYearSchedule = _.curry(function(year, schedule) {
	return (schedule.year === year)
})

var Year = React.createClass({
	mixins: [FluxChildMixin],

	canAddSemester: function() {
		return findFirstAvailableSemester(this.props.schedules, this.props.year) <= 5
	},

	addSemester: function() {
		var nextAvailableSemester = findFirstAvailableSemester(this.props.schedules, this.props.year)

		this.getFlux().actions.createSchedule(this.props.studentId, {
			year: this.props.year, semester: nextAvailableSemester,
			sequence: 1, active: true,
		})
	},

	removeYear: function() {
		var currentYearSchedules = _.filter(this.props.schedules, isCurrentYearSchedule(this.props.year))
		console.log('called removeYear', currentYearSchedules)
		var scheduleIds = _.pluck(currentYearSchedules, 'id')
		console.log('removing', scheduleIds, 'from', this.props.studentId)
		this.getFlux().actions.destroyMultipleSchedules(this.props.studentId, scheduleIds)
	},

	render: function() {
		var schedules = _.filter(this.props.schedules, {year: this.props.year})

		var terms = _.map(_.groupBy(schedules, 'semester'), function(schedule, semester) {
			semester = parseInt(semester, 10)
			return Semester({
				key: semester,
				ref: semester,
				semester: parseInt(semester, 10),
				year: this.props.year,
				schedules: this.props.schedules,
				studentId: this.props.studentId,
			})
		}, this)

		return React.DOM.div({className: 'year'},
			React.DOM.header({className: 'year-title'},
				React.DOM.h1(null, this.props.year + ' + ' + (this.props.year + 1)),
				React.DOM.button({
					className: 'remove-year',
					title: 'Remove the year ' + this.props.year,
					onClick: this.removeYear,
				})
			),
			React.DOM.div({className: 'semester-list'}, terms),
			React.DOM.button({
				className: 'add-semester',
				title: 'Add Semester',
				disabled: !this.canAddSemester(),
				onClick: this.addSemester,
			})
		)
	}
})

module.exports = Year
<|endoftext|>


<commit_before>import { AbstractFractal } from '../abstract'

export class BifurcationFractal extends AbstractFractal {

  constructor ({
    x, // (r)
    y = 0.5, // (p)
    minX,
    minY,
    maxX,
    maxY,
    width,
    height,
    depth
  }) {
    super(...arguments)

    this.x = x
    this.y = y
    this.minX = minX
    this.minY = minY
    this.maxX = maxX
    this.maxY = maxY
    this.cursor = 0
  }

  setup () {
    this.clear()

    this.deltas = {
      x: (this.maxX - this.minX) / this.width,
      y: (this.maxY - this.minY) / this.height
    }
  }

  moveUnit (cursor) {
    const scale = this.minX + this.deltas.x * cursor

    this.y = this.y * scale * (1 - this.y)
  }

  renderUnit (cursor) {
    // waits to give the function time to converge
    if (cursor > 100) {
      const x = this.cursor
      const y = this.height - (this.y - this.minY) / this.deltas.y

      this.context.fillRect(x, y, 1, 1)
    }
  }

  // TODO: clean up this.cursor vs. cursor, highly confusing
  iterate (cursor = this.cursor) {
    this.y = 0.5

    for (let at = 0; at < 200; at += 1) {
      this.moveUnit(this.cursor)
      this.renderUnit(at)
    }

    if (++this.cursor < this.width) {
      this.iterate(cursor)
    }
  }

}
<commit_msg>:scroll: :truck: Renamed cursor param to axiom in sets/bifurcation/iterate method<commit_after>import { AbstractFractal } from '../abstract'

export class BifurcationFractal extends AbstractFractal {

  constructor ({
    x, // (r)
    y = 0.5, // (p)
    minX,
    minY,
    maxX,
    maxY,
    width,
    height,
    depth
  }) {
    super(...arguments)

    this.x = x
    this.y = y
    this.minX = minX
    this.minY = minY
    this.maxX = maxX
    this.maxY = maxY
    this.cursor = 0
  }

  setup () {
    this.clear()

    this.deltas = {
      x: (this.maxX - this.minX) / this.width,
      y: (this.maxY - this.minY) / this.height
    }
  }

  moveUnit (cursor) {
    const scale = this.minX + this.deltas.x * cursor

    this.y = this.y * scale * (1 - this.y)
  }

  renderUnit (cursor) {
    // waits to give the function time to converge
    if (cursor > 100) {
      const x = this.cursor
      const y = this.height - (this.y - this.minY) / this.deltas.y

      this.context.fillRect(x, y, 1, 1)
    }
  }

  iterate (axiom = this.cursor) {
    this.y = 0.5

    for (let at = 0; at < 200; at += 1) {
      this.moveUnit(this.cursor)
      this.renderUnit(at)
    }

    if (++this.cursor < this.width) {
      this.iterate(axiom)
    }
  }

}
<|endoftext|>


<commit_before>// =============================================
// Clean `gulp clean
// destroys the build directory
// =============================================

module.exports = function (nodeModule, project) {
    return function (callback) {
        //return nodeModule.del([project.dist], callback);
    };
};<commit_msg>comment out return to stop crashing out.<commit_after>// =============================================
// Clean `gulp clean
// destroys the build directory
// =============================================

module.exports = function (nodeModule, project) {
    //return function (callback) {
    //    return nodeModule.del([project.dist], callback);
    //};
};<|endoftext|>


<commit_before>window.constants = {
  verticalSpacing: 20, // there is css that needs to match this
  readModeOffset: 246,
  minPageWidth: 1024,
  selectOffset: - 210
};

if(Meteor.Device.isPhone()){
  window.constants.readModeOffset = window.constants.readModeOffset + 92
}

window.getVerticalLeft = function() {
  return Meteor.Device.isPhone() ? (Session.get('windowWidth') > 340 ? 30 : 20) : (Session.get('windowWidth') / 2 - Session.get('cardWidth') - Session.get('separation'));
};

window.getHorizontalLeft = function() {
  var currentPos, currentHorizontal, cardWidth, numCards, left, offset, pageWidth, verticalRight, addContextBlockWidth, cardSeparation;

  // Variable definitions (width of page, width of card, offset of cards)
  cardWidth = Session.get("cardWidth");
  cardSeparation = Session.get("separation");
  addContextBlockWidth = 75;
  verticalLeft = Session.get("verticalLeft");
  verticalRight = verticalLeft + cardWidth;

  // Offset of first card, different on create page because of (+) button
  if (Session.get("read")) {
    offset = 0;
  } else {
    offset = addContextBlockWidth + cardSeparation;
  }
  if (Session.get("addingContext")) {
    offset += cardWidth + cardSeparation;
  }

  if(this.verticalIndex === Session.get("currentY")){
    currentHorizontal = Session.get("horizontalSectionsMap")[Session.get("currentY")];
    if (!currentHorizontal) {
      return
    }
    currentPos = this.index - Session.get("currentX");
    numCards = currentHorizontal.horizontal.length;
  } else { // card is from another row
    currentPos = this.index - getXByYId(this.verticalId);
    numCards = Session.get("horizontalSectionsMap")[this.verticalIndex].horizontal.length;
  }

  if (numCards === 1){
    return verticalRight + offset + cardSeparation;
  }

  if (Session.get("wrap")[this.verticalId] || numCards === 2) { // wrapping (and always position as if wrapping when two cards)

    if (currentPos < 0) { // makes the first card appear at the end of the last card
      currentPos = numCards + currentPos;
    }

    // Default context positioning (all to the right of vertical narrative)
    left = (currentPos * (cardWidth + cardSeparation)) + (verticalRight + cardSeparation + offset);

    // Last card positioning if number of cards is greater than 3
    if (numCards >= 3) {
      if (currentPos === numCards - 1) {
        left = verticalLeft - cardWidth - cardSeparation;
      }
    }
    return left;
  } else { // not wrapping

    if (currentPos === numCards - 1 || currentPos < -1) { // this makes cards appear on the right when they run off the left
      currentPos = numCards + currentPos;
    }

    if (currentPos >= 0) {
      left = (currentPos * (cardWidth + cardSeparation)) + (verticalRight + cardSeparation + offset);
    } else {

      left = ((currentPos + 1) * (cardWidth + cardSeparation)) + (verticalLeft - cardWidth - cardSeparation);
    }

    return left;
  }
};

window.getVerticalHeights = function() {
  var sum, verticalHeights;
  var offset = Session.get('read') ? constants.readModeOffset : constants.readModeOffset + constants.verticalSpacing;
  verticalHeights = [offset];
  sum = offset;
  $('.vertical-narrative-section').each(function() {
    sum += $(this).outerHeight() + constants.verticalSpacing;
    return verticalHeights.push(sum);
  });
  return verticalHeights;
};

window.slideCurrentYIntoPlace = function(){
  goToY(Session.get('currentY'), {force: true})
};

window.goToXY = function(x, y) {
  if (y !== Session.get("currentY")) {
    goToY(y);
  }
  return goToX(x);
};

window.goToY = function(y, options) {
  options = options || {};
  options.complete = options.complete || function(){};
  if ((options.force) || Session.get("currentY") !== y){
    var verticalHeights;
    verticalHeights = window.getVerticalHeights();
    $('body,html').animate({
      scrollTop: verticalHeights[y]
    }, 500, 'easeInExpo', function() {
      Session.set("currentY", y);
      Meteor.setTimeout(function(){
        options.complete();
      });
    });
  } else {
    options.complete();
  }
};

window.goToX = function(x) {
  currentXByYId = Session.get("currentXByYId");
  currentXByYId[Session.get("currentYId")] = x;
  Session.set("currentXByYId", currentXByYId);
};

window.goToContext = function(id) {
  var contextIndex, currentVertical, currentY, story;
  if (id) {
    currentY = Session.get('currentY');

    contextIndex = _.indexOf(_.pluck(Session.get('horizontalSectionsMap')[currentY].horizontal, '_id'), id.toString());
    if (contextIndex >= 0) {
      if (Meteor.Device.isPhone()){
        Session.set('mobileContextView', true);
      }
      return goToX(contextIndex);
    }
  }
};

window.goDownOneCard = function() {
  var currentY, newY;
  currentY = Session.get("currentY");
  if (typeof currentY !== 'number'){
    return goToXY(0, 0);
  }

  newY = currentY + 1;
  if (newY < Session.get("story").verticalSections.length){
    return goToY(newY);
  }
};

window.goUpOneCard = function() {
  var currentY, newY;
  currentY = Session.get("currentY");
  newY = currentY - 1;
  if (newY >= 0)
    return goToY(newY);
};

window.goRightOneCard = function() {
  var currentX, horizontalSection, newX;
  horizontalSection = Session.get("horizontalSectionsMap")[Session.get("currentY")].horizontal;
  currentX = Session.get("currentX");
  currentY = Session.get("currentY");
  currentYId = Session.get("currentYId");
  if (currentX === (horizontalSection.length - 1)) { // end of our rope
    newX = 0;
    wrap = Session.get("wrap");
    wrap[currentYId] = true;
    Session.set("wrap", wrap);
  } else {
    newX = currentX + 1;
  }
  goToX(newX);
};

window.goLeftOneCard = function() {
  var currentX, horizontalSection, newX;
  horizontalSection = Session.get("horizontalSectionsMap")[Session.get("currentY")].horizontal;
  currentX = Session.get("currentX");
  newX = currentX ? currentX - 1 : horizontalSection.length - 1;
  goToX(newX);
};

window.moveOneCard = function(d) {
  if (d < 0) {
    return goDownOneCard();
  } else if (d > 0) {
    return goUpOneCard();
  }
};

window.horizontalExists = function(){
  var currentY = Session.get('currentY');
  return ((_ref = Session.get('horizontalSectionsMap')[currentY]) != null ? _ref.horizontal.length : void 0) > 1
}

$(document).keydown(function(e) {
  if ((Router.current().route.getName() === 'read' || (Router.current().route.getName() === 'create' && Session.get('read'))) && !signingIn()){
    var letter = String.fromCharCode(e.keyCode);
    switch(letter){
      case 'J':
        goDownOneCard();
        break;
      case 'K':
        goUpOneCard();
        break;
      case 'H':
        if(Session.get('pastHeader')){
          goLeftOneCard();
        }
        break;
      case 'L':
        if(Session.get('pastHeader')) {
          goRightOneCard();
        }
        break;
      case '%': // left arrow
        if(Session.get('pastHeader')){
          goLeftOneCard();
        }
        break;
      case '\'': // right arrow
        if(Session.get('pastHeader')) {
          goRightOneCard();
        }
        break;
      case ' ': // spacebar
        break;
    }
  } else if (signingIn() && Session.equals('signinStage', 'signup')){
    if(e.keyCode === 27){ // esc
      closeSignInOverlay();
    }
  }
});

window.resetXPositionMemory = function () {
  Session.set("wrap", {});
  Session.set("currentXByYId", {});
};

window.getXByYId = function(yId) {
  if(yId){
    return Session.get('currentXByYId')[yId] || 0;
  }
};
<commit_msg>get internal navigation buttons working on preview<commit_after>window.constants = {
  verticalSpacing: 20, // there is css that needs to match this
  readModeOffset: 246,
  minPageWidth: 1024,
  selectOffset: - 210
};

if(Meteor.Device.isPhone()){
  window.constants.readModeOffset = window.constants.readModeOffset + 92
}

window.getVerticalLeft = function() {
  return Meteor.Device.isPhone() ? (Session.get('windowWidth') > 340 ? 30 : 20) : (Session.get('windowWidth') / 2 - Session.get('cardWidth') - Session.get('separation'));
};

window.getHorizontalLeft = function() {
  var currentPos, currentHorizontal, cardWidth, numCards, left, offset, pageWidth, verticalRight, addContextBlockWidth, cardSeparation;

  // Variable definitions (width of page, width of card, offset of cards)
  cardWidth = Session.get("cardWidth");
  cardSeparation = Session.get("separation");
  addContextBlockWidth = 75;
  verticalLeft = Session.get("verticalLeft");
  verticalRight = verticalLeft + cardWidth;

  // Offset of first card, different on create page because of (+) button
  if (Session.get("read")) {
    offset = 0;
  } else {
    offset = addContextBlockWidth + cardSeparation;
  }
  if (Session.get("addingContext")) {
    offset += cardWidth + cardSeparation;
  }

  if(this.verticalIndex === Session.get("currentY")){
    currentHorizontal = Session.get("horizontalSectionsMap")[Session.get("currentY")];
    if (!currentHorizontal) {
      return
    }
    currentPos = this.index - Session.get("currentX");
    numCards = currentHorizontal.horizontal.length;
  } else { // card is from another row
    currentPos = this.index - getXByYId(this.verticalId);
    numCards = Session.get("horizontalSectionsMap")[this.verticalIndex].horizontal.length;
  }

  if (numCards === 1){
    return verticalRight + offset + cardSeparation;
  }

  if (Session.get("wrap")[this.verticalId] || numCards === 2) { // wrapping (and always position as if wrapping when two cards)

    if (currentPos < 0) { // makes the first card appear at the end of the last card
      currentPos = numCards + currentPos;
    }

    // Default context positioning (all to the right of vertical narrative)
    left = (currentPos * (cardWidth + cardSeparation)) + (verticalRight + cardSeparation + offset);

    // Last card positioning if number of cards is greater than 3
    if (numCards >= 3) {
      if (currentPos === numCards - 1) {
        left = verticalLeft - cardWidth - cardSeparation;
      }
    }
    return left;
  } else { // not wrapping

    if (currentPos === numCards - 1 || currentPos < -1) { // this makes cards appear on the right when they run off the left
      currentPos = numCards + currentPos;
    }

    if (currentPos >= 0) {
      left = (currentPos * (cardWidth + cardSeparation)) + (verticalRight + cardSeparation + offset);
    } else {

      left = ((currentPos + 1) * (cardWidth + cardSeparation)) + (verticalLeft - cardWidth - cardSeparation);
    }

    return left;
  }
};

window.getVerticalHeights = function() {
  var sum, verticalHeights;
  var offset = Session.get('read') ? constants.readModeOffset : constants.readModeOffset + constants.verticalSpacing;
  verticalHeights = [offset];
  sum = offset;
  $('.vertical-narrative-section').each(function() {
    sum += $(this).outerHeight() + constants.verticalSpacing;
    return verticalHeights.push(sum);
  });
  return verticalHeights;
};

window.slideCurrentYIntoPlace = function(){
  goToY(Session.get('currentY'), {force: true})
};

window.goToXY = function(x, y) {
  if (y !== Session.get("currentY")) {
    goToY(y);
  }
  return goToX(x);
};

window.goToY = function(y, options) {
  options = options || {};
  options.complete = options.complete || function(){};
  if ((options.force) || Session.get("currentY") !== y){
    var verticalHeights;
    verticalHeights = window.getVerticalHeights();
    $('body,html').animate({
      scrollTop: verticalHeights[y]
    }, 500, 'easeInExpo', function() {
      Session.set("currentY", y);
      Meteor.setTimeout(function(){
        options.complete();
      });
    });
  } else {
    options.complete();
  }
};

window.goToX = function(x) {
  currentXByYId = Session.get("currentXByYId");
  currentXByYId[Session.get("currentYId")] = x;
  Session.set("currentXByYId", currentXByYId);
};

window.goToContext = function(id) {
  var contextIndex, currentVertical, currentY, story;
  if (id) {
    currentY = Session.get('currentY');

    contextIndex = _.indexOf(_.pluck(Session.get('horizontalSectionsMap')[currentY].horizontal, '_id'), id.toString());
    if (contextIndex >= 0) {
      if (Meteor.Device.isPhone()){
        Session.set('mobileContextView', true);
      }
      return goToX(contextIndex);
    }
  }
};

window.goDownOneCard = function() {
  var currentY, newY;
  currentY = Session.get("currentY");
  if (typeof currentY !== 'number'){
    return goToXY(0, 0);
  }

  newY = currentY + 1;
  if (newY < Session.get("story").verticalSections.length){
    return goToY(newY);
  }
};

window.goUpOneCard = function() {
  var currentY, newY;
  currentY = Session.get("currentY");
  newY = currentY - 1;
  if (newY >= 0)
    return goToY(newY);
};

window.goRightOneCard = function() {
  var currentX, horizontalSection, newX;
  horizontalSection = Session.get("horizontalSectionsMap")[Session.get("currentY")].horizontal;
  currentX = Session.get("currentX");
  currentY = Session.get("currentY");
  currentYId = Session.get("currentYId");
  if (currentX === (horizontalSection.length - 1)) { // end of our rope
    newX = 0;
    wrap = Session.get("wrap");
    wrap[currentYId] = true;
    Session.set("wrap", wrap);
  } else {
    newX = currentX + 1;
  }
  goToX(newX);
};

window.goLeftOneCard = function() {
  var currentX, horizontalSection, newX;
  horizontalSection = Session.get("horizontalSectionsMap")[Session.get("currentY")].horizontal;
  currentX = Session.get("currentX");
  newX = currentX ? currentX - 1 : horizontalSection.length - 1;
  goToX(newX);
};

window.moveOneCard = function(d) {
  if (d < 0) {
    return goDownOneCard();
  } else if (d > 0) {
    return goUpOneCard();
  }
};

window.horizontalExists = function(){
  var currentY = Session.get('currentY');
  return ((_ref = Session.get('horizontalSectionsMap')[currentY]) != null ? _ref.horizontal.length : void 0) > 1
}

$(document).keydown(function(e) {
  var routeName = Router.current().route.getName();
  if ((routeName === 'read' || (routeName === 'edit' && Session.get('read'))) && !signingIn()){
    var letter = String.fromCharCode(e.keyCode);
    switch(letter){
      case 'J':
        goDownOneCard();
        break;
      case 'K':
        goUpOneCard();
        break;
      case 'H':
        if(Session.get('pastHeader')){
          goLeftOneCard();
        }
        break;
      case 'L':
        if(Session.get('pastHeader')) {
          goRightOneCard();
        }
        break;
      case '%': // left arrow
        if(Session.get('pastHeader')){
          goLeftOneCard();
        }
        break;
      case '\'': // right arrow
        if(Session.get('pastHeader')) {
          goRightOneCard();
        }
        break;
      case ' ': // spacebar
        break;
    }
  } else if (signingIn() && Session.equals('signinStage', 'signup')){
    if(e.keyCode === 27){ // esc
      closeSignInOverlay();
    }
  }
});

window.resetXPositionMemory = function () {
  Session.set("wrap", {});
  Session.set("currentXByYId", {});
};

window.getXByYId = function(yId) {
  if(yId){
    return Session.get('currentXByYId')[yId] || 0;
  }
};
<|endoftext|>


<commit_before>var gulp = require('gulp');

// Fabric Helper Modules
var Banners = require('./modules/Banners');
var Config = require('./modules/Config');
var BuildConfig = require('./modules/BuildConfig');
var ConsoleHelper = require('./modules/ConsoleHelper');
var ErrorHandling = require('./modules/ErrorHandling');
var Plugins = require('./modules/Plugins');

//
// Clean/Delete Tasks
// ----------------------------------------------------------------------------

// Clean out the distribution folder.
gulp.task('Fabric-nuke', function () {
    return Plugins.del.sync([Config.paths.distLess, Config.paths.distCSS, Config.paths.distSass]);
});

//
// Copying Files Tasks
// ----------------------------------------------------------------------------

// Copy all LESS files to distribution folder.
gulp.task('Fabric-copyAssets', function () {
    // Copy LESS files.
     var moveLess = gulp.src([Config.paths.srcLess + '/**/*'])
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.changed(Config.paths.distLess))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Moving LESS Assets over to Dist"
            })))
            .pipe(gulp.dest(Config.paths.distLess));

     var moveSass =  gulp.src([Config.paths.srcSass + '/**/*'])
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.changed(Config.paths.distSass))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Moving SASS files over to Dist"
            })))
            .pipe(gulp.dest(Config.paths.distSass));
     return Plugins.mergeStream(moveLess, moveSass);
});

//
// LESS tasks
// ----------------------------------------------------------------------------

// Build LESS files for core Fabric into LTR and RTL CSS files.

gulp.task('Fabric-buildStyles', function () {
    var fabric = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Building Core Fabric " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
                browsers: ['last 2 versions', 'ie >= 9'],
                cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.min.css'))
            .pipe(Plugins.cssMinify())
            .pipe(gulp.dest(Config.paths.distCSS));

    // Build full and minified Fabric RTL CSS.
    var fabricRtl = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Rtl.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Building RTL Fabric " + BuildConfig.processorName + " " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.flipper())
            .pipe(Plugins.rename('fabric.rtl.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
                browsers: ['last 2 versions', 'ie >= 9'],
                cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.rtl.min.css'))
            .pipe(Plugins.cssMinify())
            .pipe(gulp.dest(Config.paths.distCSS));
    // Merge all current streams into one.
    return Plugins.mergeStream(fabric, fabricRtl);
});

//
// Rolled up Build tasks
// ----------------------------------------------------------------------------

gulp.task('Fabric', ['Fabric-copyAssets', 'Fabric-buildStyles']);

//
// Fabric Messages
// ----------------------------------------------------------------------------

gulp.task('Fabric-finished', ['Fabric'], function () {
    console.log(ConsoleHelper.generateSuccess('Fabric core-build complete, you may now celebrate and dance!', true));
});

gulp.task('Fabric-updated', ['Fabric'], function () {
    console.log(ConsoleHelper.generateSuccess(' Fabric updated successfully', false));
});

//
// Watch Tasks
// ----------------------------------------------------------------------------

// Watch and build Fabric when sources change.
gulp.task('Fabric-watch', ['Fabric', 'Fabric-finished'], function () {
    return gulp.watch(Config.paths.lessPath + '/**/*', Plugins.batch(function (events, done) {
        Plugins.runSequence('Fabric', 'Fabric-updated', done);
    }));
});<commit_msg>No longer copy LESS assets in build.<commit_after>var gulp = require('gulp');

// Fabric Helper Modules
var Banners = require('./modules/Banners');
var Config = require('./modules/Config');
var BuildConfig = require('./modules/BuildConfig');
var ConsoleHelper = require('./modules/ConsoleHelper');
var ErrorHandling = require('./modules/ErrorHandling');
var Plugins = require('./modules/Plugins');

//
// Clean/Delete Tasks
// ----------------------------------------------------------------------------

// Clean out the distribution folder.
gulp.task('Fabric-nuke', function () {
    return Plugins.del.sync([Config.paths.distLess, Config.paths.distCSS, Config.paths.distSass]);
});

//
// Copying Files Tasks
// ----------------------------------------------------------------------------

// Copy all Sass files to distribution folder.
gulp.task('Fabric-copyAssets', function () {
     var moveSass =  gulp.src([Config.paths.srcSass + '/**/*'])
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.changed(Config.paths.distSass))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Moving Sass files over to Dist"
            })))
            .pipe(gulp.dest(Config.paths.distSass));
     return moveSass;
});

//
// LESS tasks
// ----------------------------------------------------------------------------

// Build LESS files for core Fabric into LTR and RTL CSS files.

gulp.task('Fabric-buildStyles', function () {
    var fabric = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Building Core Fabric " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
                browsers: ['last 2 versions', 'ie >= 9'],
                cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.min.css'))
            .pipe(Plugins.cssMinify())
            .pipe(gulp.dest(Config.paths.distCSS));

    // Build full and minified Fabric RTL CSS.
    var fabricRtl = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Rtl.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Building RTL Fabric " + BuildConfig.processorName + " " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.flipper())
            .pipe(Plugins.rename('fabric.rtl.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
                browsers: ['last 2 versions', 'ie >= 9'],
                cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.rtl.min.css'))
            .pipe(Plugins.cssMinify())
            .pipe(gulp.dest(Config.paths.distCSS));
    // Merge all current streams into one.
    return Plugins.mergeStream(fabric, fabricRtl);
});

//
// Rolled up Build tasks
// ----------------------------------------------------------------------------

gulp.task('Fabric', ['Fabric-copyAssets', 'Fabric-buildStyles']);

//
// Fabric Messages
// ----------------------------------------------------------------------------

gulp.task('Fabric-finished', ['Fabric'], function () {
    console.log(ConsoleHelper.generateSuccess('Fabric core-build complete, you may now celebrate and dance!', true));
});

gulp.task('Fabric-updated', ['Fabric'], function () {
    console.log(ConsoleHelper.generateSuccess(' Fabric updated successfully', false));
});

//
// Watch Tasks
// ----------------------------------------------------------------------------

// Watch and build Fabric when sources change.
gulp.task('Fabric-watch', ['Fabric', 'Fabric-finished'], function () {
    return gulp.watch(Config.paths.lessPath + '/**/*', Plugins.batch(function (events, done) {
        Plugins.runSequence('Fabric', 'Fabric-updated', done);
    }));
});<|endoftext|>


<commit_before>var React = require('react');

var Home = React.createClass({
	render: function() {
		return (

Welcome

This is the React + Express starter project. It includes React for the client, express for the server, a lightweight CSS framework build with LESS, react-router and several UI components to get you started quickly.

Browse the pages above to see examples of the included layout and form components, then dive into the code in /client to get started customising it.

Public assets are served from the /public folder, and anything .css file you include from the /public/styles folder will be built from the corresponding .less file.

The express server configuration is in /server. Here you can add custom server-side middleware, routes and templates, including API endpoints.

		);
	}
});

module.exports = Home;
<commit_msg>Adding link to GitHub Project<commit_after>var React = require('react');

var Home = React.createClass({
	render: function() {
		return (

Welcome

This is the React + Express starter project. It includes React for the client, express for the server, a lightweight CSS framework build with LESS, react-router and several UI components to get you started quickly.

Browse the pages above to see examples of the included layout and form components, then dive into the code in /client to get started customising it.

Public assets are served from the /public folder, and anything .css file you include from the /public/styles folder will be built from the corresponding .less file.

The express server configuration is in /server. Here you can add custom server-side middleware, routes and templates, including API endpoints.

View the GitHub Project

		);
	}
});

module.exports = Home;
<|endoftext|>