<commit_before>(function ($) {
    // extend jQuery --------------------------------------------------------------------
    $.fn.butterCombobox = function () {
        return this.each(function () {
            new FilterableCombobox(this);
        });
    };

    // define objects --------------------------------------------------------------------

    /**
     * @class
     */
    var FilterableComboboxMouseEventListener = Class.extend({
        /**
         * Is called when a mouse down event is triggered on a result list option. Intended to be overridden.
         * @param {String} value the value of the result list option
         */
        onResultItemMouseDown: function (value) {
        },

        /**
         * Is called when a mouse enter event is triggered on a result list option. Intended to be overridden.
         * @param {String} value the value of the result list option
         */
        onResultItemMouseEnter: function (value) {
        }
    });

    /**
     * @class
     */
    var FilterableComboboxOption = Class.extend({
        /**
         * @constructs
         * @param {HTMLElement} optionElement
         * @param {FilterableComboboxMouseEventListener} mouseEventListener
         * @param {jQuery} $template
         */
        init: function (optionElement, mouseEventListener, $template) {
            this._$optionElement = $(optionElement);
            this._mouseEventListener = mouseEventListener;
            this._$resultElement;
            this._initResultItem($template);
        },

        _initResultItem: function ($template) {
            var self = this;
            var resultItemHtml = self._$optionElement.text();
            if ($template !== undefined) {
                var dataFields = self._$optionElement.data();
                resultItemHtml = $template;
                $template.replace(/\{\{(.*?)\}\}/g, function (group0, group1) {
                    resultItemHtml = resultItemHtml.replace("{{" + group1 + "}}", dataFields[group1.toLowerCase()])
                    return;
                })
            }

            self._$resultElement = $("<li>")
                .html(resultItemHtml)
                .addClass("butter-dropdownlist-resultItem");

            if (self._mouseEventListener !== undefined) {
                self._$resultElement.
                    on("mousedown", function () {
                        self._mouseEventListener.onResultItemMouseDown(self.getValue());
                    })
                    .on("mouseenter", function () {
                        self._mouseEventListener.onResultItemMouseEnter(self.getValue());
                    });
            }
        },

        /**
         * @returns {String} the option's value
         */
        getValue: function () {
            return this._$optionElement.val();
        },

        /**
         * @returns {String} the option's label text. This is the text between the option tags.
         */
        getLabel: function () {
            return this._$optionElement.text();
        },

        /**
         * @returns {jQuery} a jQuery extended HTML element
         */
        getResultElement: function () {
            return this._$resultElement;
        }
    });

    /**
     * @class
     */
    var FilterableComboboxEmtpyOption = FilterableComboboxOption.extend({
        /**
         * @constructs
         */
        init: function () {
            this._$optionElement = $("<option>")
                .text("Keine Einträge gefunden!");
            this._$resultElement;
            this._initResultItem();
            this.getResultElement()
                .removeClass("butter-dropdownlist-resultItem")
                .addClass("butter-dropdownlist-noResultItem");
        },

        /**
         * @inheritDoc
         */
        getValue: function () {
            return "butter-noResultItem";
        },
    });

    /**
     * @class
     */
    var FilterableComboboxOptionList = Class.extend({
        /**
         * @constructs
         */
        init: function () {
            this._options = [];
            this._filteredOptions = [];
            this._selectedIndex = -1;
        },

        /**
         * @param {FilterableComboboxOption} resultOption
         */
        add: function (resultOption) {
            this._options.push(resultOption);
        },

        /**
         *
         * @param filter {String} the filter text
         * @returns {Array} the array with the filtered option elements
         */
        getFilteredOptions: function (filter) {
            //console.log("FilterableComboboxOptionList - getting filtered options for '%s'", filter);
            var self = this;
            self.unselect();
            self._filteredOptions = [];

            $.each(self._options, function (index, element) {
                element.getResultElement().highlight(filter);
                if (filter === undefined || filter === "") {
                    self._filteredOptions.push(element);
                } else if (element.getResultElement().find(".search-highlighted").length > 0) {
                    self._filteredOptions.push(element);
                }
            });

            if (this.hasResultOptions()) {
                //console.log("FilterableComboboxOptionList - returning %s results", self._filteredOptions.length);
                return self._filteredOptions;
            } else {
                //console.log("FilterableComboboxOptionList - found no result returning empty result option");
                return [new FilterableComboboxEmtpyOption()];
            }
        },

        /**
         *
         * @param {String} value the value of the options that should be selected
         */
        select: function (value) {
            if (!this.hasResultOptions()) {
                return;
            }

            this.unselect();
            for (var i = 0; i < this._filteredOptions.length; i++) {
                if (this._filteredOptions[i].getValue() === value) {
                    this._doSelectAndMarkElement(i);
                    break;
                }
            }
        },

        /**
         * Select the previous element in the filtered list. If there is no previous element the last will be selected.
         */
        selectPrevious: function () {
            if (!this.hasResultOptions()) {
                return;
            }

            this._unmarkSelectedElement();
            if (this.hasSelected()) {
                if (this._selectedIndex - 1 >= 0) {
                    this._doSelectAndMarkElement(this._selectedIndex - 1);
                } else {
                    this._doSelectAndMarkElement(this._filteredOptions.length - 1);
                }
            } else {
                this._doSelectAndMarkElement(0);
            }
        },

        /**
         * Select the nex element in the filtered list. If there is no next element the first will be selected.
         */
        selectNext: function () {
            if (!this.hasResultOptions()) {
                return;
            }

            this._unmarkSelectedElement();
            if (this.hasSelected()) {
                if (this._selectedIndex + 1 < this._filteredOptions.length) {
                    this._doSelectAndMarkElement(this._selectedIndex + 1);
                } else {
                    this._doSelectAndMarkElement(0);
                }
            } else {
                this._doSelectAndMarkElement(0);
            }
        },

        /**
         * Unselect possible selected option
         */
        unselect: function () {
            if (this.hasResultOptions()) {
                this._unmarkSelectedElement();
            }
            this._selectedIndex = -1;
        },

        _unmarkSelectedElement: function () {
            if (this.hasSelected()) {
                //console.log("FilterableComboboxOptionList - unmark selected element on position %s", this._selectedIndex);
                this.getSelected().getResultElement().removeClass("butter-dropdownlist-resultItem-selected");
            }
        },

        /**
         * * @returns {FilterableComboboxOption} the option that is selected
         */
        getSelected: function () {
            return this.hasSelected() ? this._filteredOptions[this._selectedIndex] : null;
        },

        /**
         * @returns {boolean} true if an element is selected
         */
        hasSelected: function () {
            return this._selectedIndex >= 0;
        },

        /**
         * @returns {boolean} true if there elements in the filtered list
         */
        hasResultOptions: function () {
            return this._filteredOptions.length > 0;
        },

        _doSelectAndMarkElement: function (index) {
            this._filteredOptions[index].getResultElement().addClass("butter-dropdownlist-resultItem-selected");
            this._selectedIndex = index;
        }
    });

    /**
     * @class
     */
    var FilterableCombobox = FilterableComboboxMouseEventListener.extend({
        /**
         * @constructs
         * @param {HTMLElement} rootElement
         */
        init: function (rootElement) {
            this.$select = $(rootElement).find("select");
            this.$template = this.$select.parent().siblings(".butter-component-combobox-resultListItemTemplate").html();
            this.$ghostInput = null;
            this.$resultContainer = null;
            this.$resultListContainer = null;
            this._optionList;
            this._hasFocus = false;
            this._disabled = this.$select.is('[disabled=disabled]');
            this.isMouseClickBlocked = false;

            this._keyCodes = {
                //backspace: 8,
                tab: 9,
                enter: 13,
                shift: 16,
                ctrl: 17,
                alt: 18,
                pause: 19,
                caps_lock: 20,
                escape: 27,
                page_up: 33,
                page_down: 34,
                end: 35,
                home: 36,
                arrow_left: 37,
                arrow_up: 38,
                arrow_right: 39,
                arrow_down: 40,
                insert: 45,
                // 'delete' is a reserved key word
                delete_key: 46,
                left_window_key: 91,
                right_window_key: 92,
                select_key: 93,
                num_lock: 144,
                scroll_lock: 145
            };

            this._initializeGhostElement();
            this._initializeDropDownButton();
            this._initializeOptionList();
        },

        _initializeGhostElement: function () {
            var self = this;
            self.$ghostInput = self.$select.parent().find(".butter-component-combobox-ghost")
                //.addClass($select.attr("class"))
                .val(self.$select.find("option:selected").text())
                .on("focus", function () {
                    self._hasFocus = true;
                    // automatically select whole text on focus
                    self._selectCompleteTextInGhostInput();
                })
                .on("mouseup", function (event) {
                    // in safari the mouseup event unselects the text, so we have to prevent this
                    self._stopEvent(event);
                })
                .on("blur", function () {
                    self._hasFocus = false;
                    // block next mouse click only if the result list is opened
                    self.isMouseClickBlocked = self.$resultContainer !== null;

                    window.setTimeout(function () {
                        self.isMouseClickBlocked = false;
                        if (!self._hasFocus) {
                            self._hideOptionResultList();
                            self._resetDisplayValue();
                        }
                    }, 200);
                })
                .on("keydown", function (event) {
                    if (event.which === self._keyCodes.enter) {
                        self._handleEnterKeyDown(event);
                    } else if (event.which === self._keyCodes.arrow_up || event.which === self._keyCodes.arrow_down) {
                        self._handleArrowUpAndDownKeyDown(event);
                    } else if (event.which === self._keyCodes.escape) {
                        self._handleEscapeKeyDown(event);
                    }
                })
                .on("keyup", function (event) {
                    // don't handle other keys than character keys
                    for (keyName in self._keyCodes) {
                        if (self._keyCodes[keyName] === event.which) {
                            self._stopEvent(event);
                            return;
                        }
                    }

                    var searchText = $(this).val();
                    self._showOptionResultList(searchText);
                });

            if (self._disabled) {
                self.$ghostInput.attr('disabled', 'disabled');
            }
        },

        _initializeOptionList: function () {
            var self = this;
            self._optionList = new FilterableComboboxOptionList();
            self.$select.find("option").each(function (index, element) {
                self._optionList.add(new FilterableComboboxOption(element, self, self.$template));
            });
        },

        _handleEnterKeyDown: function (event) {
            if (this._optionList.hasSelected() && this._isResultContainerShown()) {
                this._stopEvent(event);
                this._setSelectedValue();
                this._selectCompleteTextInGhostInput();
            } else if (!this._optionList.hasSelected() && this._isResultContainerShown()) {
                // deactivate enter key if result list is opened but nothing is selected
                this._stopEvent(event);
            } else {
                this._resetDisplayValue();
            }
        },

        _handleArrowUpAndDownKeyDown: function (event) {
            this._stopEvent(event);
            if (!this._isResultContainerShown()) {
                this._showOptionResultList();
            }

            this._moveResultOptionElementSelectionCursor(event.which === this._keyCodes.arrow_up ? -1 : 1);
        },

        _handleEscapeKeyDown: function (event) {
            if (this._isResultContainerShown()) {
                this._hideOptionResultList();
                this._resetDisplayValue();
                this._selectCompleteTextInGhostInput();
            } else {
                this.$ghostInput.blur();
            }
        },

        _initializeDropDownButton: function () {
            var self = this;
            if (!self._disabled) {
                self.$ghostInput.next()
                    .on("click", function (event) {
                        self._stopEvent(event);

                        if (self.isMouseClickBlocked) {
                            return;
                        }

                        self._showOptionResultList();
                        self.$ghostInput.focus();
                    });
            } else {
                self.$ghostInput.next().addClass('disabled');
            }
        },

        _showOptionResultList: function (searchText) {
            var self = this;

            // create container elements if necessary
            if (!self._isResultContainerShown()) {
                var $inputGroup = self.$ghostInput.parent();
                var inputGroupOffset = $inputGroup.offset();
                self.$resultContainer = $("<div>")
                    .addClass("butter-dropdownlist-container")
                    .addClass("butter-component-combobox-dropdownlist-container")
                    .css({
                        position: "absolute",
                        left: inputGroupOffset.left,
                        top: inputGroupOffset.top + $inputGroup.outerHeight(),
                        width: $inputGroup.innerWidth()
                    });

                self.$resultListContainer = $("<ul>")
                    .addClass("butter-dropdownlist-resultList")
                    .on("mouseleave", function () {
                        self._optionList.unselect();
                    })
                    .appendTo(self.$resultContainer);
                $("body").append(self.$resultContainer);
            }

            self._detachResultListElements();
            self.$selectedOption = null;

            $.each(self._optionList.getFilteredOptions(searchText), function (index, element) {
                self.$resultListContainer.append(element.getResultElement());
            });
        },

        _hideOptionResultList: function () {
            var self = this;
            if (self._isResultContainerShown()) {
                self._detachResultListElements();
                self.$resultContainer.remove();
                self.$resultContainer = null;
                self.$resultListContainer = null;
                this._optionList.unselect();
            }
        },

        _detachResultListElements: function () {
            this._optionList.unselect();
            this.$resultListContainer.children().detach();
        },

        /**
         * @inheritdoc
         */
        onResultItemMouseDown: function (value) {
            this._setSelectedValue();
        },

        /**
         * @inheritdoc
         */
        onResultItemMouseEnter: function (value) {
            this._optionList.select(value);
        },

        _moveResultOptionElementSelectionCursor: function (direction) {
            if (direction > 0) {
                this._optionList.selectNext();
            } else {
                this._optionList.selectPrevious();
            }
        },

        _setSelectedValue: function () {
            if (this._optionList.hasSelected()) {
                var selectOption = this._optionList.getSelected();
                this.$select
                    .val(selectOption.getValue())
                    .change();
                this.$ghostInput.val(selectOption.getLabel());
                this._hideOptionResultList();
            }
        },

        _resetDisplayValue: function () {
            // set old value
            this.$ghostInput.val(this.$select.find("option:selected").text());
        },

        _stopEvent: function (event) {
            event.stopPropagation();
            event.preventDefault();
        },

        _selectCompleteTextInGhostInput: function () {
            this.$ghostInput[0].setSelectionRange(0, this.$ghostInput.val().length);
        },

        _isResultContainerShown: function () {
            return this.$resultContainer !== null;
        }
    });
}(jQuery));<commit_msg>BUT-341 automatically scroll to selected result item if it is out of view range<commit_after>(function ($) {
    // extend jQuery --------------------------------------------------------------------
    $.fn.butterCombobox = function () {
        return this.each(function () {
            new FilterableCombobox(this);
        });
    };

    // define objects --------------------------------------------------------------------

    /**
     * @class
     */
    var FilterableComboboxMouseEventListener = Class.extend({
        /**
         * Is called when a mouse down event is triggered on a result list option. Intended to be overridden.
         * @param {String} value the value of the result list option
         */
        onResultItemMouseDown: function (value) {
        },

        /**
         * Is called when a mouse enter event is triggered on a result list option. Intended to be overridden.
         * @param {String} value the value of the result list option
         */
        onResultItemMouseEnter: function (value) {
        }
    });

    /**
     * @class
     */
    var FilterableComboboxOption = Class.extend({
        /**
         * @constructs
         * @param {HTMLElement} optionElement
         * @param {FilterableComboboxMouseEventListener} mouseEventListener
         * @param {jQuery} $template
         */
        init: function (optionElement, mouseEventListener, $template) {
            this._$optionElement = $(optionElement);
            this._mouseEventListener = mouseEventListener;
            this._$resultElement;
            this._initResultItem($template);
        },

        _initResultItem: function ($template) {
            var self = this;
            var resultItemHtml = self._$optionElement.text();
            if ($template !== undefined) {
                var dataFields = self._$optionElement.data();
                resultItemHtml = $template;
                $template.replace(/\{\{(.*?)\}\}/g, function (group0, group1) {
                    resultItemHtml = resultItemHtml.replace("{{" + group1 + "}}", dataFields[group1.toLowerCase()])
                    return;
                })
            }

            self._$resultElement = $("<li>")
                .html(resultItemHtml)
                .addClass("butter-dropdownlist-resultItem");

            if (self._mouseEventListener !== undefined) {
                self._$resultElement.
                    on("mousedown", function () {
                        self._mouseEventListener.onResultItemMouseDown(self.getValue());
                    })
                    .on("mouseenter", function () {
                        self._mouseEventListener.onResultItemMouseEnter(self.getValue());
                    });
            }
        },

        /**
         * @returns {String} the option's value
         */
        getValue: function () {
            return this._$optionElement.val();
        },

        /**
         * @returns {String} the option's label text. This is the text between the option tags.
         */
        getLabel: function () {
            return this._$optionElement.text();
        },

        /**
         * @returns {jQuery} a jQuery extended HTML element
         */
        getResultElement: function () {
            return this._$resultElement;
        }
    });

    /**
     * @class
     */
    var FilterableComboboxEmtpyOption = FilterableComboboxOption.extend({
        /**
         * @constructs
         */
        init: function () {
            this._$optionElement = $("<option>")
                .text("Keine Einträge gefunden!");
            this._$resultElement;
            this._initResultItem();
            this.getResultElement()
                .removeClass("butter-dropdownlist-resultItem")
                .addClass("butter-dropdownlist-noResultItem");
        },

        /**
         * @inheritDoc
         */
        getValue: function () {
            return "butter-noResultItem";
        },
    });

    /**
     * @class
     */
    var FilterableComboboxOptionList = Class.extend({
        /**
         * @constructs
         */
        init: function () {
            this._options = [];
            this._filteredOptions = [];
            this._selectedIndex = -1;
        },

        /**
         * @param {FilterableComboboxOption} resultOption
         */
        add: function (resultOption) {
            this._options.push(resultOption);
        },

        /**
         *
         * @param filter {String} the filter text
         * @returns {Array} the array with the filtered option elements
         */
        getFilteredOptions: function (filter) {
            //console.log("FilterableComboboxOptionList - getting filtered options for '%s'", filter);
            var self = this;
            self.unselect();
            self._filteredOptions = [];

            $.each(self._options, function (index, element) {
                element.getResultElement().highlight(filter);
                if (filter === undefined || filter === "") {
                    self._filteredOptions.push(element);
                } else if (element.getResultElement().find(".search-highlighted").length > 0) {
                    self._filteredOptions.push(element);
                }
            });

            if (this.hasResultOptions()) {
                //console.log("FilterableComboboxOptionList - returning %s results", self._filteredOptions.length);
                return self._filteredOptions;
            } else {
                //console.log("FilterableComboboxOptionList - found no result returning empty result option");
                return [new FilterableComboboxEmtpyOption()];
            }
        },

        /**
         *
         * @param {String} value the value of the options that should be selected
         */
        select: function (value) {
            if (!this.hasResultOptions()) {
                return;
            }

            this.unselect();
            for (var i = 0; i < this._filteredOptions.length; i++) {
                if (this._filteredOptions[i].getValue() === value) {
                    this._doSelectAndMarkElement(i);
                    break;
                }
            }
        },

        /**
         * Select the previous element in the filtered list. If there is no previous element the last will be selected.
         */
        selectPrevious: function () {
            if (!this.hasResultOptions()) {
                return;
            }

            this._unmarkSelectedElement();
            if (this.hasSelected()) {
                if (this._selectedIndex - 1 >= 0) {
                    this._doSelectAndMarkElement(this._selectedIndex - 1);
                } else {
                    this._doSelectAndMarkElement(this._filteredOptions.length - 1);
                }
            } else {
                this._doSelectAndMarkElement(0);
            }
        },

        /**
         * Select the nex element in the filtered list. If there is no next element the first will be selected.
         */
        selectNext: function () {
            if (!this.hasResultOptions()) {
                return;
            }

            this._unmarkSelectedElement();
            if (this.hasSelected()) {
                if (this._selectedIndex + 1 < this._filteredOptions.length) {
                    this._doSelectAndMarkElement(this._selectedIndex + 1);
                } else {
                    this._doSelectAndMarkElement(0);
                }
            } else {
                this._doSelectAndMarkElement(0);
            }
        },

        /**
         * Unselect possible selected option
         */
        unselect: function () {
            if (this.hasResultOptions()) {
                this._unmarkSelectedElement();
            }
            this._selectedIndex = -1;
        },

        _unmarkSelectedElement: function () {
            if (this.hasSelected()) {
                //console.log("FilterableComboboxOptionList - unmark selected element on position %s", this._selectedIndex);
                this.getSelected().getResultElement().removeClass("butter-dropdownlist-resultItem-selected");
            }
        },

        /**
         * * @returns {FilterableComboboxOption} the option that is selected
         */
        getSelected: function () {
            return this.hasSelected() ? this._filteredOptions[this._selectedIndex] : null;
        },

        /**
         * @returns {boolean} true if an element is selected
         */
        hasSelected: function () {
            return this._selectedIndex >= 0;
        },

        /**
         * @returns {boolean} true if there elements in the filtered list
         */
        hasResultOptions: function () {
            return this._filteredOptions.length > 0;
        },

        _doSelectAndMarkElement: function (index) {
            this._filteredOptions[index].getResultElement().addClass("butter-dropdownlist-resultItem-selected");
            this._selectedIndex = index;
        }
    });

    /**
     * @class
     */
    var FilterableCombobox = FilterableComboboxMouseEventListener.extend({
        /**
         * @constructs
         * @param {HTMLElement} rootElement
         */
        init: function (rootElement) {
            this.$select = $(rootElement).find("select");
            this.$template = this.$select.parent().siblings(".butter-component-combobox-resultListItemTemplate").html();
            this.$ghostInput = null;
            this.$resultContainer = null;
            this.$resultListContainer = null;
            this._optionList;
            this._hasFocus = false;
            this._disabled = this.$select.is('[disabled=disabled]');
            this._isMouseClickBlocked = false;
            this._isMouseEnterBlocked = false;

            this._keyCodes = {
                //backspace: 8,
                tab: 9,
                enter: 13,
                shift: 16,
                ctrl: 17,
                alt: 18,
                pause: 19,
                caps_lock: 20,
                escape: 27,
                page_up: 33,
                page_down: 34,
                end: 35,
                home: 36,
                arrow_left: 37,
                arrow_up: 38,
                arrow_right: 39,
                arrow_down: 40,
                insert: 45,
                // 'delete' is a reserved key word
                delete_key: 46,
                left_window_key: 91,
                right_window_key: 92,
                select_key: 93,
                num_lock: 144,
                scroll_lock: 145
            };

            this._initializeGhostElement();
            this._initializeDropDownButton();
            this._initializeOptionList();
        },

        _initializeGhostElement: function () {
            var self = this;
            self.$ghostInput = self.$select.parent().find(".butter-component-combobox-ghost")
                //.addClass($select.attr("class"))
                .val(self.$select.find("option:selected").text())
                .on("focus", function () {
                    self._hasFocus = true;
                    // automatically select whole text on focus
                    self._selectCompleteTextInGhostInput();
                })
                .on("mouseup", function (event) {
                    // in safari the mouseup event unselects the text, so we have to prevent this
                    self._stopEvent(event);
                })
                .on("blur", function () {
                    self._hasFocus = false;
                    // block next mouse click only if the result list is opened
                    self._isMouseClickBlocked = self.$resultContainer !== null;

                    window.setTimeout(function () {
                        self._isMouseClickBlocked = false;
                        if (!self._hasFocus) {
                            self._hideOptionResultList();
                            self._resetDisplayValue();
                        }
                    }, 200);
                })
                .on("keydown", function (event) {
                    if (event.which === self._keyCodes.enter) {
                        self._handleEnterKeyDown(event);
                    } else if (event.which === self._keyCodes.arrow_up || event.which === self._keyCodes.arrow_down) {
                        self._handleArrowUpAndDownKeyDown(event);
                    } else if (event.which === self._keyCodes.escape) {
                        self._handleEscapeKeyDown(event);
                    }
                })
                .on("keyup", function (event) {
                    // don't handle other keys than character keys
                    for (keyName in self._keyCodes) {
                        if (self._keyCodes[keyName] === event.which) {
                            self._stopEvent(event);
                            return;
                        }
                    }

                    var searchText = $(this).val();
                    self._showOptionResultList(searchText);
                });

            if (self._disabled) {
                self.$ghostInput.attr('disabled', 'disabled');
            }
        },

        _initializeOptionList: function () {
            var self = this;
            self._optionList = new FilterableComboboxOptionList();
            self.$select.find("option").each(function (index, element) {
                self._optionList.add(new FilterableComboboxOption(element, self, self.$template));
            });
        },

        _handleEnterKeyDown: function (event) {
            if (this._optionList.hasSelected() && this._isResultContainerShown()) {
                this._stopEvent(event);
                this._setSelectedValue();
                this._selectCompleteTextInGhostInput();
            } else if (!this._optionList.hasSelected() && this._isResultContainerShown()) {
                // deactivate enter key if result list is opened but nothing is selected
                this._stopEvent(event);
            } else {
                this._resetDisplayValue();
            }
        },

        _handleArrowUpAndDownKeyDown: function (event) {
            this._stopEvent(event);
            if (!this._isResultContainerShown()) {
                this._showOptionResultList();
            }

            this._moveResultOptionElementSelectionCursor(event.which === this._keyCodes.arrow_up ? -1 : 1);
        },

        _handleEscapeKeyDown: function (event) {
            if (this._isResultContainerShown()) {
                this._hideOptionResultList();
                this._resetDisplayValue();
                this._selectCompleteTextInGhostInput();
            } else {
                this.$ghostInput.blur();
            }
        },

        _initializeDropDownButton: function () {
            var self = this;
            if (!self._disabled) {
                self.$ghostInput.next()
                    .on("click", function (event) {
                        self._stopEvent(event);

                        if (self._isMouseClickBlocked) {
                            return;
                        }

                        self._showOptionResultList();
                        self.$ghostInput.focus();
                    });
            } else {
                self.$ghostInput.next().addClass('disabled');
            }
        },

        _showOptionResultList: function (searchText) {
            var self = this;

            // create container elements if necessary
            if (!self._isResultContainerShown()) {
                var $inputGroup = self.$ghostInput.parent();
                var inputGroupOffset = $inputGroup.offset();
                self.$resultContainer = $("<div>")
                    .addClass("butter-dropdownlist-container")
                    .addClass("butter-component-combobox-dropdownlist-container")
                    .css({
                        position: "absolute",
                        left: inputGroupOffset.left,
                        top: inputGroupOffset.top + $inputGroup.outerHeight(),
                        width: $inputGroup.innerWidth()
                    });

                self.$resultListContainer = $("<ul>")
                    .addClass("butter-dropdownlist-resultList")
                    .on("mouseleave", function () {
                        self._optionList.unselect();
                    })
                    .appendTo(self.$resultContainer);
                $("body").append(self.$resultContainer);
            }

            self._detachResultListElements();
            self.$selectedOption = null;

            $.each(self._optionList.getFilteredOptions(searchText), function (index, element) {
                self.$resultListContainer.append(element.getResultElement());
            });
        },

        _hideOptionResultList: function () {
            var self = this;
            if (self._isResultContainerShown()) {
                self._detachResultListElements();
                self.$resultContainer.remove();
                self.$resultContainer = null;
                self.$resultListContainer = null;
                this._optionList.unselect();
            }
        },

        _detachResultListElements: function () {
            this._optionList.unselect();
            this.$resultListContainer.children().detach();
        },

        /**
         * @inheritdoc
         */
        onResultItemMouseDown: function (value) {
            this._setSelectedValue();
        },

        /**
         * @inheritdoc
         */
        onResultItemMouseEnter: function (value) {
            if (!this._isMouseEnterBlocked) {
                this._optionList.select(value);
            }
        },

        _moveResultOptionElementSelectionCursor: function (direction) {
            var self = this;

            if (direction > 0) {
                self._optionList.selectNext();
            } else {
                self._optionList.selectPrevious();
            }

            self._isMouseEnterBlocked = true;
            self._scrollToSelectedOptionElementIfNecessary();

            // this is necessary because if the mouse cursor is over a result item after the list has been scrolled a
            // mouse enter event will be triggered and the result item under the mouse cursor will be selected unintentionally.
            window.setTimeout(function () {
                self._isMouseEnterBlocked = false;
            }, 100);
        },

        _setSelectedValue: function () {
            if (this._optionList.hasSelected()) {
                var selectOption = this._optionList.getSelected();
                this.$select
                    .val(selectOption.getValue())
                    .change();
                this.$ghostInput.val(selectOption.getLabel());
                this._hideOptionResultList();
            }
        },

        _resetDisplayValue: function () {
            // set old value
            this.$ghostInput.val(this.$select.find("option:selected").text());
        },

        _stopEvent: function (event) {
            event.stopPropagation();
            event.preventDefault();
        },

        _selectCompleteTextInGhostInput: function () {
            this.$ghostInput[0].setSelectionRange(0, this.$ghostInput.val().length);
        },

        _isResultContainerShown: function () {
            return this.$resultContainer !== null;
        },

        _scrollToSelectedOptionElementIfNecessary: function () {
            if (this._optionList.hasSelected()) {
                var $item = this._optionList.getSelected().getResultElement();
                var containerHeight = this.$resultContainer.innerHeight();
                var containerScrollTop = this.$resultContainer.scrollTop();
                //console.log("FilterableCombobox - containerHeight: %s", containerHeight);
                //console.log("FilterableCombobox - containerScrollTop: %s", containerScrollTop);
                var itemHeight = $item.outerHeight();
                //console.log("FilterableCombobox - itemHeight: %s", itemHeight);
                var itemTop = $item.position().top;
                //console.log("FilterableCombobox - itemTop: %s", itemTop);

                if (itemTop < 0) {
                    // resultItem is scrolled out to top
                    this.$resultContainer.scrollTop(containerScrollTop + itemTop);
                } else if (itemTop + itemHeight > containerHeight) {
                    // resultItem is scrolled out to bottom
                    this.$resultContainer.scrollTop(containerScrollTop + ((itemTop + itemHeight) - containerHeight));
                }
            }
        }
    });
}(jQuery));<|endoftext|>


