<commit_before>
    angular
        .module('openeApp')
        .factory('workflowService', WorkflowService);

    function WorkflowService($http) {

        var service = {
            getWorkflowDefinitions: getWorkflowDefinitions,
            startWorkflow: startWorkflow,
            deleteWorkflow: deleteWorkflow
        };
        return service;

        function getWorkflowDefinitions(exclude){
            var url = "/alfresco/s/api/workflow-definitions";
            if(exclude != undefined && exclude != null){
                url += "?exclude=" + exclude;
            }
            return $http.get(url).then(function(result){
                return result.data;
            });
        }

        /**
         * workflow = {
         *  workflowType, items, assignTo, workflowDueDate, workflowPriority, message, sendEmailNotifications
         * }
         */
        function startWorkflow(workflow){
           var url = "/alfresco/s/api/openesdh/workflow/start";
           return $http.post(url, workflow).then(function(result){
               return result.data;
           });
        }

        function deleteWorkflow(workflowInstanceId, forced){
            var url = "/alfresco/service/api/workflow-instances/"+ workflowInstanceId;
            if(forced){
                url+= "?forced=" + forced;
            }
            return $http.delete(url).then(function(result){
                return result.data;
            });
        }

    }<commit_msg>OPENE-338 Addressing code review remarks.<commit_after>
    angular
        .module('openeApp')
        .factory('workflowService', WorkflowService);

    function WorkflowService($http) {

        var service = {
            getWorkflowDefinitions: getWorkflowDefinitions,
            startWorkflow: startWorkflow,
            deleteWorkflow: deleteWorkflow
        };
        return service;

        function getWorkflowDefinitions(exclude){
            var url = "/alfresco/s/api/workflow-definitions";
            var config = {};
            if(exclude != undefined && exclude != null){
                config.params.exclude = exclude;
            }
            return $http.get(url, config).then(function(result){
                return result.data;
            });
        }

        /**
         * workflow = {
         *  workflowType, items, assignTo, workflowDueDate, workflowPriority, message, sendEmailNotifications
         * }
         */
        function startWorkflow(workflow){
           var url = "/alfresco/s/api/openesdh/workflow/start";
           return $http.post(url, workflow).then(function(result){
               return result.data;
           });
        }

        function deleteWorkflow(workflowInstanceId, forced){
            var url = "/alfresco/service/api/workflow-instances/"+ workflowInstanceId;
            var config = {};
            if(forced){
                config.params.forced = forced;
            }
            return $http.delete(url, config).then(function(result){
                return result.data;
            });
        }

    }<|endoftext|>


<commit_before>/**
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the new BSD license.
 *
 * @author      David Zeller <zellerda01@gmail.com>
 * @license     http://www.opensource.org/licenses/BSD-3-Clause New BSD license
 */
(function($, tokenize){

    $.tokenize = function(opts){

        if(opts == undefined)
        {
            opts = $.fn.tokenize.defaults;
        }

        this.options = opts;
    };

    $.extend($.tokenize.prototype, {

        init: function(element){

            this.el = element;
            this.el.attr('multiple', 'multiple').hide();
            this.createHtml();

        },

        createHtml: function(){

            var $this = this;
            this.mouseOnContainer = false;
            this.tokenDelete = false;

            // Container div
            this.container = $('
')
                .addClass('Tokenize')
                .width(this.el.width());

            // Dropdown
            this.dropdown = $('
')
                .addClass('Dropdown');

            // List container
            this.tokens = $('
')
                .addClass('Tokens')
                .width(this.el.width());

            // List item search
            this.searchItem = $('
');

            // Search input
            this.searchInput = $('<input />')
                .attr('maxlength', this.options.maxChars);

            // Add to HTML
            this.searchItem.append(this.searchInput);
            this.tokens.append(this.searchItem);
            this.container
                .append(this.tokens)
                .append(this.dropdown)
                .insertAfter(this.el);

            this.updateInput();
            this.fillDefaults();

            // Configure dropdown
            this.dropdown.width(this.tokens.width())
                .css('top', this.tokens.outerHeight() - this.tokens.pixels('border-top-width'));

            if(this.options.datas == 'select')
            {
                this.searchInput.bind('click focus', function(){
                    if(!$this.tokenDelete){
                        $this.cleanPendingDelete();
                        $this.fillDropdown();
                        $this.showDropdown();
                    } else {
                        $this.tokenDelete = false;
                    }
                });
            }

            this.tokens.bind('click', function(){
                $this.searchInput.get(0).focus();
            });

            $(document).bind('click', function(){
                if(!$this.mouseOnContainer){
                    $this.closeDropdown();
                }
            });

            this.searchInput.bind('keydown', function(e){
                $this.updateInput();
                $this.keydown(e);
            });

            this.searchInput.bind('keyup', function(e){
                $this.keyup(e);
            });

            this.container.bind('mouseenter', function(){
                $this.mouseOnContainer = true;
            });

            this.container.bind('mouseleave', function(){
                $this.mouseOnContainer = false;
            });

        },

        fillDefaults: function(){

            var $this = this;

            $('option:selected', this.el).each(function(){
                $this.addToken($(this));
            });

        },

        showDropdown: function(){

            this.dropdown.show();
            this.updateDropdown();

        },

        fillDropdown: function(){

            this.cleanDropdown();
            var $this = this;

            $('option', this.el).not(':selected').each(function(){
                $this.addDropdownItem($(this).val(), $(this).html());
            });

        },

        cleanDropdown: function(){

            this.dropdown.html('');

        },

        updateDropdown: function(){

            var item_count = 0, item_height = 0;

            $('li', this.dropdown).each(function(){
                item_count++;
            });

            if(item_count > 0){
                if(item_count >= this.options.size){
                    item_height = $('li:first-child', this.dropdown).outerHeight();
                    this.dropdown.height(item_height * this.options.size);
                } else {
                    this.dropdown.height('inherit');
                }
            } else {
                this.closeDropdown();
            }

        },

        closeDropdown: function(clean){

            if(clean == undefined){
                clean = true;
            }

            this.dropdown.hide();

            if(clean){
                this.cleanDropdown();
            }

        },

        addDropdownItem: function(key, label){

            if($('li[data="' + key + '"]', this.tokens).length){
                return false;
            }

            var item = $('
'), $this = this;

            item.attr('data', key)
                .html(label)
                .bind('click', function(){
                    $this.addToken($(this));
            }).bind('mouseover', function(){
                $(this).addClass('Highlight');
            }).bind('mouseout', function(){
                $('li', $this.dropdown).removeClass('Highlight');
            });

            this.dropdown.append(item);

        },

        cleanInput: function(){

            this.searchInput.val('');
            this.updateInput();

        },

        cleanPendingDelete: function(){

            $('li.PendingDelete', this.tokens).removeClass('PendingDelete');

        },

        addToken: function(el){

            var token = $('
'),
                close = $(''),
                is_new = false,
                $this = this;

            if($('option[value="' + el.attr('data') + '"]', this.el).length){
                $('option[value="' + el.attr('data') + '"]', this.el).attr('selected', 'selected');
            } else if(!el.is('option')) {
                var option = $('<option />')
                    .attr('selected', 'selected')
                    .attr('value', el.attr('data'))
                    .attr('data', 'custom')
                    .html(el.html());

                this.el.append(option);
                is_new = true;
            }

            close.html('×')
                .addClass('Close')
                .bind('click', function(){
                    $this.removeToken(token, true);
            });

            token.addClass('Token');

            if(el.is('option')){
                token.attr('data', el.attr('value'));
            } else {
                token.attr('data', el.attr('data'));
            }

            token.append('' + el.html() + '')
                .prepend(close)
                .insertBefore(this.searchItem);

            if(is_new){
                this.options.onTokenNew(el);
            } else {
                this.options.onTokenAdd(el);
            }

            this.cleanInput();
            this.closeDropdown();

        },

        removeToken: function(token, disable_click){

            if(disable_click == undefined){
                disable_click = false;
            }

            var option = $('option[value="' + token.attr('data') + '"]', this.el);

            if(option.attr('data') == 'custom'){
                option.remove();
            } else {
                option.removeAttr('selected');
            }

            token.remove();
            this.options.onTokenRemove(token);

            this.updateInput();
            this.closeDropdown();

            if(disable_click){
                this.tokenDelete = true;
            }

        },

        updateInput: function(){

            if($('li.Token', this.tokens).length > 0){

                this.searchFieldScale();

            } else {
                // Change input width
                this.searchInput.width(
                    this.tokens.width() -
                    this.searchInput.pixels('padding-left') -
                    this.searchInput.pixels('padding-right')
                );
            }

        },

        searchFieldScale: function(){

            var measure = $('
'), margins;

            measure.css({ position: 'absolute', visibility: 'hidden' })
                .addClass('TokenizeMeasure')
                .html(this.searchInput.val());

            $('body').append(measure);

            margins = this.searchInput.pixels('padding-left') + this.searchInput.pixels('padding-right');
            this.searchInput.width(measure.width() + 25 - margins);

            measure.remove();

        },

        keydown: function(e){

            if(e.keyCode == this.options.validator){
                e.preventDefault();
                this.addCustomToken();
            } else {
                switch(e.keyCode){
                    // Delete
                    case 8:
                        if(this.searchInput.val().length == 0){
                            e.preventDefault();
                            if($('li.Token.PendingDelete', this.tokens).length){
                                this.removeToken($('li.Token.PendingDelete'));
                            } else {
                                $('li.Token:last', this.tokens).addClass('PendingDelete');
                            }

                            this.closeDropdown();
                        }
                        break;

                    // Return
                    case 9:
                    case 13:
                        e.preventDefault();
                        if($('li.Highlight', this.dropdown).length){
                            this.addToken($('li.Highlight', this.dropdown));
                        } else {
                            this.addCustomToken();
                        }
                        this.cleanPendingDelete();
                        break;

                    // ESC
                    case 27:
                        this.cleanInput();
                        this.closeDropdown();
                        this.cleanPendingDelete();
                        break;

                    // Go up
                    case 38:
                        e.preventDefault();
                        this.goUp();
                        break;

                    // Go down
                    case 40:
                        e.preventDefault();
                        this.goDown();
                        break;

                    default:
                        this.cleanPendingDelete();
                        break;
                }
            }

        },

        keyup: function(e){

            if(e.keyCode != this.options.validator){
                switch(e.keyCode){
                    case 9:
                    case 13:
                    case 27:
                    case 38:
                    case 40:
                        break;

                    case 8:
                        if(this.searchInput.val()){
                            this.search();
                        } else {
                            this.closeDropdown();
                        }
                        break;
                    default:
                        this.search();
                        break;
                }
            }

        },

        addCustomToken: function(){

            if(this.options.newElements && this.searchInput.val()){
                if($('li[data="' + this.searchInput.val() + '"]', this.tokens).length){
                    this.cleanInput();
                    return false;
                }

                var li = $('
')
                    .attr('data', this.searchInput.val())
                    .html(this.searchInput.val());

                this.addToken(li);
                this.closeDropdown();
            } else {
                this.cleanInput();
            }

        },

        goUp: function(){

            if($('li.Highlight', this.dropdown).length > 0){
                if(!$('li.Highlight').is('li:first-child')){
                    $('li.Highlight').removeClass('Highlight').prev().addClass('Highlight');
                }
            } else {
                $('li:first', this.dropdown).addClass('Highlight');
            }
        },

        goDown: function(){

            if($('li.Highlight', this.dropdown).length > 0){
                if(!$('li.Highlight').is('li:last-child')){
                    $('li.Highlight').removeClass('Highlight').next().addClass('Highlight');
                }
            } else {
                $('li:first', this.dropdown).addClass('Highlight');
            }
        },

        search: function(){

            var $this = this;

            if(this.options.datas == 'select'){
                var found = false,
                    regexp = new RegExp($this.searchInput.val().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');

                this.cleanDropdown();

                $('option', this.el).not(':selected').each(function(){
                    if(regexp.test($(this).html())){
                        $this.addDropdownItem($(this).attr('value'), $(this).html());
                        found = true;
                    }
                });

                if(found){
                    this.showDropdown();
                    $('li:first', this.dropdown).addClass('Highlight');
                    this.updateDropdown();
                } else {
                    this.closeDropdown();
                }
            } else {
                $.getJSON(this.options.datas, this.options.searchParam + "=" + this.searchInput.val(), function(data){
                    if(data){
                        $this.cleanDropdown();

                        $.each(data, function(key, val){
                            $this.addDropdownItem(key, val);
                        });

                        $this.showDropdown();
                        $('li:first', $this.dropdown).addClass('Highlight');
                        $this.updateDropdown();
                    } else {
                        $this.closeDropdown();
                    }
                });
            }
        }

    });

    $.fn.tokenize = function(options){

        if(options == undefined){
            options = {};
        }

        var opt = $.extend({}, $.fn.tokenize.defaults, options);
        var obj = new $.tokenize(opt);
        obj.init(this);

        $(this).data('tokenize', obj);

        return this;

    };

    $.fn.tokenize.defaults = {

        datas: 'select',
        searchParam: 'search',
        validator: 188,
        newElements: true,
        size: 10,
        maxChars: 50,

        onTokenAdd: function(token){},
        onTokenNew: function(token){},
        onTokenRemove: function(token){}

    };

    $.fn.pixels = function(property) {

        return parseInt(this.css(property).slice(0,-2));

    };

})(jQuery, 'tokenize');<commit_msg>Fix the position off the drop down when field is initialized hidden<commit_after>/**
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the new BSD license.
 *
 * @author      David Zeller <zellerda01@gmail.com>
 * @license     http://www.opensource.org/licenses/BSD-3-Clause New BSD license
 */
(function($, tokenize){

    $.tokenize = function(opts){

        if(opts == undefined)
        {
            opts = $.fn.tokenize.defaults;
        }

        this.options = opts;
    };

    $.extend($.tokenize.prototype, {

        init: function(element){

            this.el = element;
            this.el.attr('multiple', 'multiple').hide();
            this.createHtml();

        },

        createHtml: function(){

            var $this = this;
            this.mouseOnContainer = false;
            this.tokenDelete = false;

            // Container div
            this.container = $('
')
                .addClass('Tokenize')
                .width(this.el.width());

            // Dropdown
            this.dropdown = $('
')
                .addClass('Dropdown');

            // List container
            this.tokens = $('
')
                .addClass('Tokens')
                .width(this.el.width());

            // List item search
            this.searchItem = $('
');

            // Search input
            this.searchInput = $('<input />')
                .attr('maxlength', this.options.maxChars);

            // Add to HTML
            this.searchItem.append(this.searchInput);
            this.tokens.append(this.searchItem);
            this.container
                .append(this.tokens)
                .append(this.dropdown)
                .insertAfter(this.el);

            this.updateInput();
            this.fillDefaults();

            if(this.options.datas == 'select')
            {
                this.searchInput.bind('click focus', function(){
                    if(!$this.tokenDelete){
                        $this.cleanPendingDelete();
                        $this.fillDropdown();
                        $this.showDropdown();
                    } else {
                        $this.tokenDelete = false;
                    }
                });
            }

            this.tokens.bind('click', function(){
                $this.searchInput.get(0).focus();
            });

            $(document).bind('click', function(){
                if(!$this.mouseOnContainer){
                    $this.closeDropdown();
                }
            });

            this.searchInput.bind('keydown', function(e){
                $this.updateInput();
                $this.keydown(e);
            });

            this.searchInput.bind('keyup', function(e){
                $this.keyup(e);
            });

            this.container.bind('mouseenter', function(){
                $this.mouseOnContainer = true;
            });

            this.container.bind('mouseleave', function(){
                $this.mouseOnContainer = false;
            });

        },

        fillDefaults: function(){

            var $this = this;

            $('option:selected', this.el).each(function(){
                $this.addToken($(this));
            });

        },

        showDropdown: function(){

            this.dropdown.show()
                .width(this.tokens.width())
                .css('top', this.tokens.outerHeight() - this.tokens.pixels('border-top-width'));

            this.updateDropdown();

        },

        fillDropdown: function(){

            this.cleanDropdown();
            var $this = this;

            $('option', this.el).not(':selected').each(function(){
                $this.addDropdownItem($(this).val(), $(this).html());
            });

        },

        cleanDropdown: function(){

            this.dropdown.html('');

        },

        updateDropdown: function(){

            var item_count = 0, item_height = 0;

            $('li', this.dropdown).each(function(){
                item_count++;
            });

            if(item_count > 0){
                if(item_count >= this.options.size){
                    item_height = $('li:first-child', this.dropdown).outerHeight();
                    this.dropdown.height(item_height * this.options.size);
                } else {
                    this.dropdown.height('inherit');
                }
            } else {
                this.closeDropdown();
            }

        },

        closeDropdown: function(clean){

            if(clean == undefined){
                clean = true;
            }

            this.dropdown.hide();

            if(clean){
                this.cleanDropdown();
            }

        },

        addDropdownItem: function(key, label){

            if($('li[data="' + key + '"]', this.tokens).length){
                return false;
            }

            var item = $('
'), $this = this;

            item.attr('data', key)
                .html(label)
                .bind('click', function(){
                    $this.addToken($(this));
            }).bind('mouseover', function(){
                $(this).addClass('Highlight');
            }).bind('mouseout', function(){
                $('li', $this.dropdown).removeClass('Highlight');
            });

            this.dropdown.append(item);

        },

        cleanInput: function(){

            this.searchInput.val('');
            this.updateInput();

        },

        cleanPendingDelete: function(){

            $('li.PendingDelete', this.tokens).removeClass('PendingDelete');

        },

        addToken: function(el){

            var token = $('
'),
                close = $(''),
                is_new = false,
                $this = this;

            if($('option[value="' + el.attr('data') + '"]', this.el).length){
                $('option[value="' + el.attr('data') + '"]', this.el).attr('selected', 'selected');
            } else if(!el.is('option')) {
                var option = $('<option />')
                    .attr('selected', 'selected')
                    .attr('value', el.attr('data'))
                    .attr('data', 'custom')
                    .html(el.html());

                this.el.append(option);
                is_new = true;
            }

            close.html('×')
                .addClass('Close')
                .bind('click', function(){
                    $this.removeToken(token, true);
            });

            token.addClass('Token');

            if(el.is('option')){
                token.attr('data', el.attr('value'));
            } else {
                token.attr('data', el.attr('data'));
            }

            token.append('' + el.html() + '')
                .prepend(close)
                .insertBefore(this.searchItem);

            if(is_new){
                this.options.onTokenNew(el);
            } else {
                this.options.onTokenAdd(el);
            }

            this.cleanInput();
            this.closeDropdown();

        },

        removeToken: function(token, disable_click){

            if(disable_click == undefined){
                disable_click = false;
            }

            var option = $('option[value="' + token.attr('data') + '"]', this.el);

            if(option.attr('data') == 'custom'){
                option.remove();
            } else {
                option.removeAttr('selected');
            }

            token.remove();
            this.options.onTokenRemove(token);

            this.updateInput();
            this.closeDropdown();

            if(disable_click){
                this.tokenDelete = true;
            }

        },

        updateInput: function(){

            if($('li.Token', this.tokens).length > 0){

                this.searchFieldScale();

            } else {
                // Change input width
                this.searchInput.width(
                    this.tokens.width() -
                    this.searchInput.pixels('padding-left') -
                    this.searchInput.pixels('padding-right')
                );
            }

        },

        searchFieldScale: function(){

            var measure = $('
'), margins;

            measure.css({ position: 'absolute', visibility: 'hidden' })
                .addClass('TokenizeMeasure')
                .html(this.searchInput.val());

            $('body').append(measure);

            margins = this.searchInput.pixels('padding-left') + this.searchInput.pixels('padding-right');
            this.searchInput.width(measure.width() + 25 - margins);

            measure.remove();

        },

        keydown: function(e){

            if(e.keyCode == this.options.validator){
                e.preventDefault();
                this.addCustomToken();
            } else {
                switch(e.keyCode){
                    // Delete
                    case 8:
                        if(this.searchInput.val().length == 0){
                            e.preventDefault();
                            if($('li.Token.PendingDelete', this.tokens).length){
                                this.removeToken($('li.Token.PendingDelete'));
                            } else {
                                $('li.Token:last', this.tokens).addClass('PendingDelete');
                            }

                            this.closeDropdown();
                        }
                        break;

                    // Return
                    case 9:
                    case 13:
                        e.preventDefault();
                        if($('li.Highlight', this.dropdown).length){
                            this.addToken($('li.Highlight', this.dropdown));
                        } else {
                            this.addCustomToken();
                        }
                        this.cleanPendingDelete();
                        break;

                    // ESC
                    case 27:
                        this.cleanInput();
                        this.closeDropdown();
                        this.cleanPendingDelete();
                        break;

                    // Go up
                    case 38:
                        e.preventDefault();
                        this.goUp();
                        break;

                    // Go down
                    case 40:
                        e.preventDefault();
                        this.goDown();
                        break;

                    default:
                        this.cleanPendingDelete();
                        break;
                }
            }

        },

        keyup: function(e){

            if(e.keyCode != this.options.validator){
                switch(e.keyCode){
                    case 9:
                    case 13:
                    case 27:
                    case 38:
                    case 40:
                        break;

                    case 8:
                        if(this.searchInput.val()){
                            this.search();
                        } else {
                            this.closeDropdown();
                        }
                        break;
                    default:
                        this.search();
                        break;
                }
            }

        },

        addCustomToken: function(){

            if(this.options.newElements && this.searchInput.val()){
                if($('li[data="' + this.searchInput.val() + '"]', this.tokens).length){
                    this.cleanInput();
                    return false;
                }

                var li = $('
')
                    .attr('data', this.searchInput.val())
                    .html(this.searchInput.val());

                this.addToken(li);
                this.closeDropdown();
            } else {
                this.cleanInput();
            }

        },

        goUp: function(){

            if($('li.Highlight', this.dropdown).length > 0){
                if(!$('li.Highlight').is('li:first-child')){
                    $('li.Highlight').removeClass('Highlight').prev().addClass('Highlight');
                }
            } else {
                $('li:first', this.dropdown).addClass('Highlight');
            }
        },

        goDown: function(){

            if($('li.Highlight', this.dropdown).length > 0){
                if(!$('li.Highlight').is('li:last-child')){
                    $('li.Highlight').removeClass('Highlight').next().addClass('Highlight');
                }
            } else {
                $('li:first', this.dropdown).addClass('Highlight');
            }
        },

        search: function(){

            var $this = this;

            if(this.options.datas == 'select'){
                var found = false,
                    regexp = new RegExp($this.searchInput.val().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');

                this.cleanDropdown();

                $('option', this.el).not(':selected').each(function(){
                    if(regexp.test($(this).html())){
                        $this.addDropdownItem($(this).attr('value'), $(this).html());
                        found = true;
                    }
                });

                if(found){
                    this.showDropdown();
                    $('li:first', this.dropdown).addClass('Highlight');
                    this.updateDropdown();
                } else {
                    this.closeDropdown();
                }
            } else {
                $.getJSON(this.options.datas, this.options.searchParam + "=" + this.searchInput.val(), function(data){
                    if(data){
                        $this.cleanDropdown();

                        $.each(data, function(key, val){
                            $this.addDropdownItem(key, val);
                        });

                        $this.showDropdown();
                        $('li:first', $this.dropdown).addClass('Highlight');
                        $this.updateDropdown();
                    } else {
                        $this.closeDropdown();
                    }
                });
            }
        }

    });

    $.fn.tokenize = function(options){

        if(options == undefined){
            options = {};
        }

        var opt = $.extend({}, $.fn.tokenize.defaults, options);
        var obj = new $.tokenize(opt);
        obj.init(this);

        $(this).data('tokenize', obj);

        return this;

    };

    $.fn.tokenize.defaults = {

        datas: 'select',
        searchParam: 'search',
        validator: 188,
        newElements: true,
        size: 10,
        maxChars: 50,

        onTokenAdd: function(token){},
        onTokenNew: function(token){},
        onTokenRemove: function(token){}

    };

    $.fn.pixels = function(property) {

        return parseInt(this.css(property).slice(0,-2));

    };

})(jQuery, 'tokenize');<|endoftext|>


<commit_before>import md5 from 'md5'

function createNewVersionsTable(release) {
  const entries = Object.keys(release.newVersionByPackage)
    .filter(packageName => {
      return (
        release.currentVersionByPackage[packageName] !==
        release.newVersionByPackage[packageName]
      )
    })
    .map(packageName => {
      return `| ${packageName} | ${release.currentVersionByPackage[
        packageName
      ]} | ${release.newVersionByPackage[packageName]} |`
    })

  if (!entries.length) {
    return ''
  }

  return `## Updated packages

| package | from version | to version |
|:---|:---|:---|
${entries.join('\n')}
`
}

function byPackageName(a, b) {
  if (a.packageName > b.packageName) return 1
  else if (a.packageName < b.packageName) return -1
  else return 0
}

const typeHeaders = {
  feat: ':fire: Feature change',
  fix: ':bug: Bug fixes',
  docs: ':paperclip: Documentation',
  chore: ':wrench: Chores',
  style: ':pencil2: Styling',
  refactor: ':mag: Refactors',
  perf: ':runner: Performance',
  test: ':vertical_traffic_light: Tests',
  ts: ':pencil: Typescript',
}

function createChangeTable(type, release) {
  const entries = release.summary[type]
    .reduce((allEntries, summary) => {
      return allEntries.concat(
        summary.commits.map(commit => {
          return {
            packageName: summary.name,
            summary: commit.summary,
            issues: commit.issues,
            hash: commit.hash,
            authorName: commit.author.name,
            authorEmail: commit.author.email,
          }
        })
      )
    }, [])
    .sort(byPackageName)

  return `## ${typeHeaders[type]}
| package | summary | commit | issues | author | |
|:---|:---|:---|:---|:---|---|
${entries
    .map(entry => {
      return `| ${entry.packageName} | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.authorName} | ![${entry.authorName}](https://www.gravatar.com/avatar/${md5(
        entry.authorEmail
      )}?s=40) |`
    })
    .join('\n')}
`
}

function createBreakingTable(type, release) {
  const entries = release.summary[type]
    .reduce((allEntries, summary) => {
      const onlyBreaking = summary.commits.filter(
        commit => commit.breaks.length
      )

      // Mutate in place (easier :))
      summary.commits = summary.commits.filter(commit => !commit.breaks.length)

      return allEntries.concat(
        onlyBreaking.map(commit => {
          return {
            packageName: summary.name,
            summary: commit.summary,
            issues: commit.issues,
            hash: commit.hash,
            authorName: commit.author.name,
            authorEmail: commit.author.email,
          }
        })
      )
    }, [])
    .sort(byPackageName)

  if (!entries.length) {
    return ''
  }

  return `## :rotating_light: Breaking
| package | summary | commit | issues | author | |
|:---|:---|:---|:---|:---|---|
${entries
    .map(entry => {
      return `| ${entry.packageName} | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.authorName} | ![${entry.authorName}](https://www.gravatar.com/avatar/${md5(
        entry.authorEmail
      )}?s=40) |`
    })
    .join('\n')}
`
}

function createOtherTable(release) {
  if (!release.commitsWithoutPackage.length) {
    return ''
  }

  return `## :relieved: Other
| type | summary | commit | issues | author | |
|---|:---|:---|:---|:---|---|
${release.commitsWithoutPackage
    .map(entry => {
      return `| ${entry.type} (${entry.scope ||
        'monorepo'}) | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.author.name} | ![${entry.author
        .email}](https://www.gravatar.com/avatar/${md5(
        entry.author.email
      )}?s=40) |`
    })
    .join('\n')}
`
}

export default release => {
  console.log(release)
  const breaking = Object.keys(release.summary).map(type =>
    createBreakingTable(type, release)
  )
  const changes = Object.keys(release.summary).map(type =>
    createChangeTable(type, release)
  )

  const other = createOtherTable(release)

  return `
${createNewVersionsTable(release)}
${breaking.join('\n')}
${changes.join('\n')}
${other}
`
}
<commit_msg>chore(release): fix gravatar column to show gravatar images<commit_after>import md5 from 'md5'

function createNewVersionsTable(release) {
  const entries = Object.keys(release.newVersionByPackage)
    .filter(packageName => {
      return (
        release.currentVersionByPackage[packageName] !==
        release.newVersionByPackage[packageName]
      )
    })
    .map(packageName => {
      return `| ${packageName} | ${release.currentVersionByPackage[
        packageName
      ]} | ${release.newVersionByPackage[packageName]} |`
    })

  if (!entries.length) {
    return ''
  }

  return `## Updated packages

| package | from version | to version |
|:---|:---|:---|
${entries.join('\n')}
`
}

function byPackageName(a, b) {
  if (a.packageName > b.packageName) return 1
  else if (a.packageName < b.packageName) return -1
  else return 0
}

const typeHeaders = {
  feat: ':fire: Feature change',
  fix: ':bug: Bug fixes',
  docs: ':paperclip: Documentation',
  chore: ':wrench: Chores',
  style: ':pencil2: Styling',
  refactor: ':mag: Refactors',
  perf: ':runner: Performance',
  test: ':vertical_traffic_light: Tests',
  ts: ':pencil: Typescript',
}

function createChangeTable(type, release) {
  const entries = release.summary[type]
    .reduce((allEntries, summary) => {
      return allEntries.concat(
        summary.commits.map(commit => {
          return {
            packageName: summary.name,
            summary: commit.summary,
            issues: commit.issues,
            hash: commit.hash,
            authorName: commit.author.name,
            authorEmail: commit.author.email,
          }
        })
      )
    }, [])
    .sort(byPackageName)

  return `## ${typeHeaders[type]}
| package | summary | commit | issues | author | gravatar |
|:---|:---|:---|:---|:---|---|
${entries
    .map(entry => {
      return `| ${entry.packageName} | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.authorName} | ![${entry.authorName}](https://www.gravatar.com/avatar/${md5(
        entry.authorEmail
      )}?s=40) |`
    })
    .join('\n')}
`
}

function createBreakingTable(type, release) {
  const entries = release.summary[type]
    .reduce((allEntries, summary) => {
      const onlyBreaking = summary.commits.filter(
        commit => commit.breaks.length
      )

      // Mutate in place (easier :))
      summary.commits = summary.commits.filter(commit => !commit.breaks.length)

      return allEntries.concat(
        onlyBreaking.map(commit => {
          return {
            packageName: summary.name,
            summary: commit.summary,
            issues: commit.issues,
            hash: commit.hash,
            authorName: commit.author.name,
            authorEmail: commit.author.email,
          }
        })
      )
    }, [])
    .sort(byPackageName)

  if (!entries.length) {
    return ''
  }

  return `## :rotating_light: Breaking
| package | summary | commit | issues | author | gravatar |
|:---|:---|:---|:---|:---|---|
${entries
    .map(entry => {
      return `| ${entry.packageName} | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.authorName} | ![${entry.authorName}](https://www.gravatar.com/avatar/${md5(
        entry.authorEmail
      )}?s=40) |`
    })
    .join('\n')}
`
}

function createOtherTable(release) {
  if (!release.commitsWithoutPackage.length) {
    return ''
  }

  return `## :relieved: Other
| type | summary | commit | issues | author | gravatar |
|---|:---|:---|:---|:---|---|
${release.commitsWithoutPackage
    .map(entry => {
      return `| ${entry.type} (${entry.scope ||
        'monorepo'}) | ${entry.summary} | ${entry.hash} | ${entry.issues.join(
        ', '
      )} | ${entry.author.name} | ![${entry.author
        .email}](https://www.gravatar.com/avatar/${md5(
        entry.author.email
      )}?s=40) |`
    })
    .join('\n')}
`
}

export default release => {
  console.log(release)
  const breaking = Object.keys(release.summary).map(type =>
    createBreakingTable(type, release)
  )
  const changes = Object.keys(release.summary).map(type =>
    createChangeTable(type, release)
  )

  const other = createOtherTable(release)

  return `
${createNewVersionsTable(release)}
${breaking.join('\n')}
${changes.join('\n')}
${other}
`
}
<|endoftext|>


<commit_before>/*!
Deck JS - deck.menu - v1.0
Copyright (c) 2011 Caleb Troughton
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module adds the methods and key binding to show and hide a menu of all
slides in the deck. The deck menu state is indicated by the presence of a class
on the deck container.
*/
(function($, deck, undefined) {
	var $d = $(document);

	/*
	Extends defaults/options.

	options.classes.menu
		This class is added to the deck container when showing the slide menu.

	options.keys.menu
		The numeric keycode used to toggle between showing and hiding the slide
		menu.

	options.touch.doubletapWindow
		Two consecutive touch events within this number of milliseconds will
		be considered a double tap, and will toggle the menu on touch devices.
	*/
	$.extend(true, $[deck].defaults, {
		classes: {
			menu: 'deck-menu'
		},

		keys: {
			menu: 77 // m
		},

		touch: {
			doubletapWindow: 400
		}
	});

	/*
	jQuery.deck('showMenu')

	Shows the slide menu by adding the class specified by the menu class option
	to the deck container.
	*/
	$[deck]('extend', 'showMenu', function() {
		$[deck]('getContainer').addClass($[deck]('getOptions').classes.menu);
		$[deck]('getContainer').scrollTop($[deck]('getSlide').offset().top);
	});

	/*
	jQuery.deck('hideMenu')

	Hides the slide menu by removing the class specified by the menu class
	option from the deck container.
	*/
	$[deck]('extend', 'hideMenu', function() {
		$[deck]('getContainer').removeClass($[deck]('getOptions').classes.menu);
		$[deck]('getContainer').scrollTop(0);
	});

	/*
	jQuery.deck('toggleMenu')

	Toggles between showing and hiding the slide menu.
	*/
	$[deck]('extend', 'toggleMenu', function() {
		$[deck]('getContainer').hasClass($[deck]('getOptions').classes.menu) ?
		$[deck]('hideMenu') : $[deck]('showMenu');
	});

	$d.bind('deck.init', function() {
		var opts = $[deck]('getOptions'),
		touchEndTime = 0;

		// Bind key events
		$d.bind('keydown.deck', function(e) {
			if (e.which == opts.keys.menu) {
				$[deck]('toggleMenu');
			}
		});

		// Double tap to toggle slide menu for touch devices
		$[deck]('getContainer').bind('touchend.deck', function(e) {
			var now = Date.now();

			if (now - touchEndTime < opts.touch.doubletapWindow) {
				$[deck]('toggleMenu');
			}
			touchEndTime = now;
			e.preventDefault();
		});
	})
	.bind('deck.change', function(e, from, to) {
		var container = $[deck]('getContainer');
		if (container.hasClass($[deck]('getOptions').classes.menu)) {
			container.scrollTop($[deck]('getSlide', to).offset().top);
		}
	});
})(jQuery, 'deck');

<commit_msg>Add checks to menu double tap so it doesn't fire if the user swipes fast<commit_after>/*!
Deck JS - deck.menu - v1.0
Copyright (c) 2011 Caleb Troughton
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module adds the methods and key binding to show and hide a menu of all
slides in the deck. The deck menu state is indicated by the presence of a class
on the deck container.
*/
(function($, deck, undefined) {
	var $d = $(document);

	/*
	Extends defaults/options.

	options.classes.menu
		This class is added to the deck container when showing the slide menu.

	options.keys.menu
		The numeric keycode used to toggle between showing and hiding the slide
		menu.

	options.touch.doubletapWindow
		Two consecutive touch events within this number of milliseconds will
		be considered a double tap, and will toggle the menu on touch devices.
	*/
	$.extend(true, $[deck].defaults, {
		classes: {
			menu: 'deck-menu'
		},

		keys: {
			menu: 77 // m
		},

		touch: {
			doubletapWindow: 400
		}
	});

	/*
	jQuery.deck('showMenu')

	Shows the slide menu by adding the class specified by the menu class option
	to the deck container.
	*/
	$[deck]('extend', 'showMenu', function() {
		$[deck]('getContainer').addClass($[deck]('getOptions').classes.menu);
		$[deck]('getContainer').scrollTop($[deck]('getSlide').offset().top);
	});

	/*
	jQuery.deck('hideMenu')

	Hides the slide menu by removing the class specified by the menu class
	option from the deck container.
	*/
	$[deck]('extend', 'hideMenu', function() {
		$[deck]('getContainer').removeClass($[deck]('getOptions').classes.menu);
		$[deck]('getContainer').scrollTop(0);
	});

	/*
	jQuery.deck('toggleMenu')

	Toggles between showing and hiding the slide menu.
	*/
	$[deck]('extend', 'toggleMenu', function() {
		$[deck]('getContainer').hasClass($[deck]('getOptions').classes.menu) ?
		$[deck]('hideMenu') : $[deck]('showMenu');
	});

	$d.bind('deck.init', function() {
		var opts = $[deck]('getOptions'),
		touchEndTime = 0,
		currentSlide;

		// Bind key events
		$d.bind('keydown.deck', function(e) {
			if (e.which == opts.keys.menu) {
				$[deck]('toggleMenu');
			}
		});

		// Double tap to toggle slide menu for touch devices
		$[deck]('getContainer').bind('touchstart.deck', function(e) {
			currentSlide = $[deck]('getSlide');
		})
		.bind('touchend.deck', function(e) {
			var now = Date.now();

			// Ignore this touch event if it caused a nav change (swipe)
			if (currentSlide !== $[deck]('getSlide')) return;

			if (now - touchEndTime < opts.touch.doubletapWindow) {
				$[deck]('toggleMenu');
				e.preventDefault();
			}
			touchEndTime = now;
		});
	})
	.bind('deck.change', function(e, from, to) {
		var container = $[deck]('getContainer');
		if (container.hasClass($[deck]('getOptions').classes.menu)) {
			container.scrollTop($[deck]('getSlide', to).offset().top);
		}
	});
})(jQuery, 'deck');

<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { Button, Flexbox2 as Flexbox, Icon } from 'bonde-styleguide'
import { ButtonLink } from 'components/Link'
import { authSession } from 'services/auth'

const menuBuilder = (menuName, { community, module }) => ({
  'dashboard': {
    icon: 'chart',
    component: ButtonLink,
    to: `/admin/${community.id}/analytics`
  },
  'chatbot': {
    icon: 'bot',
    component: ButtonLink,
    to: `/admin/${community.id}/chatbot/${module}`
  },
  'mobilization': {
    icon: 'window',
    component: Button,
    onClick: () => {
      authSession
        .setAsyncItem('community', community)
        .then(() => {
          const baseUrl = process.env.REACT_APP_DOMAIN_ADMIN || 'http://app.bonde.devel:5001'
          window.open(baseUrl, '_blank')
        })
    }
  },
  'settings': {
    icon: 'settings',
    component: ButtonLink,
    to: `/admin/${community.id}/settings`
  }
})[menuName]

const CommunityMenu = ({ community, dark, pathname }) => {
  const { modules } = community

  const menus = Object.keys(modules).map((moduleName) => {
    const module = modules[moduleName]
    if (module) {
      return menuBuilder(moduleName, { community, module })
    }
    return null
  }).filter(obj => !!obj)

  return (
    <Flexbox horizontal justify='flex-end'>
      {menus.map(({ component: Component, icon, ...rest }, i) => {
        const ownProps = {
          ...rest, dark, flat: true, active: pathname && pathname.startsWith(rest.to)
        }
        return (
          <Component key={`community-menu-${i}`} {...ownProps}>
            <Icon size={20} name={icon} />
          </Component>
        )
      })}
    </Flexbox>
  )
}

CommunityMenu.propTypes = {
  community: PropTypes.object.isRequired,
  pathname: PropTypes.string,
  dark: PropTypes.bool
}

export default CommunityMenu
<commit_msg>fix(canary): redirect edit community to admin<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import urljoin from 'url-join'
import { Button, Flexbox2 as Flexbox, Icon } from 'bonde-styleguide'
import { ButtonLink } from 'components/Link'
import { authSession } from 'services/auth'

const menuBuilder = (menuName, { community, module }) => ({
  'chatbot': {
    icon: 'bot',
    component: ButtonLink,
    to: `/admin/${community.id}/chatbot/${module}`
  },
  'mobilization': {
    icon: 'window',
    component: Button,
    onClick: () => {
      authSession
        .setAsyncItem('community', community)
        .then(() => {
          const baseUrl = process.env.REACT_APP_DOMAIN_ADMIN || 'http://app.bonde.devel:5001'
          window.open(baseUrl, '_blank')
        })
    }
  },
  'settings': {
    icon: 'settings',
    component: Button,
    onClick: () => {
      authSession
        .setAsyncItem('community', community)
        .then(() => {
          const baseUrl = process.env.REACT_APP_DOMAIN_ADMIN || 'http://app.bonde.devel:5001'
          window.open(urljoin(baseUrl, '/community/info'), '_blank')
        })
    }
  }
})[menuName]

const CommunityMenu = ({ community, dark, pathname }) => {
  const { modules } = community

  const menus = Object.keys(modules).map((moduleName) => {
    const module = modules[moduleName]
    if (module) {
      return menuBuilder(moduleName, { community, module })
    }
    return null
  }).filter(obj => !!obj)

  return (
    <Flexbox horizontal justify='flex-end'>
      {menus.map(({ component: Component, icon, ...rest }, i) => {
        const ownProps = {
          ...rest, dark, flat: true, active: pathname && pathname.startsWith(rest.to)
        }
        return (
          <Component key={`community-menu-${i}`} {...ownProps}>
            <Icon size={20} name={icon} />
          </Component>
        )
      })}
    </Flexbox>
  )
}

CommunityMenu.propTypes = {
  community: PropTypes.object.isRequired,
  pathname: PropTypes.string,
  dark: PropTypes.bool
}

export default CommunityMenu
<|endoftext|>


<commit_before>/**
 * This file is specific to index.html for homepage. It renders the fields
 * on the page dynamically, and it also makes the POST request when the form
 * is submitted.
 */

// TODO(issue/21): get the language from the browser
const CurrentLocale = 'en';

/**
 * Import statements are static so its parameters cannot be dynamic.
 * TODO(issue/22): figure out how to use dynamic imports
 */
import {AppStrings} from './strings.en.js';

const STRINGS = AppStrings['homepage'];
const JOBPAGE_PATH = '../new-job/index.html';
const RESPONSE_ERROR = 'An error occured while getting the job listings';

// TODO(issue/34): implement pagination for job listings
const DEFAULT_PAGE_SIZE = 20;
const DEFAULT_PAGE_INDEX = 0;

window.onload = () => {
  renderHomepageElements();
};

/** Adds all the titles to the fields on this page. */
function renderHomepageElements() {
  const homepageTitle = document.getElementById('homepage-page-title');
  homepageTitle.innerText = STRINGS['homepage-page-title'];

  const newPostButton = document.getElementById('homepage-new-post');
  newPostButton.innerText = STRINGS['homepage-new-post'];
  newPostButton.addEventListener('click', (_) => {
    window.location.href= JOBPAGE_PATH;
  });

  const accountButton = document.getElementById('homepage-account');
  accountButton.innerText = STRINGS['homepage-account'];

  const sortByTitle = document.getElementById('homepage-sort-by-title');
  sortByTitle.innerText = STRINGS['homepage-sort-by-title'];

  renderJobSortOptions();
  renderJobOrderOptions();
  renderJobSortSubmit();

  const filterByTitle = document.getElementById('homepage-filter-by-title');
  filterByTitle.innerText = STRINGS['homepage-filter-by-title'];

  const distanceFilterTitle =
    document.getElementById('homepage-filter-distance-title');
  distanceFilterTitle.innerText = STRINGS['homepage-filter-distance-title'];

  const distanceMin = document.getElementById('homepage-filter-distance-min');
  distanceMin.setAttribute('type', 'number');
  distanceMin.setAttribute('placeholder',
      STRINGS['homepage-filter-distance-min']);

  const distanceMax = document.getElementById('homepage-filter-distance-max');
  distanceMax.setAttribute('type', 'number');
  distanceMax.setAttribute('placeholder',
      STRINGS['homepage-filter-distance-max']);

  const salaryFilterTitle =
    document.getElementById('homepage-filter-salary-title');
  salaryFilterTitle.innerText = STRINGS['homepage-filter-salary-title'];

  const salaryMin = document.getElementById('homepage-filter-salary-min');
  salaryMin.setAttribute('type', 'number');
  salaryMin.setAttribute('placeholder',
      STRINGS['homepage-filter-salary-min']);

  const salaryMax = document.getElementById('homepage-filter-salary-max');
  salaryMax.setAttribute('type', 'number');
  salaryMax.setAttribute('placeholder',
      STRINGS['homepage-filter-salary-max']);
  renderJobFilterSubmit();

  const jobListingsTitle =
    document.getElementById('homepage-job-listings-title');
  jobListingsTitle.innerText = STRINGS['homepage-job-listings-title'];

  const defaultSortBy = document.getElementById('homepage-sort-by').value;
  const defaultSortOrder =
    document.getElementById('homepage-sort-by-order').value;
  renderJobListings(defaultSortBy, defaultSortOrder, DEFAULT_PAGE_SIZE,
      DEFAULT_PAGE_INDEX);

  /** reset the error to make sure no error msg initially present */
  setErrorMessage(/* msg */ '', /** includes default msg */ false);
}

/**
 * Sets the error message according to the param.
 * @param {String} msg the message that the error div should display.
 * @param {boolean} includesDefault whether the default
 * message should be included.
 */
function setErrorMessage(msg, includesDefault) {
  document.getElementById('homepage-error-message').innerText =
    (includesDefault ? STRINGS['homepage-error-message'] + msg : msg);
}

/** Dynamically add the options for sorting the jobs. */
function renderJobSortOptions() {
  const jobSortSelect = document.getElementById('homepage-sort-by');

  renderSelectOptions(jobSortSelect, STRINGS['homepage-sort-by']);
}

/** Dynaimcally add the options for orders of the sorting options. */
function renderJobOrderOptions() {
  const jobOrderSelect = document.getElementById('homepage-sort-by-order');

  renderSelectOptions(jobOrderSelect, STRINGS['homepage-sort-by-order']);
}

/**
 * Add the keys and values from the options map to the select element.
 * @param {Element} select The select element.
 * @param {Map} options The map of options to be added.
 */
function renderSelectOptions(select, options) {
  select.options.length = 0;

  for (const key in options) {
    if (options.hasOwnProperty(key)) {
      select.options[select.options.length] =
        new Option(options[key], key);
    }
  }
}

/**
 * Add the attributes and on click function to the sorting
 * submit button.
 */
function renderJobSortSubmit() {
  const sortBySubmit = document.getElementById('homepage-sort-by-submit');
  sortBySubmit.setAttribute('type', 'submit');
  sortBySubmit.setAttribute('value', STRINGS['homepage-sort-by-submit']);

  sortBySubmit.addEventListener('click', (_) => {
    if (!validSortByInput()) {
      return;
    }

    const sortByParam = document.getElementById('homepage-sort-by').value;
    const sortOrderParam = document.getElementById('homepage-sort-by-order')
        .value;
    renderJobListings(sortByParam, sortOrderParam, DEFAULT_PAGE_SIZE,
        DEFAULT_PAGE_INDEX);
  });
}

/**
 * Add the attributes and on click function to the filters
 * submit button.
 */
function renderJobFilterSubmit() {
  const filterBySubmit = document.getElementById('homepage-filter-by-submit');
  filterBySubmit.setAttribute('type', 'submit');
  filterBySubmit.setAttribute('value', STRINGS['homepage-filter-by-submit']);

  /**
   * TODO(issue/35): add filtering event listener
   * + validation + GET request functions
   */
}

/**
 * Add the list of jobs that are stored in the database.
 * @param {String} sortBy How the jobs should be sorted.
 * @param {String} order The order of the sorting.
 * @param {int} pageSize The number of jobs for the page.
 * @param {int} pageIndex The page index (starting from 0).
 */
async function renderJobListings(sortBy, order, pageSize, pageIndex) {
  const jobListings = await getJobListings(sortBy, order, pageSize, pageIndex)
      .catch((error) => {
        console.log('error', error);
        setErrorMessage(/* msg */ RESPONSE_ERROR,
            /** include default msg */ false);
      });
  const jobListingsElement = document.getElementById('job-listings');

  /** reset the list so we don't render the same jobs twice */
  jobListingsElement.innerHTML = '';
  const jobListingTemplate = document.getElementById('job-listing-template');

  jobListings.forEach((job) => {
    const jobListing =
      jobListingTemplate.cloneNode( /** and child elements */ true);
    jobListing.setAttribute('id', job['jobId']);

    const jobTitle = jobListing.children[0];
    jobTitle.innerText = job['jobTitle'];

    const jobAddress = jobListing.children[1];

    const location = job['jobLocation'];
    const address = location['address'];
    const postalCode = location['postalCode'];
    jobAddress.innerText = address + ', ' + postalCode;

    jobListingsElement.appendChild(jobListing);
  });
}

/**
 * Makes GET request to retrieve all the job listings from the database
 * given the sorting and order. This function is called when the
 * homepage is loaded and also when the sorting is changed.
 * @param {String} sortBy How the jobs should be sorted.
 * @param {String} order The order of the sorting.
 * @param {int} pageSize The number of jobs for the page.
 * @param {int} pageIndex The page index (starting from 0).
 */
function getJobListings(sortBy, order, pageSize, pageIndex) {
  // TODO(issue/18): get jobs from database and render them on screen
  // returning some hardcoded data for now
  const params = 'sortBy=' + sortBy +
    '&order=' + order +
    '&pageSize=' + pageSize +
    '&pageIndex=' + pageIndex;

  fetch('/jobs?' + params)
      .then((response) => response.text())
      .then((data) => {
        console.log('data', data);
        /** reset the error (there might have been an error msg from earlier) */
        setErrorMessage(/* msg */ '', /** include default msg */ false);
        return data['jobList'];
      });
}
<commit_msg>remove todo comments<commit_after>/**
 * This file is specific to index.html for homepage. It renders the fields
 * on the page dynamically, and it also makes the POST request when the form
 * is submitted.
 */

// TODO(issue/21): get the language from the browser
const CurrentLocale = 'en';

/**
 * Import statements are static so its parameters cannot be dynamic.
 * TODO(issue/22): figure out how to use dynamic imports
 */
import {AppStrings} from './strings.en.js';

const STRINGS = AppStrings['homepage'];
const JOBPAGE_PATH = '../new-job/index.html';
const RESPONSE_ERROR = 'An error occured while getting the job listings';

// TODO(issue/34): implement pagination for job listings
const DEFAULT_PAGE_SIZE = 20;
const DEFAULT_PAGE_INDEX = 0;

window.onload = () => {
  renderHomepageElements();
};

/** Adds all the titles to the fields on this page. */
function renderHomepageElements() {
  const homepageTitle = document.getElementById('homepage-page-title');
  homepageTitle.innerText = STRINGS['homepage-page-title'];

  const newPostButton = document.getElementById('homepage-new-post');
  newPostButton.innerText = STRINGS['homepage-new-post'];
  newPostButton.addEventListener('click', (_) => {
    window.location.href= JOBPAGE_PATH;
  });

  const accountButton = document.getElementById('homepage-account');
  accountButton.innerText = STRINGS['homepage-account'];

  const sortByTitle = document.getElementById('homepage-sort-by-title');
  sortByTitle.innerText = STRINGS['homepage-sort-by-title'];

  renderJobSortOptions();
  renderJobOrderOptions();
  renderJobSortSubmit();

  const filterByTitle = document.getElementById('homepage-filter-by-title');
  filterByTitle.innerText = STRINGS['homepage-filter-by-title'];

  const distanceFilterTitle =
    document.getElementById('homepage-filter-distance-title');
  distanceFilterTitle.innerText = STRINGS['homepage-filter-distance-title'];

  const distanceMin = document.getElementById('homepage-filter-distance-min');
  distanceMin.setAttribute('type', 'number');
  distanceMin.setAttribute('placeholder',
      STRINGS['homepage-filter-distance-min']);

  const distanceMax = document.getElementById('homepage-filter-distance-max');
  distanceMax.setAttribute('type', 'number');
  distanceMax.setAttribute('placeholder',
      STRINGS['homepage-filter-distance-max']);

  const salaryFilterTitle =
    document.getElementById('homepage-filter-salary-title');
  salaryFilterTitle.innerText = STRINGS['homepage-filter-salary-title'];

  const salaryMin = document.getElementById('homepage-filter-salary-min');
  salaryMin.setAttribute('type', 'number');
  salaryMin.setAttribute('placeholder',
      STRINGS['homepage-filter-salary-min']);

  const salaryMax = document.getElementById('homepage-filter-salary-max');
  salaryMax.setAttribute('type', 'number');
  salaryMax.setAttribute('placeholder',
      STRINGS['homepage-filter-salary-max']);
  renderJobFilterSubmit();

  const jobListingsTitle =
    document.getElementById('homepage-job-listings-title');
  jobListingsTitle.innerText = STRINGS['homepage-job-listings-title'];

  const defaultSortBy = document.getElementById('homepage-sort-by').value;
  const defaultSortOrder =
    document.getElementById('homepage-sort-by-order').value;
  renderJobListings(defaultSortBy, defaultSortOrder, DEFAULT_PAGE_SIZE,
      DEFAULT_PAGE_INDEX);

  /** reset the error to make sure no error msg initially present */
  setErrorMessage(/* msg */ '', /** includes default msg */ false);
}

/**
 * Sets the error message according to the param.
 * @param {String} msg the message that the error div should display.
 * @param {boolean} includesDefault whether the default
 * message should be included.
 */
function setErrorMessage(msg, includesDefault) {
  document.getElementById('homepage-error-message').innerText =
    (includesDefault ? STRINGS['homepage-error-message'] + msg : msg);
}

/** Dynamically add the options for sorting the jobs. */
function renderJobSortOptions() {
  const jobSortSelect = document.getElementById('homepage-sort-by');

  renderSelectOptions(jobSortSelect, STRINGS['homepage-sort-by']);
}

/** Dynaimcally add the options for orders of the sorting options. */
function renderJobOrderOptions() {
  const jobOrderSelect = document.getElementById('homepage-sort-by-order');

  renderSelectOptions(jobOrderSelect, STRINGS['homepage-sort-by-order']);
}

/**
 * Add the keys and values from the options map to the select element.
 * @param {Element} select The select element.
 * @param {Map} options The map of options to be added.
 */
function renderSelectOptions(select, options) {
  select.options.length = 0;

  for (const key in options) {
    if (options.hasOwnProperty(key)) {
      select.options[select.options.length] =
        new Option(options[key], key);
    }
  }
}

/**
 * Add the attributes and on click function to the sorting
 * submit button.
 */
function renderJobSortSubmit() {
  const sortBySubmit = document.getElementById('homepage-sort-by-submit');
  sortBySubmit.setAttribute('type', 'submit');
  sortBySubmit.setAttribute('value', STRINGS['homepage-sort-by-submit']);

  sortBySubmit.addEventListener('click', (_) => {
    if (!validSortByInput()) {
      return;
    }

    const sortByParam = document.getElementById('homepage-sort-by').value;
    const sortOrderParam = document.getElementById('homepage-sort-by-order')
        .value;
    renderJobListings(sortByParam, sortOrderParam, DEFAULT_PAGE_SIZE,
        DEFAULT_PAGE_INDEX);
  });
}

/**
 * Add the attributes and on click function to the filters
 * submit button.
 */
function renderJobFilterSubmit() {
  const filterBySubmit = document.getElementById('homepage-filter-by-submit');
  filterBySubmit.setAttribute('type', 'submit');
  filterBySubmit.setAttribute('value', STRINGS['homepage-filter-by-submit']);

  /**
   * TODO(issue/35): add filtering event listener
   * + validation + GET request functions
   */
}

/**
 * Add the list of jobs that are stored in the database.
 * @param {String} sortBy How the jobs should be sorted.
 * @param {String} order The order of the sorting.
 * @param {int} pageSize The number of jobs for the page.
 * @param {int} pageIndex The page index (starting from 0).
 */
async function renderJobListings(sortBy, order, pageSize, pageIndex) {
  const jobListings = await getJobListings(sortBy, order, pageSize, pageIndex)
      .catch((error) => {
        console.log('error', error);
        setErrorMessage(/* msg */ RESPONSE_ERROR,
            /** include default msg */ false);
      });
  const jobListingsElement = document.getElementById('job-listings');

  /** reset the list so we don't render the same jobs twice */
  jobListingsElement.innerHTML = '';
  const jobListingTemplate = document.getElementById('job-listing-template');

  jobListings.forEach((job) => {
    const jobListing =
      jobListingTemplate.cloneNode( /** and child elements */ true);
    jobListing.setAttribute('id', job['jobId']);

    const jobTitle = jobListing.children[0];
    jobTitle.innerText = job['jobTitle'];

    const jobAddress = jobListing.children[1];

    const location = job['jobLocation'];
    const address = location['address'];
    const postalCode = location['postalCode'];
    jobAddress.innerText = address + ', ' + postalCode;

    jobListingsElement.appendChild(jobListing);
  });
}

/**
 * Makes GET request to retrieve all the job listings from the database
 * given the sorting and order. This function is called when the
 * homepage is loaded and also when the sorting is changed.
 * @param {String} sortBy How the jobs should be sorted.
 * @param {String} order The order of the sorting.
 * @param {int} pageSize The number of jobs for the page.
 * @param {int} pageIndex The page index (starting from 0).
 */
function getJobListings(sortBy, order, pageSize, pageIndex) {
  const params = 'sortBy=' + sortBy +
    '&order=' + order +
    '&pageSize=' + pageSize +
    '&pageIndex=' + pageIndex;

  fetch('/jobs?' + params)
      .then((response) => response.text())
      .then((data) => {
        console.log('data', data);
        /** reset the error (there might have been an error msg from earlier) */
        setErrorMessage(/* msg */ '', /** include default msg */ false);
        return data['jobList'];
      });
}
<|endoftext|>


<commit_before>// Copyright 2013-2020, University of Colorado Boulder

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *
 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through
 * instances of this class. This class may centralize default behavior or state for Screens in the future, but right
 * now it only allows you to create Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import Shape from '../../kite/js/Shape.js';
import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import Path from '../../scenery/js/nodes/Path.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import joistStrings from './joistStrings.js';
import ScreenIcon from './ScreenIcon.js';
import ScreenIO from './ScreenIO.js';

const screenNamePatternString = joistStrings.a11y.screenNamePattern;
const screenSimPatternString = joistStrings.a11y.screenSimPattern;
const simScreenString = joistStrings.a11y.simScreen;

// constants
const MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
const MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
const NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
const HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
const ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

// Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
  'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

/**
 * @param {function} createModel
 * @param {function:Object } createView - function( model )
 * @param {Object} [options]
 * @constructor
 */
function Screen( createModel, createView, options ) {

  options = merge( {

    // {string|null} name of the sim, as displayed to the user.
    // For single-screen sims, there is no home screen or navigation bar, and null is OK.
    // For multi-screen sims, this must be provided.
    name: null,

    // {Property.<Color|string>} background color of the Screen
    backgroundColorProperty: new Property( 'white' ),

    // {Node|null} icon shown on the home screen. If null, then a default is created.
    // For single-screen sims, there is no home screen and the default is OK.
    homeScreenIcon: null,

    // {boolean} whether to draw a frame around the small icons on home screen
    showUnselectedHomeScreenIconFrame: false,

    // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
    navigationBarIcon: null,

    // {string|null} show a frame around the screen icon when the navbar's background fill is this color
    // 'black', 'white', or null (no frame)
    showScreenIconFrameForNavigationBarFill: null,

    // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
    maxDT: 0.5,

    tandem: Tandem.REQUIRED,
    phetioType: ScreenIO,
    phetioState: false,
    phetioFeatured: true,

    // pdom - The description that is used when interacting with screen icons/buttons in joist.
    // This is often a full but short sentence with a period at the end of it.
    descriptionContent: null
  }, options );

  assert && assert( !options.homeScreenIcon || options.homeScreenIcon instanceof ScreenIcon, 'invalid homeScreenIcon' );
  assert && assert( !options.navigationBarIcon || options.navigationBarIcon instanceof ScreenIcon, 'invalid navigationBarIcon' );

  assert && assert( _.includes( [ 'black', 'white', null ], options.showScreenIconFrameForNavigationBarFill ),
    'invalid showScreenIconFrameForNavigationBarFill: ' + options.showScreenIconFrameForNavigationBarFill );

  PhetioObject.call( this, options );

  // Create a default homeScreenIcon, using the Screen's background color
  if ( !options.homeScreenIcon ) {
    options.homeScreenIcon = new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
      fill: options.backgroundColorProperty.value,
      stroke: 'black'
    } );
  }

  // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
  if ( !options.navigationBarIcon ) {
    options.navigationBarIcon = options.homeScreenIcon;
  }

  // Validate icon sizes
  validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
  validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

  if ( assert && options.tandem.supplied ) {
    assert && assert( _.endsWith( options.tandem.phetioID, 'Screen' ), 'Screen tandems should end with Screen suffix' );
  }

  assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

  // @public
  this.backgroundColorProperty = options.backgroundColorProperty;

  // @public (read-only) {Property<String|null>}
  this.nameProperty = new Property( options.name, {
    phetioType: PropertyIO( NullableIO( StringIO ) ),
    tandem: options.tandem.createTandem( 'nameProperty' ),
    phetioFeatured: true,
    phetioDocumentation: 'The name of the screen. Changing this value will update the screen name for the screen\'s ' +
                         'corresponding button on the navigation bar and home screen, if they exist. May be null for ' +
                         'single-screen simulations.'
  } );

  // @public (read-only)
  this.homeScreenIcon = options.homeScreenIcon;
  this.navigationBarIcon = options.navigationBarIcon;
  this.showUnselectedHomeScreenIconFrame = options.showUnselectedHomeScreenIconFrame;
  this.showScreenIconFrameForNavigationBarFill = options.showScreenIconFrameForNavigationBarFill;

  // @public (read-only, joist)
  this.maxDT = options.maxDT;

  // @private
  this.createModel = createModel;
  this.createView = createView;

  // Construction of the model and view are delayed and controlled to enable features like
  // a) faster loading when only loading certain screens
  // b) showing a loading progress bar <not implemented>
  this._model = null; // @private
  this._view = null;  // @private

  // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
  // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
  // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
  this.activeProperty = new BooleanProperty( true, {
    tandem: options.tandem.createTandem( 'activeProperty' ),
    phetioReadOnly: true,
    phetioDocumentation: 'Indicates whether the screen is currently displayed in the simulation.  For single-screen ' +
                         'simulations, there is only one screen and it is always active.'
  } );

  // @public (a11y) - used to set the ScreenView's descriptionContent. This is a bit of a misnomer because Screen is
  // not a Node subtype, so this is a value property rather than a setter.
  this.descriptionContent = '';
  if ( options.descriptionContent ) {
    this.descriptionContent = options.descriptionContent;
  }
  else if ( this.nameProperty.value ) {
    this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
      name: this.nameProperty.value
    } );
  }
  else {
    this.descriptionContent = simScreenString; // fall back on generic name
  }

  const self = this;
  assert && this.activeProperty.lazyLink( function() {
    assert( self._view, 'isActive should not change before the Screen view has been initialized' );

    // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
    // changed before the view's visibility is set.
    if ( !Tandem.PHET_IO_ENABLED ) {
      assert( !self._view.isVisible(), 'isActive should not change while the Screen view is visible' );
    }
  } );
}