<commit_before>/* eslint-disable prefer-template */
import {component} from 'flightjs';
import $ from 'jquery';
import queryString from 'query-string';

export default component(function lookback() {
  this.onChange = function() {
    if (this.$node.val() === 'custom') {
      $('#custom-lookback').show();
    } else {
      $('#custom-lookback').hide();
    }
  };

  this.render = function() {
    const selectedLookback = queryString.parse(window.location.search).lookback;
    this.$node.find('option').each((i, option) => {
      const $option = $(option);
      if ($option.val() === selectedLookback) {
        $option.prop('selected', true);
      }
    });
  };

  this.after('initialize', function() {
    this.render();

    this.on('change', this.onChange);
  });
});
<commit_msg>refresh extra custom date fields on page load (#1929)<commit_after>/* eslint-disable prefer-template */
import {component} from 'flightjs';
import $ from 'jquery';
import queryString from 'query-string';

export default component(function lookback() {
  this.refreshCustomFields = function() {
    if (this.$node.val() === 'custom') {
      $('#custom-lookback').show();
    } else {
      $('#custom-lookback').hide();
    }
  };

  this.render = function() {
    const selectedLookback = queryString.parse(window.location.search).lookback;
    this.$node.find('option').each((i, option) => {
      const $option = $(option);
      if ($option.val() === selectedLookback) {
        $option.prop('selected', true);
      }
    });
  };

  this.after('initialize', function() {
    this.render();
    this.refreshCustomFields();

    this.on('change', this.refreshCustomFields);
  });
});
<|endoftext|>


<commit_before>import { Model, ValidationError, Validator } from 'objection'
import Joi from 'joi'

import { appLogger } from '../hooks/logger'

class JoiValidator extends Validator {
  // eslint-disable-next-line class-methods-use-this
  validate({ model, json }) {
    if (!model.constructor.jsonSchema) {
      appLogger.warn('skipping validation for model ', model)
      return json
    }
    const result = Joi.validate(json, model.constructor.jsonSchema)

    if (result.error) {
      appLogger.error(result.error)
      throw new ValidationError(result.error)
    }
    return result.value
  }
}

export default class BaseModel extends Model {
  static createValidator() {
    return new JoiValidator()
  }

  static virtualAttributes = ['type', 'link']
}

<commit_msg>Bug: Don't validate patch operations (as the are not a complete set of input values)<commit_after>import { Model, ValidationError, Validator } from 'objection'
import Joi from 'joi'

import { appLogger } from '../hooks/logger'

class JoiValidator extends Validator {
  // eslint-disable-next-line class-methods-use-this
  validate({ model, json, options: { patch } }) {
    if (!model.constructor.jsonSchema || patch) {
      return json
    }
    const result = Joi.validate(json, model.constructor.jsonSchema)

    if (result.error) {
      appLogger.error(result.error)
      throw new ValidationError(result.error)
    }
    return result.value
  }
}

export default class BaseModel extends Model {
  static createValidator() {
    return new JoiValidator()
  }

  static virtualAttributes = ['type', 'link']
}
<|endoftext|>


<commit_before>Ext.require([
    'Ext.panel.Panel',
    'Ext.layout.*'
]);

Ext.onReady(function() {

    function getDefaultPanel(x, y, frame) {
        return {
            title: (frame ? 'Framed ' : '') + 'Default Panel',
            layout: 'fit',
            bodyStyle: 'padding:10px;',
            collapsible: true,
            width: 200,
            height: 150,
            x: x,
            y: y,
            frame: frame,

            items: [{
                title: (frame ? 'Framed ' : '') + 'Child Panel',
                cls: 'extro-panel-child',
                frame: frame,
                bodyStyle: 'padding:10px;',
                html: "cls: 'extro-panel-child'"
            }]
        };
    }

    function getCustomPanel(name, cls, x, y, frame) {
        return {
            title: (frame ? 'Framed ' : '') + '"' + name + '" Panel',
            width: 200,
            height: 150,
            x: x,
            y: y,
            frame: frame,
            cls: 'extro-panel-' + cls,
            bodyStyle: 'padding:10px;',
            html: "cls: 'extro-panel-" + cls + "'"
        };
    }

    function getMultiPanel() {
        return {
            title: 'Multi-Nested',
            width: 200,
            height: 150,
            x: 910,
            y: 20,
            layout: 'fit',
            bodyStyle: 'padding:10px;',

            items: [{
                title: 'First Child',
                cls: 'extro-panel-danger',
                layout: 'fit',
                bodyStyle: 'padding:10px;',

                items: [{
                    title: 'Second Child',
                    cls: 'extro-panel-warning'
                }]
            }]
        }
    }

    function getBorderLayoutPanel() {
        return {
            title : 'BorderLayout Panel with Child Styles',
            width : 420,
            height: 350,
            x: 690, y: 190,
            collapsible: true,
            layout: {
                type: 'border',
                padding: 5
            },
            defaults: {
                collapsible: true,
                split: true,
                cls: 'extro-panel-child'
            },

            items: [{
                title: 'North',
                region: 'north',
                html: 'North',
                ctitle: 'North',
                height: 70
            },{
                title: 'South',
                region: 'south',
                html: 'South',
                collapseMode: 'mini',
                height: 70
            },{
                title: 'West',
                region: 'west',
                html: 'West',
                collapseMode: 'mini',
                width: 100
            },{
                title: 'East',
                region: 'east',
                html: 'East',
                width: 100
            },{
                title: 'Center',
                region: 'center',
                id: 'center-region',
                bodyStyle: 'padding:10px;',
                html: 'You can change the panel style dynamically',

                dockedItems: [{
                    xtype: 'toolbar',
                    dock: 'bottom',

                    items: [{
                        xtype: 'cycle',
                        showText: true,
                        menu: {
                            id: 'view-type-menu',
                            items: [{
                                text: 'Default',
                                checked: true
                            },{
                                text: 'Danger'
                            },{
                                text: 'Warning'
                            },{
                                text: 'Inverse'
                            }]
                        },
                        changeHandler: function(cycleBtn, activeItem) {
                            var center = Ext.get('center-region'),
                                cls = 'extro-panel-' + activeItem.text.toLowerCase();

                            // Remove the previous one, if any
                            center.removeCls(center.extroCls);

                            if (cls !== 'default') {
                                center.extroCls = cls;
                                center.addCls(center.extroCls);
                            }
                        }
                    }]
                }]
            }]
        };
    }

    Ext.create('Ext.Viewport', {
        layout: 'border',
        items: [{
            region: 'north',
            height: 80,
            contentEl: 'header',
            border: 0
        },{
            region: 'center',
            layout: 'absolute',
            autoScroll: true,
            border: 0,

            items: [
                // Row 1
                getDefaultPanel(30, 20),
                getCustomPanel('Danger', 'danger', 250, 20),
                getCustomPanel('Warning', 'warning', 470, 20),
                getCustomPanel('Inverse', 'inverse', 690, 20),
                getMultiPanel(),

                // Row 2
                getDefaultPanel(30, 190, true),
                getCustomPanel('Danger', 'danger', 250, 190, true),
                getCustomPanel('Warning', 'warning', 470, 190, true),
                getBorderLayoutPanel()
            ]
        }]
    })
});
<commit_msg>Minor tweak to extro styles example<commit_after>Ext.require([
    'Ext.panel.Panel',
    'Ext.layout.*'
]);

Ext.onReady(function() {

    function getDefaultPanel(x, y, frame) {
        return {
            title: (frame ? 'Framed ' : '') + 'Default Panel',
            layout: 'fit',
            bodyStyle: 'padding:10px;',
            collapsible: true,
            width: 200,
            height: 150,
            x: x,
            y: y,
            frame: frame,

            items: [{
                title: (frame ? 'Framed ' : '') + 'Child Panel',
                cls: 'extro-panel-child',
                frame: frame,
                bodyStyle: 'padding:10px;',
                html: "cls: 'extro-panel-child'"
            }]
        };
    }

    function getCustomPanel(name, cls, x, y, frame) {
        return {
            title: (frame ? 'Framed ' : '') + '"' + name + '" Panel',
            width: 200,
            height: 150,
            x: x,
            y: y,
            frame: frame,
            cls: 'extro-panel-' + cls,
            bodyStyle: 'padding:10px;',
            html: "cls: 'extro-panel-" + cls + "'"
        };
    }

    function getMultiPanel() {
        return {
            title: 'Multi-Nested',
            width: 200,
            height: 150,
            x: 910,
            y: 20,
            layout: 'fit',
            bodyStyle: 'padding:10px;',

            items: [{
                title: 'First Child',
                cls: 'extro-panel-danger',
                layout: 'fit',
                bodyStyle: 'padding:10px;',

                items: [{
                    title: 'Second Child',
                    cls: 'extro-panel-warning'
                }]
            }]
        }
    }

    function getBorderLayoutPanel() {
        return {
            title : 'BorderLayout Panel with Child Styles',
            width : 420,
            height: 350,
            x: 690, y: 190,
            collapsible: true,
            layout: {
                type: 'border',
                padding: 5
            },
            defaults: {
                collapsible: true,
                split: true,
                cls: 'extro-panel-child'
            },

            items: [{
                title: 'North',
                region: 'north',
                html: 'North',
                ctitle: 'North',
                height: 70
            },{
                title: 'South',
                region: 'south',
                html: 'South',
                collapseMode: 'mini',
                height: 70
            },{
                title: 'West',
                region: 'west',
                html: 'West',
                collapseMode: 'mini',
                width: 100
            },{
                title: 'East',
                region: 'east',
                html: 'East',
                width: 100
            },{
                title: 'Center',
                region: 'center',
                id: 'center-region',
                bodyStyle: 'padding:10px;',
                html: 'You can change the panel style dynamically',

                dockedItems: [{
                    xtype: 'toolbar',
                    dock: 'bottom',

                    items: [{
                        xtype: 'cycle',
                        showText: true,
                        menu: {
                            id: 'view-type-menu',
                            items: [{
                                text: 'Default',
                                checked: true
                            },{
                                text: 'Danger'
                            },{
                                text: 'Warning'
                            },{
                                text: 'Inverse'
                            }]
                        },
                        changeHandler: function(cycleBtn, activeItem) {
                            var center = Ext.getCmp('center-region'),
                                cls = 'extro-panel-' + activeItem.text.toLowerCase();

                            // Remove the previous one, if any
                            center.removeCls(center.extroCls);

                            if (cls !== 'Default') {
                                center.extroCls = cls;
                                center.addCls(center.extroCls);
                                center.setTitle(activeItem.text);
                            }
                            else {
                                center.setTitle('Center');
                            }
                        }
                    }]
                }]
            }]
        };
    }

    Ext.create('Ext.Viewport', {
        layout: 'border',
        items: [{
            region: 'north',
            height: 80,
            contentEl: 'header',
            border: 0
        },{
            region: 'center',
            layout: 'absolute',
            autoScroll: true,
            border: 0,

            items: [
                // Row 1
                getDefaultPanel(30, 20),
                getCustomPanel('Danger', 'danger', 250, 20),
                getCustomPanel('Warning', 'warning', 470, 20),
                getCustomPanel('Inverse', 'inverse', 690, 20),
                getMultiPanel(),

                // Row 2
                getDefaultPanel(30, 190, true),
                getCustomPanel('Danger', 'danger', 250, 190, true),
                getCustomPanel('Warning', 'warning', 470, 190, true),
                getBorderLayoutPanel()
            ]
        }]
    })
});
<|endoftext|>


<commit_before>'use strict';

module.exports = function(Chart) {

	var helpers = Chart.helpers;
	// Default config for a category scale
	var defaultConfig = {
		position: 'bottom'
	};

	var DatasetScale = Chart.Scale.extend({
		/**
		* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
		* else fall back to data.labels
		* @private
		*/
		getLabels: function() {
			var data = this.chart.data;
			return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
		},
		// Implement this so that
		determineDataLimits: function() {
			var me = this;
			var labels = me.getLabels();
			me.minIndex = 0;
			me.maxIndex = labels.length - 1;
			var findIndex;

			if (me.options.ticks.min !== undefined) {
				// user specified min value
				findIndex = helpers.indexOf(labels, me.options.ticks.min);
				me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
			}

			if (me.options.ticks.max !== undefined) {
				// user specified max value
				findIndex = helpers.indexOf(labels, me.options.ticks.max);
				me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
			}

			me.min = labels[me.minIndex];
			me.max = labels[me.maxIndex];
		},

		buildTicks: function() {
			var me = this;
			var labels = me.getLabels();
			// If we are viewing some subset of labels, slice the original array
			me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
		},

		getLabelForIndex: function(index, datasetIndex) {
			var me = this;
			var data = me.chart.data;
			var isHorizontal = me.isHorizontal();

			if (data.yLabels && !isHorizontal) {
				return me.getRightValue(data.datasets[datasetIndex].data[index]);
			}
			return me.ticks[index - me.minIndex];
		},

		// Used to get data value locations.  Value can either be an index or a numerical value
		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
			var me = this;
			// 1 is added because we need the length but we have the indexes
			var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);

			// If value is a data object, then index is the index in the data array,
			// not the index of the scale. We need to change that.
			var valueCategory;
			if (value !== undefined && value !== null) {
				valueCategory = me.isHorizontal() ? value.x : value.y;
			}
			if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
				var labels = me.getLabels();
				value = valueCategory || value;
				var idx = labels.indexOf(value);
				index = idx !== -1 ? idx : index;
			}

			if (me.isHorizontal()) {
				var valueWidth = me.width / offsetAmt;
				var widthOffset = (valueWidth * (index - me.minIndex));

				if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
					widthOffset += (valueWidth / 2);
				}

				return me.left + Math.round(widthOffset);
			}
			var valueHeight = me.height / offsetAmt;
			var heightOffset = (valueHeight * (index - me.minIndex));

			if (me.options.gridLines.offsetGridLines && includeOffset) {
				heightOffset += (valueHeight / 2);
			}

			return me.top + Math.round(heightOffset);
		},
		getPixelForTick: function(index, includeOffset) {
			return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
		},
		getValueForPixel: function(pixel) {
			var me = this;
			var value;
			var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
			var horz = me.isHorizontal();
			var valueDimension = (horz ? me.width : me.height) / offsetAmt;

			pixel -= horz ? me.left : me.top;

			if (me.options.gridLines.offsetGridLines) {
				pixel -= (valueDimension / 2);
			}

			if (pixel <= 0) {
				value = 0;
			} else {
				value = Math.round(pixel / valueDimension);
			}

			return value;
		},
		getBasePixel: function() {
			return this.bottom;
		}
	});

	Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);

};
<commit_msg>Remove malformed comment<commit_after>'use strict';