joist.register( 'Screen', Screen );

/**
 * Convenience function to validate the sizes for the home screen icon and navigation bar icon.
 * @param {Node} icon - the icon to validate
 * @param {Dimension2} minimumSize - the minimum allowed size for the icon
 * @param {number} aspectRatio - the required aspect ratio
 * @param {string} name - the name of the icon type (for assert messages)
 */
var validateIconSize = function( icon, minimumSize, aspectRatio, name ) {
  assert && assert( icon.width >= minimumSize.width, `${name} width is too small: ${icon.width} < ${minimumSize.width}` );
  assert && assert( icon.height >= minimumSize.height, `${name} height is too small: ${icon.height} < ${minimumSize.height}` );

  // Validate home screen aspect ratio
  const actualAspectRatio = icon.width / icon.height;
  assert && assert(
    Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
    name + ' has invalid aspect ratio: ' + actualAspectRatio
  );
};

/**
 * For showing ScreenView layoutBounds with 'dev' query parameter.
 * @param {Bounds2} layoutBounds
 * @returns {Node}
 */
const devCreateLayoutBoundsNode = function( layoutBounds ) {
  return new Path( Shape.bounds( layoutBounds ), {
    stroke: 'red',
    lineWidth: 3,
    pickable: false
  } );
};

/**
 * For showing ScreenView visible bounds with 'showVisibleBounds' query parameter.
 * @param {ScreenView} screenView
 * @returns {Node}
 */