module.exports = function(Chart) {

	var helpers = Chart.helpers;
	// Default config for a category scale
	var defaultConfig = {
		position: 'bottom'
	};

	var DatasetScale = Chart.Scale.extend({
		/**
		* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
		* else fall back to data.labels
		* @private
		*/
		getLabels: function() {
			var data = this.chart.data;
			return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
		},

		determineDataLimits: function() {
			var me = this;
			var labels = me.getLabels();
			me.minIndex = 0;
			me.maxIndex = labels.length - 1;
			var findIndex;

			if (me.options.ticks.min !== undefined) {
				// user specified min value
				findIndex = helpers.indexOf(labels, me.options.ticks.min);
				me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
			}

			if (me.options.ticks.max !== undefined) {
				// user specified max value
				findIndex = helpers.indexOf(labels, me.options.ticks.max);
				me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
			}

			me.min = labels[me.minIndex];
			me.max = labels[me.maxIndex];
		},

		buildTicks: function() {
			var me = this;
			var labels = me.getLabels();
			// If we are viewing some subset of labels, slice the original array
			me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
		},

		getLabelForIndex: function(index, datasetIndex) {
			var me = this;
			var data = me.chart.data;
			var isHorizontal = me.isHorizontal();

			if (data.yLabels && !isHorizontal) {
				return me.getRightValue(data.datasets[datasetIndex].data[index]);
			}
			return me.ticks[index - me.minIndex];
		},

		// Used to get data value locations.  Value can either be an index or a numerical value
		getPixelForValue: function(value, index, datasetIndex, includeOffset) {
			var me = this;
			// 1 is added because we need the length but we have the indexes
			var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);

			// If value is a data object, then index is the index in the data array,
			// not the index of the scale. We need to change that.
			var valueCategory;
			if (value !== undefined && value !== null) {
				valueCategory = me.isHorizontal() ? value.x : value.y;
			}
			if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
				var labels = me.getLabels();
				value = valueCategory || value;
				var idx = labels.indexOf(value);
				index = idx !== -1 ? idx : index;
			}

			if (me.isHorizontal()) {
				var valueWidth = me.width / offsetAmt;
				var widthOffset = (valueWidth * (index - me.minIndex));

				if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
					widthOffset += (valueWidth / 2);
				}

				return me.left + Math.round(widthOffset);
			}
			var valueHeight = me.height / offsetAmt;
			var heightOffset = (valueHeight * (index - me.minIndex));

			if (me.options.gridLines.offsetGridLines && includeOffset) {
				heightOffset += (valueHeight / 2);
			}

			return me.top + Math.round(heightOffset);
		},
		getPixelForTick: function(index, includeOffset) {
			return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
		},
		getValueForPixel: function(pixel) {
			var me = this;
			var value;
			var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
			var horz = me.isHorizontal();
			var valueDimension = (horz ? me.width : me.height) / offsetAmt;

			pixel -= horz ? me.left : me.top;

			if (me.options.gridLines.offsetGridLines) {
				pixel -= (valueDimension / 2);
			}

			if (pixel <= 0) {
				value = 0;
			} else {
				value = Math.round(pixel / valueDimension);
			}

			return value;
		},
		getBasePixel: function() {
			return this.bottom;
		}
	});

	Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);

};
<|endoftext|>


<commit_before>'use strict';

var
	util = require('util'),
	path = require('path'),
	fs = require('fs');

var
	glob = require('glob'),
	isGlob = require('is-glob'),
	slash = require('slash'),
	merge = require('merge'),
	less = require('less'),
	findIndex = require('find-index');

var
	logger = require('../../../logger'),
	utils = require('../../../utils'),
	outfileSource = require('../outfile-source');

var
	Transform = require('stream').Transform,
	CleanCss = require('clean-css');

var
	log = logger.child({component: 'process-style-stream'});

module.exports = ProcessStyleStream;

function ProcessStyleStream (opts) {
	if (!(this instanceof ProcessStyleStream)) return new ProcessStyleStream(opts);

	Transform.call(this, {objectMode: true});

	var base;

	opts = opts || {};
	this.options = opts;
	this._bundles = [];

	log.level(opts.logLevel);

	if (opts.lessPlugins) log.debug(opts.lessPlugins);

	if (opts.outCssFile) {
		base = path.relative(
			opts.outdir,
			path.join(opts.outdir, path.dirname(opts.outCssFile))
		);
	}

	if (!base) base = opts.outdir;

	this._basePath = base;
}

util.inherits(ProcessStyleStream, Transform);

ProcessStyleStream.prototype._transform = function (bundle, nil, next) {
	this._bundles.push(bundle);
	next();
};