const devCreateVisibleBoundsNode = function( screenView ) {
  const path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
    stroke: 'blue',
    lineWidth: 6,
    pickable: false
  } );
  screenView.visibleBoundsProperty.link( function( visibleBounds ) {
    path.shape = Shape.bounds( visibleBounds );
  } );
  return path;
};

inherit( PhetioObject, Screen, {

  // @public
  reset: function() {
    // Background color not reset, as it's a responsibility of the code that changes the property
  },

  // @public - Returns the model (if it has been constructed)
  get model() {
    assert && assert( this._model, 'Model has not yet been constructed' );
    return this._model;
  },

  // @public - Returns the view (if it has been constructed)
  get view() {
    assert && assert( this._view, 'View has not yet been constructed' );
    return this._view;
  },

  /**
   * Initialize the model.
   * @public (joist-internal)
   */
  initializeModel: function() {
    assert && assert( this._model === null, 'there was already a model' );
    this._model = this.createModel();
  },

  /**
   * Initialize the view.
   * @public (joist-internal)
   * @param {string} [simName] - The display name of the sim, used for a11y. Not provided for the home screen.
   * @param {number} [numberOfScreens] - the number of screens in the whole sim.
   */
  initializeView: function( simName, numberOfScreens ) {
    assert && assert( this._view === null, 'there was already a view' );
    this._view = this.createView( this.model );
    this._view.setVisible( false ); // a Screen is invisible until selected

    // Show the home screen's layoutBounds
    if ( phet.chipper.queryParameters.dev ) {
      this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
    }

    // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
    // it should just be equal to what you see.
    if ( phet.chipper.queryParameters.showVisibleBounds ) {
      this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
    }

    // Set the accessible label for the screen. If simName is not provided, then we are creating the home screen.
    if ( simName ) {

      // Single screen sims don't need screen names, instead just show the title of the sim.
      // Using total screens for sim breaks modularity a bit, but it also is needed as that parameter changes the
      // labelling of this screen, see https://github.com/phetsims/joist/issues/496
      if ( numberOfScreens === 1 ) {
        this._view.labelContent = simName;
      }
      else {

        // Like "My Awesome Screen" because "My Awesome" is the name of the screen.
        const screenNameWithScreen = StringUtils.fillIn( screenNamePatternString, {
          name: this.nameProperty.value
        } );

        // initialize proper PDOM labelling for ScreenView
        this._view.labelContent = StringUtils.fillIn( screenSimPatternString, {
          screenName: screenNameWithScreen,
          simName: simName
        } );
      }

      // if there is a screenSummaryNode, then set its intro string now
      this._view.setScreenSummaryIntroString( simName, numberOfScreens );
    }
    assert && this._view.accessibleAudit();
  }
}, {

  // @public
  MINIMUM_HOME_SCREEN_ICON_SIZE: MINIMUM_HOME_SCREEN_ICON_SIZE,
  HOME_SCREEN_ICON_ASPECT_RATIO: HOME_SCREEN_ICON_ASPECT_RATIO,

  // @public
  MINIMUM_NAVBAR_ICON_SIZE: MINIMUM_NAVBAR_ICON_SIZE
} );