ProcessStyleStream.prototype._flush = function (done) {

	var
		bundles = this._bundles,
		stream = this;

	this._processBundles(bundles.slice(), function (err) {

		// as noted further below, this is due to a bug in Less...
		if (err) return process.nextTick(function () {
			utils.streamError(stream, 'failed to compile less: %s', err);
		});

		log.info('done processing all bundle styles');

		bundles.forEach(function (bundle) { stream.push(bundle); });
		stream.push(null);
		done();
	});

};

ProcessStyleStream.prototype._processBundles = function (bundles, done) {

	var
		stream = this,
		bundle = bundles.shift();

	if (!bundle) return this._renderStyles(done);

	this._processBundle(bundle, function () {
		stream._processBundles(bundles, done);
	});
};

ProcessStyleStream.prototype._processBundle = function (bundle, done) {

	var
		stream = this,
		modules = bundle.modules,
		order = bundle.order,
		entries = [],
		entry;

	log.info({bundle: bundle.name}, 'processing bundle style');

	for (var i = 0; i < order.length; ++i) {
		entry = modules[order[i]];
		if (entry.isPackage) {
			if (entry.json.styles) {

				if (entry.styleEntries) {
					log.info({bundle: bundle.name, module: entry.relName},
						'package %s has previously been processed and has %d known %s; ' +
						'we will be able to use this but will also need to re-scan for new files ' +
						'if they are based on dynamic globs',
						entry.relName,
						entry.styleEntries.length,
						entry.styleEntries.length === 1 ? 'entry' : 'entries'
					);
					entries = entries.concat(entry.styleEntries.map(function (style) {
						style.module = entry; return style;
					}));
				} else {

					log.info({bundle: bundle.name, module: entry.relName},
						'package %s has %d style %s',
						entry.relName,
						entry.json.styles.length,
						entry.json.styles.length === 1 ? 'entry' : 'entries'
					);

					entry.styleEntries = entry.json.styles.map(function (style) {
						// note that we will remove the module property later to avoid circular
						// references but is useful in debugging so we can associate output with
						// the correct module and bundle; also for correct pathing
						return {glob: style, module: entry};
					});

					entries = entries.concat(entry.styleEntries);
				}
			}
		}
	}

	this._findFiles(entries.slice(), function () {

		log.debug({bundle: bundle.name}, 'done processing raw style content');

		// here we concatenate the ordered content from the files that were found
		bundle.rawStyle = '';

		entries.forEach(function (entry) {
			bundle.rawStyle += ((bundle.rawStyle ? '\n' : '') + entry.source);
			// here is where we clean up the circular reference
			delete entry.module;
		});

		done();
	});
};