export default Screen;<commit_msg>revert unintentional commit, #626<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/**
 * A Screen is the largest chunk of a simulation. (Java sims used the term Module, but that term
 * is too overloaded to use with JavaScript and Git.)
 *
 * When creating a Sim, Screens are supplied as the arguments. They can be specified as object literals or through
 * instances of this class. This class may centralize default behavior or state for Screens in the future, but right
 * now it only allows you to create Sims without using named parameter object literals.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import BooleanProperty from '../../axon/js/BooleanProperty.js';
import Property from '../../axon/js/Property.js';
import PropertyIO from '../../axon/js/PropertyIO.js';
import Dimension2 from '../../dot/js/Dimension2.js';
import Shape from '../../kite/js/Shape.js';
import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import StringUtils from '../../phetcommon/js/util/StringUtils.js';
import Path from '../../scenery/js/nodes/Path.js';
import Rectangle from '../../scenery/js/nodes/Rectangle.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import NullableIO from '../../tandem/js/types/NullableIO.js';
import StringIO from '../../tandem/js/types/StringIO.js';
import joist from './joist.js';
import joistStrings from './joistStrings.js';
import ScreenIO from './ScreenIO.js';

const screenNamePatternString = joistStrings.a11y.screenNamePattern;
const screenSimPatternString = joistStrings.a11y.screenSimPattern;
const simScreenString = joistStrings.a11y.simScreen;