ProcessStyleStream.prototype._findFiles = function (entries, done) {

	var
		entry = entries.shift(),
		stream = this,
		file;

	if (!entry) return done();

	log.info({bundle: entry.module.bundleName, module: entry.module.relName},
		'finding style files associated with module %s in bundle %s',
		entry.module.relName,
		entry.module.bundleName
	);

	// always reset these so we only ever cache and use the correct files and avoid using ones
	// that were removed
	entry.files = [];
	if (!entry.sources) entry.sources = {};

	if (isGlob(entry.glob)) {

		log.debug({bundle: entry.module.bundleName, module: entry.module.relName},
			'determined that entry %s is a glob and must be re-scanned for new or missing files',
			entry.glob
		);

		glob(entry.glob, {cwd: entry.module.fullpath}, function (err, files) {

			var
				i, idx, fp, known;

			if (err) utils.streamError(stream,
				'failed to read files from glob entry %s from module %s in bundle %s: %s',
				entry.glob,
				entry.module.relName,
				entry.module.bundleName,
				err
			);

			log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
				'found %d files associated with style glob pattern %s',
				files.length,
				entry.glob
			);

			// reset the combined source
			entry.source = '';

			entry.files = files.map(function (e) { return path.join(entry.module.fullpath, e); });
			files = entry.files.slice();

			(fp = function () {
				var file = files.shift();
				if (!file) {
					// if we are working with cached entries then there is a chance that we will
					// have unused entries so we try to find and remove them
					known = Object.keys(entry.sources);
					if (known.length > entry.files.length) {
						known.forEach(function (kf) {
							idx = entry.files.indexOf(kf);
							if (idx === -1) {
								log.debug(
									{module: entry.module.relName, bundle: entry.module.bundleName},
									'removing stale style file entry %s',
									kf
								);
								delete entry.sources[kf];
							}
						});
					}

					log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
						'done processing glob entry %s',
						entry.glob
					);
					return stream._findFiles(entries, done);
				}
				stream._getFileContents(file, entry, function (data) {
					entry.sources[file] = data;
					entry.source += ((entry.source ? '\n' : '') + data);
					fp();
				});
			})();
		});

	} else {

		file = path.join(entry.module.fullpath, entry.glob);

		log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
			'attempting to %s file %s',
			entry.source ? 'update if necessary' : 'read',
			file
		);

		entry.files.push(file);
		this._getFileContents(file, entry, function (data) {
			entry.source = entry.sources[file] = typeof data == 'string' ? data : '';
			stream._findFiles(entries, done);
		});
	}
};

ProcessStyleStream.prototype._renderStyles = function (done) {

	log.info('rendering final, cumulative styles');

	var
		bundles = this._bundles,
		opts = this.options,
		src = '',
		cfg = {},
		minifier;

	bundles.forEach(function (bundle) {
		if (bundle.rawStyle) {

			log.info({bundle: bundle.name}, 'using style');

			var tok = bundle.token = '/*bundle=' + bundle.name + '*/';
			src += '\n' + tok;
			src += bundle.rawStyle;
			src += tok + '\n';
		}
	});

	if (!src) {
		log.info('no style to process');
		return done();
	}

	if (opts.lessPlugins) {
		cfg.plugins = opts.lessPlugins.map(function (entry) {

			log.info('using plugin %s', entry.name);

			return new entry.plugin(entry.options);
		});
	}

	less
		.render(src, cfg)
		.then(function (compiled) {

			log.info('done compiling less');

			var css = compiled.css;
			bundles.forEach(function (bundle) {

				if (bundle.rawStyle) {
					var
						start = css.indexOf(bundle.token) + bundle.token.length + 1,
						end = css.lastIndexOf(bundle.token);

					log.debug({bundle: bundle.name}, 'attempting to separate bundle style');

					bundle.style = css.slice(start, end);
					if (opts.production) {
						if (!minifier) {
							minifier = new CleanCss({
								processImport: false,
								rebase: false,
								roundingPrecision: -1,
								keepSpecialComments: 0
							});
						}

						log.debug({bundle: bundle.name}, 'minifying style source');

						bundle.style = minifier.minify(bundle.style).styles;
					}

					log.debug({bundle: bundle.name}, 'done separating style');
				}
			});

			done();
		}, function (err) {
			log.error('there was an error compiling less');
			// there appears to be a bug where Less wraps the error callback handlers so re-throwing
			// is caught by them...
			done(err);
		});

};

ProcessStyleStream.prototype._getFileContents = function (file, entry, done) {

	var
		stream = this,
		opts = this.options,
		base = this._basePath;

	log.debug({file: file, bundle: entry.module.bundleName, module: entry.module.relName},
		'attempting to stat and potentially read file %s',
		file
	);

	fs.stat(file, function (err, stat) {
		if (err) {
			if (opts.strict) {
				utils.streamError(stream,
					'could not stat style file %s from module %s in bundle %s',
					file,
					entry.module.relName,
					entry.module.bundleName
				);
			} else {
				log.warn({file: file, module: entry.module.relName, bundle: entry.module.bundleName},
					'could not find style-file %s',
					file
				);
				return done();
			}
		}

		var prev = entry.module.mtime[file];

		// update the mtime for the newly read file
		entry.module.mtime[file] = stat.mtime.getTime();

		if (prev && prev == entry.module.mtime[file] && entry.sources[file]) {
			log.debug({file: file, module: entry.module.relName, bundle: entry.module.bundleName},
				'determined that the file is current'
			);

			return done(entry.sources[file]);
		}

		log.debug(
			{file: path.relative(opts.cwd, file), module: entry.module.relName, bundle: entry.module.bundleName}, 'reading'
		);

		fs.readFile(file, 'utf8', function (err, data) {
			if (err) utils.streamError(stream,
				'could not read style file %s from module %s in bundle %s',
				file,
				entry.module.relName,
				entry.module.bundleName
			);

			log.debug(
				{file: path.relative(opts.cwd, file), module: entry.module.relName, bundle: entry.module.bundleName}, 'done reading'
			);

			// we need to update all of the paths for the data
			data = translateImportPaths(data, path.dirname(file), path.relative(opts.cwd, file), entry.module);
			data = translateUrlPaths(data, file, entry.module, opts);
			done(data);
		});
	});
}