// constants
const MINIMUM_HOME_SCREEN_ICON_SIZE = new Dimension2( 548, 373 );
const MINIMUM_NAVBAR_ICON_SIZE = new Dimension2( 147, 100 );
const NAVBAR_ICON_ASPECT_RATIO = MINIMUM_NAVBAR_ICON_SIZE.width / MINIMUM_NAVBAR_ICON_SIZE.height;
const HOME_SCREEN_ICON_ASPECT_RATIO = MINIMUM_HOME_SCREEN_ICON_SIZE.width / MINIMUM_HOME_SCREEN_ICON_SIZE.height;
const ICON_ASPECT_RATIO_TOLERANCE = 5E-3; // how close to the ideal aspect ratio an icon must be

// Home screen and navigation bar icons must have the same aspect ratio, see https://github.com/phetsims/joist/issues/76
assert && assert( Math.abs( HOME_SCREEN_ICON_ASPECT_RATIO - HOME_SCREEN_ICON_ASPECT_RATIO ) < ICON_ASPECT_RATIO_TOLERANCE,
  'MINIMUM_HOME_SCREEN_ICON_SIZE and MINIMUM_NAVBAR_ICON_SIZE must have the same aspect ratio' );

/**
 * @param {function} createModel
 * @param {function:Object } createView - function( model )
 * @param {Object} [options]
 * @constructor
 */