function translateImportPaths (text, base, file, pkg) {
	text = text.replace(/(\@import\s+(['"])(?!https?)([a-zA-Z0-9\ \/\-\.\@\{\}]+)\2)/g,
		function (match, full, wrap, src) {
			var ret;
			if (!utils.isAbsolute(src)) {
				ret = '@import \'' + (
						// we simply convert the relative path to the actual path
						slash(path.join(base, src))
					) + '\'';
				if (log.debug()) log.debug({file: file, module: pkg.relName, bundle: pkg.bundleName}, 'translating import from %s to %s', full, ret);
				return ret;
			} else return full;
		}
	);
	return text;
}

function translateUrlPaths (data, file, entry, opts) {

	var
		dir = path.dirname(file);

	return data.replace(/url\((?!(['"])?((http|data)\S+))(['"]?)(\S+?)\4\)/g, function (match, n0, n1, n2, n3, sub) {

		var
			actual, ret;

		if (!utils.isAbsolute(sub)) {
			actual = outfileSource(path.join(dir, sub), entry, opts);
			ret = 'url(\'' + actual + '\')';

			if (log.debug()) log.debug(
				{module: entry.relName, file: file, bundle: entry.bundleName},
				'translating url from %s to %s',
				match,
				ret
			);

			return ret;
		}

		return match;
	});
}<commit_msg>ENYO-2062: Disabled CSS restructuring during magnification to prevent collapsing of incompatible selectors into one selector-set. Also several blank-line whitespace cleanups.<commit_after>'use strict';

var
	util = require('util'),
	path = require('path'),
	fs = require('fs');

var
	glob = require('glob'),
	isGlob = require('is-glob'),
	slash = require('slash'),
	merge = require('merge'),
	less = require('less'),
	findIndex = require('find-index');

var
	logger = require('../../../logger'),
	utils = require('../../../utils'),
	outfileSource = require('../outfile-source');

var
	Transform = require('stream').Transform,
	CleanCss = require('clean-css');

var
	log = logger.child({component: 'process-style-stream'});

module.exports = ProcessStyleStream;

function ProcessStyleStream (opts) {
	if (!(this instanceof ProcessStyleStream)) return new ProcessStyleStream(opts);

	Transform.call(this, {objectMode: true});

	var base;

	opts = opts || {};
	this.options = opts;
	this._bundles = [];

	log.level(opts.logLevel);

	if (opts.lessPlugins) log.debug(opts.lessPlugins);

	if (opts.outCssFile) {
		base = path.relative(
			opts.outdir,
			path.join(opts.outdir, path.dirname(opts.outCssFile))
		);
	}

	if (!base) base = opts.outdir;

	this._basePath = base;
}

util.inherits(ProcessStyleStream, Transform);

ProcessStyleStream.prototype._transform = function (bundle, nil, next) {
	this._bundles.push(bundle);
	next();
};

ProcessStyleStream.prototype._flush = function (done) {

	var
		bundles = this._bundles,
		stream = this;

	this._processBundles(bundles.slice(), function (err) {

		// as noted further below, this is due to a bug in Less...
		if (err) return process.nextTick(function () {
			utils.streamError(stream, 'failed to compile less: %s', err);
		});

		log.info('done processing all bundle styles');

		bundles.forEach(function (bundle) { stream.push(bundle); });
		stream.push(null);
		done();
	});

};

ProcessStyleStream.prototype._processBundles = function (bundles, done) {

	var
		stream = this,
		bundle = bundles.shift();

	if (!bundle) return this._renderStyles(done);

	this._processBundle(bundle, function () {
		stream._processBundles(bundles, done);
	});
};

ProcessStyleStream.prototype._processBundle = function (bundle, done) {

	var
		stream = this,
		modules = bundle.modules,
		order = bundle.order,
		entries = [],
		entry;

	log.info({bundle: bundle.name}, 'processing bundle style');

	for (var i = 0; i < order.length; ++i) {
		entry = modules[order[i]];
		if (entry.isPackage) {
			if (entry.json.styles) {

				if (entry.styleEntries) {
					log.info({bundle: bundle.name, module: entry.relName},
						'package %s has previously been processed and has %d known %s; ' +
						'we will be able to use this but will also need to re-scan for new files ' +
						'if they are based on dynamic globs',
						entry.relName,
						entry.styleEntries.length,
						entry.styleEntries.length === 1 ? 'entry' : 'entries'
					);
					entries = entries.concat(entry.styleEntries.map(function (style) {
						style.module = entry; return style;
					}));
				} else {

					log.info({bundle: bundle.name, module: entry.relName},
						'package %s has %d style %s',
						entry.relName,
						entry.json.styles.length,
						entry.json.styles.length === 1 ? 'entry' : 'entries'
					);

					entry.styleEntries = entry.json.styles.map(function (style) {
						// note that we will remove the module property later to avoid circular
						// references but is useful in debugging so we can associate output with
						// the correct module and bundle; also for correct pathing
						return {glob: style, module: entry};
					});

					entries = entries.concat(entry.styleEntries);
				}
			}
		}
	}

	this._findFiles(entries.slice(), function () {

		log.debug({bundle: bundle.name}, 'done processing raw style content');

		// here we concatenate the ordered content from the files that were found
		bundle.rawStyle = '';

		entries.forEach(function (entry) {
			bundle.rawStyle += ((bundle.rawStyle ? '\n' : '') + entry.source);
			// here is where we clean up the circular reference
			delete entry.module;
		});

		done();
	});
};

ProcessStyleStream.prototype._findFiles = function (entries, done) {

	var
		entry = entries.shift(),
		stream = this,
		file;

	if (!entry) return done();

	log.info({bundle: entry.module.bundleName, module: entry.module.relName},
		'finding style files associated with module %s in bundle %s',
		entry.module.relName,
		entry.module.bundleName
	);

	// always reset these so we only ever cache and use the correct files and avoid using ones
	// that were removed
	entry.files = [];
	if (!entry.sources) entry.sources = {};

	if (isGlob(entry.glob)) {

		log.debug({bundle: entry.module.bundleName, module: entry.module.relName},
			'determined that entry %s is a glob and must be re-scanned for new or missing files',
			entry.glob
		);

		glob(entry.glob, {cwd: entry.module.fullpath}, function (err, files) {

			var
				i, idx, fp, known;

			if (err) utils.streamError(stream,
				'failed to read files from glob entry %s from module %s in bundle %s: %s',
				entry.glob,
				entry.module.relName,
				entry.module.bundleName,
				err
			);

			log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
				'found %d files associated with style glob pattern %s',
				files.length,
				entry.glob
			);

			// reset the combined source
			entry.source = '';

			entry.files = files.map(function (e) { return path.join(entry.module.fullpath, e); });
			files = entry.files.slice();

			(fp = function () {
				var file = files.shift();
				if (!file) {
					// if we are working with cached entries then there is a chance that we will
					// have unused entries so we try to find and remove them
					known = Object.keys(entry.sources);
					if (known.length > entry.files.length) {
						known.forEach(function (kf) {
							idx = entry.files.indexOf(kf);
							if (idx === -1) {
								log.debug(
									{module: entry.module.relName, bundle: entry.module.bundleName},
									'removing stale style file entry %s',
									kf
								);
								delete entry.sources[kf];
							}
						});
					}

					log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
						'done processing glob entry %s',
						entry.glob
					);
					return stream._findFiles(entries, done);
				}
				stream._getFileContents(file, entry, function (data) {
					entry.sources[file] = data;
					entry.source += ((entry.source ? '\n' : '') + data);
					fp();
				});
			})();
		});

	} else {

		file = path.join(entry.module.fullpath, entry.glob);

		log.debug({module: entry.module.relName, bundle: entry.module.bundleName},
			'attempting to %s file %s',
			entry.source ? 'update if necessary' : 'read',
			file
		);

		entry.files.push(file);
		this._getFileContents(file, entry, function (data) {
			entry.source = entry.sources[file] = typeof data == 'string' ? data : '';
			stream._findFiles(entries, done);
		});
	}
};

ProcessStyleStream.prototype._renderStyles = function (done) {

	log.info('rendering final, cumulative styles');

	var
		bundles = this._bundles,
		opts = this.options,
		src = '',
		cfg = {},
		minifier;

	bundles.forEach(function (bundle) {
		if (bundle.rawStyle) {

			log.info({bundle: bundle.name}, 'using style');

			var tok = bundle.token = '/*bundle=' + bundle.name + '*/';
			src += '\n' + tok;
			src += bundle.rawStyle;
			src += tok + '\n';
		}
	});

	if (!src) {
		log.info('no style to process');
		return done();
	}

	if (opts.lessPlugins) {
		cfg.plugins = opts.lessPlugins.map(function (entry) {

			log.info('using plugin %s', entry.name);

			return new entry.plugin(entry.options);
		});
	}

	less
		.render(src, cfg)
		.then(function (compiled) {

			log.info('done compiling less');

			var css = compiled.css;
			bundles.forEach(function (bundle) {

				if (bundle.rawStyle) {
					var
						start = css.indexOf(bundle.token) + bundle.token.length + 1,
						end = css.lastIndexOf(bundle.token);

					log.debug({bundle: bundle.name}, 'attempting to separate bundle style');

					bundle.style = css.slice(start, end);
					if (opts.production) {
						if (!minifier) {
							minifier = new CleanCss({
								processImport: false,
								restructuring: false,	// Added because of: ENYO-2062
								rebase: false,
								roundingPrecision: -1,
								keepSpecialComments: 0
							});
						}

						log.debug({bundle: bundle.name}, 'minifying style source');

						bundle.style = minifier.minify(bundle.style).styles;
					}

					log.debug({bundle: bundle.name}, 'done separating style');
				}
			});

			done();
		}, function (err) {
			log.error('there was an error compiling less');
			// there appears to be a bug where Less wraps the error callback handlers so re-throwing
			// is caught by them...
			done(err);
		});

};

ProcessStyleStream.prototype._getFileContents = function (file, entry, done) {

	var
		stream = this,
		opts = this.options,
		base = this._basePath;

	log.debug({file: file, bundle: entry.module.bundleName, module: entry.module.relName},
		'attempting to stat and potentially read file %s',
		file
	);

	fs.stat(file, function (err, stat) {
		if (err) {
			if (opts.strict) {
				utils.streamError(stream,
					'could not stat style file %s from module %s in bundle %s',
					file,
					entry.module.relName,
					entry.module.bundleName
				);
			} else {
				log.warn({file: file, module: entry.module.relName, bundle: entry.module.bundleName},
					'could not find style-file %s',
					file
				);
				return done();
			}
		}

		var prev = entry.module.mtime[file];

		// update the mtime for the newly read file
		entry.module.mtime[file] = stat.mtime.getTime();

		if (prev && prev == entry.module.mtime[file] && entry.sources[file]) {
			log.debug({file: file, module: entry.module.relName, bundle: entry.module.bundleName},
				'determined that the file is current'
			);

			return done(entry.sources[file]);
		}

		log.debug(
			{file: path.relative(opts.cwd, file), module: entry.module.relName, bundle: entry.module.bundleName}, 'reading'
		);

		fs.readFile(file, 'utf8', function (err, data) {
			if (err) utils.streamError(stream,
				'could not read style file %s from module %s in bundle %s',
				file,
				entry.module.relName,
				entry.module.bundleName
			);

			log.debug(
				{file: path.relative(opts.cwd, file), module: entry.module.relName, bundle: entry.module.bundleName}, 'done reading'
			);

			// we need to update all of the paths for the data
			data = translateImportPaths(data, path.dirname(file), path.relative(opts.cwd, file), entry.module);
			data = translateUrlPaths(data, file, entry.module, opts);
			done(data);
		});
	});
}

function translateImportPaths (text, base, file, pkg) {
	text = text.replace(/(\@import\s+(['"])(?!https?)([a-zA-Z0-9\ \/\-\.\@\{\}]+)\2)/g,
		function (match, full, wrap, src) {
			var ret;
			if (!utils.isAbsolute(src)) {
				ret = '@import \'' + (
						// we simply convert the relative path to the actual path
						slash(path.join(base, src))
					) + '\'';
				if (log.debug()) log.debug({file: file, module: pkg.relName, bundle: pkg.bundleName}, 'translating import from %s to %s', full, ret);
				return ret;
			} else return full;
		}
	);
	return text;
}

function translateUrlPaths (data, file, entry, opts) {

	var
		dir = path.dirname(file);

	return data.replace(/url\((?!(['"])?((http|data)\S+))(['"]?)(\S+?)\4\)/g, function (match, n0, n1, n2, n3, sub) {

		var
			actual, ret;

		if (!utils.isAbsolute(sub)) {
			actual = outfileSource(path.join(dir, sub), entry, opts);
			ret = 'url(\'' + actual + '\')';

			if (log.debug()) log.debug(
				{module: entry.relName, file: file, bundle: entry.bundleName},
				'translating url from %s to %s',
				match,
				ret
			);

			return ret;
		}

		return match;
	});
}<|endoftext|>


<commit_before>// courses.js

// Specify strict mode for 'secure' code
'use_strict';

// Import dependencies and initilize global constants

// Web framework tools
const express  = require('express');
const router   = express.Router();

// PostgreSQL client library for Node.js
const pg       = require('pg').native;

// Library for date formatting
const moment   = require('moment');

// Library with lots of functions
const _        = require('underscore');

// PostgreSQL URL and client
const PG_URL = process.env.DATABASE_URL;
const client = new pg.Client(PG_URL);

// =============================================================================
// MIDDLEWARE FOR ALL REQUESTS

client.connect(function(err) {

    // Catch errors
    if (err) console.error(err);

    // GET: list of all courses
    router.get('/', function(req, res, next) {
        const query = client.query('SELECT title, abbr FROM course');
        query.on('end', function(result) {
            const data = result.rows;
            res.json(data);
        });
    });

    // GET: Lookup a course by abbreviation
    router.get('/:abbr', function(req, res, next) {
        const sql = 'SELECT * FROM course ' +
                    'LEFT JOIN section ' +
                    'ON course.id = section.course_id ' +
                    'WHERE course.abbr = $1';

        const query = client.query({
            text: sql,
            values: [req.params.abbr],
            name: 'course join section',
        });

        query.on('end', function(result) {
            const data = result.rows;

            var jsonResponse = {
                title: data[0].title,
                abbr: data[0].abbr,
                credits: data[0].credits,
                description: data[0].description,
                sections: [ ],
            };

        // Disabling JSCS so it wont complain about database column names
        // jscs:disable

        // ---------------------------------------------------------------------
        // Make data output prettier
        data.forEach(function(section, index, array) {

            // Make modifications to section data here
            if (section.days) {
                const days = section.days
                .toString()
                .replace(',', '');
            }

            // Reformat date data for prettiness
            const session = section.session === 'null' ? '' : section.session;
            const startDate = moment(section.start_date)
            .format('YYYY-MM-DD');
            const endDate = moment(section.endDate)
            .format('YYYY-MM-DD');

            // Map JSON key values with data
            jsonResponse.sections[index] = {
                term: section.term,
                startDate: startDate,
                endDate: endDate,
                session: session,
                crn: section.crn,
                sectionNumber: section.section,
                credits: section.credits,
                instructor: section.instructor,
                days: days,
                startTime: section.start_time,
                endTime: section.end_time,
                location: section.location,
                campus: section.campus,
                type: section.type,
                status: section.status,
                enrollmentCap: section.cap,
                enrolled: section.enrolled,
                waitlisted: section.wl_current,
                waitlistCap: section.wl_cap,
                fees: section.fees,
                restrictions: section.restrictions,
                comments: section.comments,
            };
        });

        // jscs:enable

        // Send response back as JSON
        res.json(jsonResponse);

        });
    });
});

module.exports = router;
<commit_msg>Removed lines and unnecessary comments<commit_after>'use_strict';

// Web framework tools
const express  = require('express');
const router   = express.Router();

// PostgreSQL client library for Node.js
const pg       = require('pg').native;

// Library for date formatting
const moment   = require('moment');

// Library with lots of functions
const _        = require('underscore');

// PostgreSQL URL and client
const PG_URL = process.env.DATABASE_URL;
const client = new pg.Client(PG_URL);

// ----- MIDDLEWARE FOR ALL REQUESTS -----

client.connect(function(err) {

    // Catch errors
    if (err) console.error(err);

    // GET: list of all courses
    router.get('/', function(req, res, next) {
        const query = client.query('SELECT title, abbr FROM course');
        query.on('end', function(result) {
            const data = result.rows;
            res.json(data);
        });
    });

    // GET: Lookup a course by abbreviation
    router.get('/:abbr', function(req, res, next) {
        const sql = 'SELECT * FROM course ' +
                    'LEFT JOIN section ' +
                    'ON course.id = section.course_id ' +
                    'WHERE course.abbr = $1';

        const query = client.query({
            text: sql,
            values: [req.params.abbr],
            name: 'course join section',
        });

        query.on('end', function(result) {
            const data = result.rows;

            var jsonResponse = {
                title: data[0].title,
                abbr: data[0].abbr,
                credits: data[0].credits,
                description: data[0].description,
                sections: [ ],
            };

        // Disabling JSCS so it wont complain about database column names
        // jscs:disable

        // ---------------------------------------------------------------------
        // Make data output prettier
        data.forEach(function(section, index, array) {

            // Make modifications to section data here
            if (section.days) {
                const days = section.days
                .toString()
                .replace(',', '');
            }

            // Reformat date data for prettiness
            const session = section.session === 'null' ? '' : section.session;
            const startDate = moment(section.start_date)
            .format('YYYY-MM-DD');
            const endDate = moment(section.endDate)
            .format('YYYY-MM-DD');

            // Map JSON key values with data
            jsonResponse.sections[index] = {
                term: section.term,
                startDate: startDate,
                endDate: endDate,
                session: session,
                crn: section.crn,
                sectionNumber: section.section,
                credits: section.credits,
                instructor: section.instructor,
                days: days,
                startTime: section.start_time,
                endTime: section.end_time,
                location: section.location,
                campus: section.campus,
                type: section.type,
                status: section.status,
                enrollmentCap: section.cap,
                enrolled: section.enrolled,
                waitlisted: section.wl_current,
                waitlistCap: section.wl_cap,
                fees: section.fees,
                restrictions: section.restrictions,
                comments: section.comments,
            };
        });

        // jscs:enable

        // Send response back as JSON
        res.json(jsonResponse);

        });
    });
});

module.exports = router;
<|endoftext|>