function Screen( createModel, createView, options ) {

  options = merge( {

    // {string|null} name of the sim, as displayed to the user.
    // For single-screen sims, there is no home screen or navigation bar, and null is OK.
    // For multi-screen sims, this must be provided.
    name: null,

    // {Property.<Color|string>} background color of the Screen
    backgroundColorProperty: new Property( 'white' ),

    // {Node|null} icon shown on the home screen. If null, then a default is created.
    // For single-screen sims, there is no home screen and the default is OK.
    homeScreenIcon: null,

    // {boolean} whether to draw a frame around the small icons on home screen
    showUnselectedHomeScreenIconFrame: false,

    // {Node|null} icon shown in the navigation bar. If null, then the home screen icon will be used, scaled to fit.
    navigationBarIcon: null,

    // {string|null} show a frame around the screen icon when the navbar's background fill is this color
    // 'black', 'white', or null (no frame)
    showScreenIconFrameForNavigationBarFill: null,

    // dt cap in seconds, see https://github.com/phetsims/joist/issues/130
    maxDT: 0.5,

    tandem: Tandem.REQUIRED,
    phetioType: ScreenIO,
    phetioState: false,
    phetioFeatured: true,

    // pdom - The description that is used when interacting with screen icons/buttons in joist.
    // This is often a full but short sentence with a period at the end of it.
    descriptionContent: null
  }, options );

  assert && assert( _.includes( [ 'black', 'white', null ], options.showScreenIconFrameForNavigationBarFill ),
    'invalid showScreenIconFrameForNavigationBarFill: ' + options.showScreenIconFrameForNavigationBarFill );

  PhetioObject.call( this, options );

  // Create a default homeScreenIcon, using the Screen's background color
  if ( !options.homeScreenIcon ) {
    options.homeScreenIcon = new Rectangle( 0, 0, MINIMUM_HOME_SCREEN_ICON_SIZE.width, MINIMUM_HOME_SCREEN_ICON_SIZE.height, {
      fill: options.backgroundColorProperty.value,
      stroke: 'black'
    } );
  }

  // navigationBarIcon defaults to homeScreenIcon, and will be scaled down
  if ( !options.navigationBarIcon ) {
    options.navigationBarIcon = options.homeScreenIcon;
  }

  // Validate icon sizes
  validateIconSize( options.homeScreenIcon, MINIMUM_HOME_SCREEN_ICON_SIZE, HOME_SCREEN_ICON_ASPECT_RATIO, 'homeScreenIcon' );
  validateIconSize( options.navigationBarIcon, MINIMUM_NAVBAR_ICON_SIZE, NAVBAR_ICON_ASPECT_RATIO, 'navigationBarIcon' );

  if ( assert && options.tandem.supplied ) {
    assert && assert( _.endsWith( options.tandem.phetioID, 'Screen' ), 'Screen tandems should end with Screen suffix' );
  }

  assert && assert( !options.backgroundColor, 'Please provide backgroundColorProperty instead' );

  // @public
  this.backgroundColorProperty = options.backgroundColorProperty;

  // @public (read-only) {Property<String|null>}
  this.nameProperty = new Property( options.name, {
    phetioType: PropertyIO( NullableIO( StringIO ) ),
    tandem: options.tandem.createTandem( 'nameProperty' ),
    phetioFeatured: true,
    phetioDocumentation: 'The name of the screen. Changing this value will update the screen name for the screen\'s ' +
                         'corresponding button on the navigation bar and home screen, if they exist. May be null for ' +
                         'single-screen simulations.'
  } );

  // @public (read-only)
  this.homeScreenIcon = options.homeScreenIcon;
  this.navigationBarIcon = options.navigationBarIcon;
  this.showUnselectedHomeScreenIconFrame = options.showUnselectedHomeScreenIconFrame;
  this.showScreenIconFrameForNavigationBarFill = options.showScreenIconFrameForNavigationBarFill;

  // @public (read-only, joist)
  this.maxDT = options.maxDT;

  // @private
  this.createModel = createModel;
  this.createView = createView;

  // Construction of the model and view are delayed and controlled to enable features like
  // a) faster loading when only loading certain screens
  // b) showing a loading progress bar <not implemented>
  this._model = null; // @private
  this._view = null;  // @private

  // @public {Property.<boolean>} indicates whether the Screen is active. Clients can read this, joist sets it.
  // To prevent potential visual glitches, the value should change only while the screen's view is invisible.
  // That is: transitions from false to true before a Screen becomes visible, and from true to false after a Screen becomes invisible.
  this.activeProperty = new BooleanProperty( true, {
    tandem: options.tandem.createTandem( 'activeProperty' ),
    phetioReadOnly: true,
    phetioDocumentation: 'Indicates whether the screen is currently displayed in the simulation.  For single-screen ' +
                         'simulations, there is only one screen and it is always active.'
  } );

  // @public (a11y) - used to set the ScreenView's descriptionContent. This is a bit of a misnomer because Screen is
  // not a Node subtype, so this is a value property rather than a setter.
  this.descriptionContent = '';
  if ( options.descriptionContent ) {
    this.descriptionContent = options.descriptionContent;
  }
  else if ( this.nameProperty.value ) {
    this.descriptionContent = StringUtils.fillIn( screenNamePatternString, {
      name: this.nameProperty.value
    } );
  }
  else {
    this.descriptionContent = simScreenString; // fall back on generic name
  }

  const self = this;
  assert && this.activeProperty.lazyLink( function() {
    assert( self._view, 'isActive should not change before the Screen view has been initialized' );

    // In phet-io mode, the state of a sim can be set without a deterministic order. The activeProperty could be
    // changed before the view's visibility is set.
    if ( !Tandem.PHET_IO_ENABLED ) {
      assert( !self._view.isVisible(), 'isActive should not change while the Screen view is visible' );
    }
  } );
}

joist.register( 'Screen', Screen );

/**
 * Convenience function to validate the sizes for the home screen icon and navigation bar icon.
 * @param {Node} icon - the icon to validate
 * @param {Dimension2} minimumSize - the minimum allowed size for the icon
 * @param {number} aspectRatio - the required aspect ratio
 * @param {string} name - the name of the icon type (for assert messages)
 */
var validateIconSize = function( icon, minimumSize, aspectRatio, name ) {
  assert && assert( icon.width >= minimumSize.width, `${name} width is too small: ${icon.width} < ${minimumSize.width}` );
  assert && assert( icon.height >= minimumSize.height, `${name} height is too small: ${icon.height} < ${minimumSize.height}` );

  // Validate home screen aspect ratio
  const actualAspectRatio = icon.width / icon.height;
  assert && assert(
    Math.abs( aspectRatio - actualAspectRatio ) < ICON_ASPECT_RATIO_TOLERANCE,
    name + ' has invalid aspect ratio: ' + actualAspectRatio
  );
};

/**
 * For showing ScreenView layoutBounds with 'dev' query parameter.
 * @param {Bounds2} layoutBounds
 * @returns {Node}
 */
const devCreateLayoutBoundsNode = function( layoutBounds ) {
  return new Path( Shape.bounds( layoutBounds ), {
    stroke: 'red',
    lineWidth: 3,
    pickable: false
  } );
};

/**
 * For showing ScreenView visible bounds with 'showVisibleBounds' query parameter.
 * @param {ScreenView} screenView
 * @returns {Node}
 */
const devCreateVisibleBoundsNode = function( screenView ) {
  const path = new Path( Shape.bounds( screenView.visibleBoundsProperty.value ), {
    stroke: 'blue',
    lineWidth: 6,
    pickable: false
  } );
  screenView.visibleBoundsProperty.link( function( visibleBounds ) {
    path.shape = Shape.bounds( visibleBounds );
  } );
  return path;
};

inherit( PhetioObject, Screen, {

  // @public
  reset: function() {
    // Background color not reset, as it's a responsibility of the code that changes the property
  },

  // @public - Returns the model (if it has been constructed)
  get model() {
    assert && assert( this._model, 'Model has not yet been constructed' );
    return this._model;
  },

  // @public - Returns the view (if it has been constructed)
  get view() {
    assert && assert( this._view, 'View has not yet been constructed' );
    return this._view;
  },

  /**
   * Initialize the model.
   * @public (joist-internal)
   */
  initializeModel: function() {
    assert && assert( this._model === null, 'there was already a model' );
    this._model = this.createModel();
  },

  /**
   * Initialize the view.
   * @public (joist-internal)
   * @param {string} [simName] - The display name of the sim, used for a11y. Not provided for the home screen.
   * @param {number} [numberOfScreens] - the number of screens in the whole sim.
   */
  initializeView: function( simName, numberOfScreens ) {
    assert && assert( this._view === null, 'there was already a view' );
    this._view = this.createView( this.model );
    this._view.setVisible( false ); // a Screen is invisible until selected

    // Show the home screen's layoutBounds
    if ( phet.chipper.queryParameters.dev ) {
      this._view.addChild( devCreateLayoutBoundsNode( this._view.layoutBounds ) );
    }

    // For debugging, make it possible to see the visibleBounds.  This is not included with ?dev since
    // it should just be equal to what you see.
    if ( phet.chipper.queryParameters.showVisibleBounds ) {
      this._view.addChild( devCreateVisibleBoundsNode( this._view ) );
    }

    // Set the accessible label for the screen. If simName is not provided, then we are creating the home screen.
    if ( simName ) {

      // Single screen sims don't need screen names, instead just show the title of the sim.
      // Using total screens for sim breaks modularity a bit, but it also is needed as that parameter changes the
      // labelling of this screen, see https://github.com/phetsims/joist/issues/496
      if ( numberOfScreens === 1 ) {
        this._view.labelContent = simName;
      }
      else {

        // Like "My Awesome Screen" because "My Awesome" is the name of the screen.
        const screenNameWithScreen = StringUtils.fillIn( screenNamePatternString, {
          name: this.nameProperty.value
        } );

        // initialize proper PDOM labelling for ScreenView
        this._view.labelContent = StringUtils.fillIn( screenSimPatternString, {
          screenName: screenNameWithScreen,
          simName: simName
        } );
      }

      // if there is a screenSummaryNode, then set its intro string now
      this._view.setScreenSummaryIntroString( simName, numberOfScreens );
    }
    assert && this._view.accessibleAudit();
  }
}, {

  // @public
  MINIMUM_HOME_SCREEN_ICON_SIZE: MINIMUM_HOME_SCREEN_ICON_SIZE,
  HOME_SCREEN_ICON_ASPECT_RATIO: HOME_SCREEN_ICON_ASPECT_RATIO,

  // @public
  MINIMUM_NAVBAR_ICON_SIZE: MINIMUM_NAVBAR_ICON_SIZE
} );

export default Screen;<|endoftext|>


<commit_before>import express from 'express'
import path from 'path'
import proxy from 'proxy-middleware'
import webpack from 'webpack'
import config from '../webpack.config.dev'
import { PORT as API_PORT } from './api-server'

const app = express()
const compiler = webpack(config)

const PORT = 3333

app.use(require('webpack-hot-middleware')(compiler))
app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath,
  stats: {
    colors: true
  }
}))

app.use(express.static('public'))
app.use('/api', proxy(`http://localhost:${API_PORT}`))

app.get('*', function(req, res) {
  res.sendFile(path.join(__dirname, '../public', 'index.html'))
})

app.listen(PORT, 'localhost', (err) => {
  if (err) {
    console.log(err)
    return
  }

  console.log(`dev-server listening at http://localhost:${PORT}`)
})
<commit_msg>Arrow fn<commit_after>import express from 'express'
import path from 'path'
import proxy from 'proxy-middleware'
import webpack from 'webpack'
import config from '../webpack.config.dev'
import { PORT as API_PORT } from './api-server'

const app = express()
const compiler = webpack(config)

const PORT = 3333

app.use(require('webpack-hot-middleware')(compiler))
app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath,
  stats: {
    colors: true
  }
}))

app.use(express.static('public'))
app.use('/api', proxy(`http://localhost:${API_PORT}`))

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../public', 'index.html'))
})

app.listen(PORT, 'localhost', (err) => {
  if (err) {
    console.log(err)
    return
  }

  console.log(`dev-server listening at http://localhost:${PORT}`)
})
<|endoftext|>