<commit_before>// --
// Core.AJAX.js - provides the funcionality for AJAX calls
// Copyright (C) 2001-2010 OTRS AG, http://otrs.org/\n";
// --
// $Id: Core.AJAX.js,v 1.11 2010-09-23 13:23:01 mn Exp $
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (AGPL). If you
// did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
// --

"use strict";

var Core = Core || {};

/**
 * @namespace
 * @exports TargetNS as Core.AJAX
 * @description
 *      This namespace contains the functionality for AJAX calls.
 */
Core.AJAX = (function (TargetNS) {
    var AJAXLoaderPrefix = 'AJAXLoader',
        ActiveAJAXCalls = {};

    /**
     * @function
     * @private
     * @param {string} FieldID Id of the field which is updated via ajax
     * @description Shows and hides an ajax loader for every element which is updates via ajax
     */
    function ToggleAJAXLoader(FieldID) {
        var $Element = $('#' + FieldID),
            $Loader = $('#' + AJAXLoaderPrefix + FieldID),
            LoaderHTML = '';

        if (!$Loader.length) {
            if ( $Element.not('[type=hidden]').length ) {
                $Element.after(LoaderHTML);
                if (typeof ActiveAJAXCalls[FieldID] === 'undefined') {
                    ActiveAJAXCalls[FieldID] = 0;
                }
                ActiveAJAXCalls[FieldID]++;
            }
        }
        else if ($Loader.is(':hidden')) {
            $Loader.show();
            if (typeof ActiveAJAXCalls[FieldID] === 'undefined') {
                ActiveAJAXCalls[FieldID] = 0;
            }
            ActiveAJAXCalls[FieldID]++;
        }
        else {
            ActiveAJAXCalls[FieldID]--;
            if (ActiveAJAXCalls[FieldID] <= 0) {
                $Loader.hide();
                ActiveAJAXCalls[FieldID] = 0;
            }
        }
    }

    /**
     * @function
     * @private
     * @param {Object} Data The data that should be converted
     * @return {string} query string of the data
     * @description Converts a given hash into a query string
     */
    function SerializeData(Data) {
        var QueryString = "";
        $.each(Data, function (Key, Value) {
            QueryString += Key + '=' + encodeURIComponent(Value) + ";";
        });
        return QueryString;
    }

    /**
     * @function
     * @private
     * @return {Object} Hash with session data, if needed
     * @description Collects session data in a hash if available
     */
    function GetSessionInformation() {
        var Data = {};
        if (!Core.Config.Get('SessionIDCookie')) {
            Data[Core.Config.Get('SessionName')] = Core.Config.Get('SessionID');
            Data[Core.Config.Get('CustomerPanelSessionName')] = Core.Config.Get('SessionID');
        }
        return Data;
    }

    /**
     * @function
     * @private
     * @return {Object} Hash with additional session and action data
     * @description Collects additional data that are needed for the ajax requests
     */
    function GetAdditionalDefaultData() {
        var Data = {};
        Data = GetSessionInformation();
        Data.Action = Core.Config.Get('Action');
        return Data;
    }

    /**
     * @function
     * @private
     * @param {Object} Data The new field data
     * @param {Object} FieldsToUpdate The array of field elements
     * @return nothing
     * @description Updates the given fields with the given data
     */
    function UpdateFormElements(Data, FieldsToUpdate) {
        $.each(FieldsToUpdate, function (Index, Value) {
            var $Element = $('#' + Value),
                ElementData;
            if ($Element.length && Data) {
                // Select elements
                if ($Element.is('select')) {
                    ElementData = Data[Value];
                    if (ElementData) {
                        $Element.empty();
                        $.each(ElementData, function (Index, Value) {
                            var NewOption = new Option(Value[1], Value[0], Value[2], Value[3]);
                            // overwrite option text, because of wrong html quoting of text content
                            // needed for IE
                            NewOption.innerHTML = Value[1];
                            $Element.append(NewOption);
                        });
                    }
                }
                // Other form elements
                else {
                    if (Data[Value]) {
                        $Element.val(Value);
                    }
                }
            }
        });
    }

    /**
     * @function
     *      Serializes the form data into a query string
     * @param {jQueryObject} $Element The jQuery object of the form  or any element within this form that should be serialized
     * @param {Object} Data Elements (Keys) which should not be included in the serialized form string (optional)
     * @return {string} The query string
     */
    TargetNS.SerializeForm = function ($Element, Data) {
        var QueryString = "";
        if (typeof Data === 'undefined') {
            Data = {};
        }
        if (isJQueryObject($Element) && $Element.length) {
            $Element = $Element.closest('form');
            $Element.find('input:not(:file), textarea, select').filter(':not([disabled=disabled])').each(function () {
                // only add element to the string, if there is no key in the data hash with the same name
                if (typeof Data[$(this).attr('name')] === 'undefined') {
                    if ($(this).is(':checkbox, :radio')) {
                        QueryString += $(this).attr('name') + '=' + encodeURIComponent(($(this).is(':checked') ? ($(this).val() || 'on') : '')) + ";";
                    }
                    else {
                        QueryString += $(this).attr('name') + '=' + encodeURIComponent($(this).val()) + ";";
                    }
                }
            });
        }
        return QueryString;
    };

    /**
     * @function
     *      Submits a special form via ajax and updates the form with the data returned from the server
     * @param {jQueryObject} $EventElement The jQuery object of the element(s) which are included in the form that should be submitted
     * @param {String} Subaction The subaction parameter for the perl module
     * @param {String} ChangedElement The name of the element which was changed by the user
     * @param {Object} FieldsToUpdate The names of the fields that should be updated with the server answer
     * @param {Function} [SuccessCallback] Callback function to be executed on AJAX success (optional).
     * @return nothing
     */
    TargetNS.FormUpdate = function ($EventElement, Subaction, ChangedElement, FieldsToUpdate, SuccessCallback) {
        var URL = Core.Config.Get('Baselink'),
            Data = GetAdditionalDefaultData(),
            QueryString;

        Data.Subaction = Subaction;
        Data.ElementChanged = ChangedElement;
        QueryString = TargetNS.SerializeForm($EventElement, Data) + SerializeData(Data);

        $.each(FieldsToUpdate, function (Index, Value) {
            ToggleAJAXLoader(Value);
        });

        $.ajax({
            url: URL,
            data: QueryString,
            dataType: 'json',
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("Invalid JSON from: " + URL, 'CommunicationError');
                }
                else {
                    UpdateFormElements(Response, FieldsToUpdate);
                    if (typeof SuccessCallback === 'function') {
                        SuccessCallback();
                    }
                }
            },
            complete: function () {
                $.each(FieldsToUpdate, function (Index, Value) {
                    ToggleAJAXLoader(Value);
                });
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });

        return false;
    };

    /**
     * @function
     *      Binds an event on the form element to call the FormUpdate on event trigger
     * @param {jQueryObject} $EventElement The jQuery object of the element(s) which are included in the form that should be submitted
     * @param {String} Subaction The subaction parameter for the perl module
     * @param {String} ChangedElement The name of the element which was changed by the user
     * @param {Object} FieldsToUpdate The names of the fields that should be updated with the server answer
     * @return nothing
     */
    TargetNS.RegisterFormUpdate = function (EventType, $EventElement, Subaction, ChangedElement, FieldsToUpdate) {
        if ($EventElement.length) {
            $EventElement.bind(EventType, function () {
                TargetNS.AJAXFormUpdate($EventElement, Subaction, ChangedElement, FieldsToUpdate);
            });
        }
    };

    /**
     * @function
     *      Calls an URL via Ajax and updates a html element with the answer html of the server
     * @param {jQueryObject} $ElementToUpdate The jQuery object of the element(s) which should be updated
     * @param {String} URL The URL which is called via Ajax
     * @param {Function} Callback The additional callback function which is called after the request returned from the server
     * @return nothing
     */
    TargetNS.ContentUpdate = function ($ElementToUpdate, URL, Callback) {
        var QueryString, QueryIndex = URL.indexOf("?");

        if (QueryIndex >= 0) {
            QueryString = URL.substr(QueryIndex + 1);
            URL = URL.substr(0, QueryIndex);
        }
        QueryString += SerializeData(GetSessionInformation());

        $.ajax({
            url: URL,
            data: QueryString,
            dataType: 'html',
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("No content from: " + URL, 'CommunicationError');
                }
                else if ($ElementToUpdate && isJQueryObject($ElementToUpdate) && $ElementToUpdate.length) {
                    $ElementToUpdate.html(Response);
                }
                else {
                    Core.Exception.Throw("No such element id: " + $ElementToUpdate.attr('id') + " in page!", 'CommunicationError');
                }
            },
            complete: function () {
                if ($.isFunction(Callback)) {
                    Callback();
                }
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });

        return false;
    };

    /**
     * @function
     *      Binds an event on a given element to call The ContentUpdate on event trigger
     * @param {String} EventType The type of event for which the listener is defined ('click', 'change', ...)
     * @param {jQueryObject} $EventElement The jQuery object of the element(s) on which the event listener is defined
     * @param {jQueryObject} $ElementToUpdate The jQuery object of the element(s) which should be updated
     * @param {String} URL The URL which is called via Ajax
     * @param {Function} Callback The additional callback function which is called after the request returned from the server
     * @return nothing
     */
    TargetNS.RegisterContentUpdate = function (EventType, $EventElement, $ElementToUpdate, URL, Callback) {
        if ($EventElement.length) {
            $EventElement.bind(EventType, function () {
                TargetNS.AJAXContentUpdate($ElementToUpdate, URL, Callback);
            });
        }
    };

    /**
     * @function
     *      Calls an URL via Ajax and executes a given function after the request returned from the server
     * @param {String} URL The URL which is called via Ajax
     * @param {Object} Data The data hash or data query string
     * @param {Function} Callback The callback function which is called after the request returned from the server
     * @param {String} DataType Optional, defines teh datatype, default 'json', could also be 'html'
     * @return nothing
     */
    TargetNS.FunctionCall = function (URL, Data, Callback, DataType) {
        if (typeof Data === 'string') {
            Data += SerializeData(GetSessionInformation());
        } else {
            Data = $.extend(Data, GetSessionInformation());
        }
        $.ajax({
            type: 'POST',
            url: URL,
            data: Data,
            dataType: (typeof DataType === 'undefined') ? 'json' : DataType,
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("No content from: " + URL, 'CommunicationError');
                }
                // call the callback
                if ($.isFunction(Callback)) {
                    Callback(Response);
                }
                else {
                    Core.Exception.Throw("Invalid callback method: " + ((typeof Callback === 'undefined') ? 'undefined' : Callback.toString()), 'CommunicationError');
                }
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });
    };

    /**
     * @function
     *      Binds an event on a given element to call the FunctionCall ajax function on event trigger
     * @param {String} EventType The type of event for which the listener is defined ('click', 'change', ...)
     * @param {jQueryObject} $EventElement The jQuery object of the element(s) on which the event listener is defined
     * @param {String} URL The URL which is called via Ajax
     * @param {Object} Data The data hash
     * @param {Function} Callback The callback function which is called after the request returned from the server
     * @return nothing
     */
    TargetNS.RegisterFunctionCall = function (EventType, $EventElement, URL, Data, Callback) {
        if ($EventElement.length) {
            $EventElement.bind(EventType, function () {
                TargetNS.AJAXFunctionCall(URL, Data, Callback);
            });
        }
    };

    return TargetNS;
}(Core.AJAX || {}));<commit_msg>Removed unused functions.<commit_after>// --
// Core.AJAX.js - provides the funcionality for AJAX calls
// Copyright (C) 2001-2010 OTRS AG, http://otrs.org/\n";
// --
// $Id: Core.AJAX.js,v 1.12 2010-10-05 09:33:06 mg Exp $
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (AGPL). If you
// did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
// --

"use strict";

var Core = Core || {};

/**
 * @namespace
 * @exports TargetNS as Core.AJAX
 * @description
 *      This namespace contains the functionality for AJAX calls.
 */
Core.AJAX = (function (TargetNS) {
    var AJAXLoaderPrefix = 'AJAXLoader',
        ActiveAJAXCalls = {};

    /**
     * @function
     * @private
     * @param {string} FieldID Id of the field which is updated via ajax
     * @description Shows and hides an ajax loader for every element which is updates via ajax
     */
    function ToggleAJAXLoader(FieldID) {
        var $Element = $('#' + FieldID),
            $Loader = $('#' + AJAXLoaderPrefix + FieldID),
            LoaderHTML = '';

        if (!$Loader.length) {
            if ( $Element.not('[type=hidden]').length ) {
                $Element.after(LoaderHTML);
                if (typeof ActiveAJAXCalls[FieldID] === 'undefined') {
                    ActiveAJAXCalls[FieldID] = 0;
                }
                ActiveAJAXCalls[FieldID]++;
            }
        }
        else if ($Loader.is(':hidden')) {
            $Loader.show();
            if (typeof ActiveAJAXCalls[FieldID] === 'undefined') {
                ActiveAJAXCalls[FieldID] = 0;
            }
            ActiveAJAXCalls[FieldID]++;
        }
        else {
            ActiveAJAXCalls[FieldID]--;
            if (ActiveAJAXCalls[FieldID] <= 0) {
                $Loader.hide();
                ActiveAJAXCalls[FieldID] = 0;
            }
        }
    }

    /**
     * @function
     * @private
     * @param {Object} Data The data that should be converted
     * @return {string} query string of the data
     * @description Converts a given hash into a query string
     */
    function SerializeData(Data) {
        var QueryString = "";
        $.each(Data, function (Key, Value) {
            QueryString += Key + '=' + encodeURIComponent(Value) + ";";
        });
        return QueryString;
    }

    /**
     * @function
     * @private
     * @return {Object} Hash with session data, if needed
     * @description Collects session data in a hash if available
     */
    function GetSessionInformation() {
        var Data = {};
        if (!Core.Config.Get('SessionIDCookie')) {
            Data[Core.Config.Get('SessionName')] = Core.Config.Get('SessionID');
            Data[Core.Config.Get('CustomerPanelSessionName')] = Core.Config.Get('SessionID');
        }
        return Data;
    }

    /**
     * @function
     * @private
     * @return {Object} Hash with additional session and action data
     * @description Collects additional data that are needed for the ajax requests
     */
    function GetAdditionalDefaultData() {
        var Data = {};
        Data = GetSessionInformation();
        Data.Action = Core.Config.Get('Action');
        return Data;
    }

    /**
     * @function
     * @private
     * @param {Object} Data The new field data
     * @param {Object} FieldsToUpdate The array of field elements
     * @return nothing
     * @description Updates the given fields with the given data
     */
    function UpdateFormElements(Data, FieldsToUpdate) {
        $.each(FieldsToUpdate, function (Index, Value) {
            var $Element = $('#' + Value),
                ElementData;
            if ($Element.length && Data) {
                // Select elements
                if ($Element.is('select')) {
                    ElementData = Data[Value];
                    if (ElementData) {
                        $Element.empty();
                        $.each(ElementData, function (Index, Value) {
                            var NewOption = new Option(Value[1], Value[0], Value[2], Value[3]);
                            // overwrite option text, because of wrong html quoting of text content
                            // needed for IE
                            NewOption.innerHTML = Value[1];
                            $Element.append(NewOption);
                        });
                    }
                }
                // Other form elements
                else {
                    if (Data[Value]) {
                        $Element.val(Value);
                    }
                }
            }
        });
    }

    /**
     * @function
     *      Serializes the form data into a query string
     * @param {jQueryObject} $Element The jQuery object of the form  or any element within this form that should be serialized
     * @param {Object} Data Elements (Keys) which should not be included in the serialized form string (optional)
     * @return {string} The query string
     */
    TargetNS.SerializeForm = function ($Element, Data) {
        var QueryString = "";
        if (typeof Data === 'undefined') {
            Data = {};
        }
        if (isJQueryObject($Element) && $Element.length) {
            $Element = $Element.closest('form');
            $Element.find('input:not(:file), textarea, select').filter(':not([disabled=disabled])').each(function () {
                // only add element to the string, if there is no key in the data hash with the same name
                if (typeof Data[$(this).attr('name')] === 'undefined') {
                    if ($(this).is(':checkbox, :radio')) {
                        QueryString += $(this).attr('name') + '=' + encodeURIComponent(($(this).is(':checked') ? ($(this).val() || 'on') : '')) + ";";
                    }
                    else {
                        QueryString += $(this).attr('name') + '=' + encodeURIComponent($(this).val()) + ";";
                    }
                }
            });
        }
        return QueryString;
    };

    /**
     * @function
     *      Submits a special form via ajax and updates the form with the data returned from the server
     * @param {jQueryObject} $EventElement The jQuery object of the element(s) which are included in the form that should be submitted
     * @param {String} Subaction The subaction parameter for the perl module
     * @param {String} ChangedElement The name of the element which was changed by the user
     * @param {Object} FieldsToUpdate The names of the fields that should be updated with the server answer
     * @param {Function} [SuccessCallback] Callback function to be executed on AJAX success (optional).
     * @return nothing
     */
    TargetNS.FormUpdate = function ($EventElement, Subaction, ChangedElement, FieldsToUpdate, SuccessCallback) {
        var URL = Core.Config.Get('Baselink'),
            Data = GetAdditionalDefaultData(),
            QueryString;

        Data.Subaction = Subaction;
        Data.ElementChanged = ChangedElement;
        QueryString = TargetNS.SerializeForm($EventElement, Data) + SerializeData(Data);

        $.each(FieldsToUpdate, function (Index, Value) {
            ToggleAJAXLoader(Value);
        });

        $.ajax({
            url: URL,
            data: QueryString,
            dataType: 'json',
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("Invalid JSON from: " + URL, 'CommunicationError');
                }
                else {
                    UpdateFormElements(Response, FieldsToUpdate);
                    if (typeof SuccessCallback === 'function') {
                        SuccessCallback();
                    }
                }
            },
            complete: function () {
                $.each(FieldsToUpdate, function (Index, Value) {
                    ToggleAJAXLoader(Value);
                });
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });

        return false;
    };

    /**
     * @function
     *      Calls an URL via Ajax and updates a html element with the answer html of the server
     * @param {jQueryObject} $ElementToUpdate The jQuery object of the element(s) which should be updated
     * @param {String} URL The URL which is called via Ajax
     * @param {Function} Callback The additional callback function which is called after the request returned from the server
     * @return nothing
     */
    TargetNS.ContentUpdate = function ($ElementToUpdate, URL, Callback) {
        var QueryString, QueryIndex = URL.indexOf("?");

        if (QueryIndex >= 0) {
            QueryString = URL.substr(QueryIndex + 1);
            URL = URL.substr(0, QueryIndex);
        }
        QueryString += SerializeData(GetSessionInformation());

        $.ajax({
            url: URL,
            data: QueryString,
            dataType: 'html',
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("No content from: " + URL, 'CommunicationError');
                }
                else if ($ElementToUpdate && isJQueryObject($ElementToUpdate) && $ElementToUpdate.length) {
                    $ElementToUpdate.html(Response);
                }
                else {
                    Core.Exception.Throw("No such element id: " + $ElementToUpdate.attr('id') + " in page!", 'CommunicationError');
                }
            },
            complete: function () {
                if ($.isFunction(Callback)) {
                    Callback();
                }
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });

        return false;
    };

    /**
     * @function
     *      Calls an URL via Ajax and executes a given function after the request returned from the server
     * @param {String} URL The URL which is called via Ajax
     * @param {Object} Data The data hash or data query string
     * @param {Function} Callback The callback function which is called after the request returned from the server
     * @param {String} DataType Optional, defines teh datatype, default 'json', could also be 'html'
     * @return nothing
     */
    TargetNS.FunctionCall = function (URL, Data, Callback, DataType) {
        if (typeof Data === 'string') {
            Data += SerializeData(GetSessionInformation());
        } else {
            Data = $.extend(Data, GetSessionInformation());
        }
        $.ajax({
            type: 'POST',
            url: URL,
            data: Data,
            dataType: (typeof DataType === 'undefined') ? 'json' : DataType,
            success: function (Response) {
                if (!Response) {
                    Core.Exception.Throw("No content from: " + URL, 'CommunicationError');
                }
                // call the callback
                if ($.isFunction(Callback)) {
                    Callback(Response);
                }
                else {
                    Core.Exception.Throw("Invalid callback method: " + ((typeof Callback === 'undefined') ? 'undefined' : Callback.toString()), 'CommunicationError');
                }
            },
            error: function () {
                Core.Exception.Throw("Error during AJAX communication", 'CommunicationError');
            }
        });
    };

    return TargetNS;
}(Core.AJAX || {}));<|endoftext|>


<commit_before>/**
 * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

/* globals self */

// Not named `NBSP` because that creates a duplicate identifier (util.js).
const NBSP2 = '\xa0';
const KiB = 1024;
const MiB = KiB * KiB;

/**
 * @template T
 */
class I18n {
  /**
   * @param {LH.Locale} locale
   * @param {T} strings
   */
  constructor(locale, strings) {
    // When testing, use a locale with more exciting numeric formatting.
    if (locale === 'en-XA') locale = 'de';

    this._numberDateLocale = locale;
    this._numberFormatter = new Intl.NumberFormat(locale);
    this._percentFormatter = new Intl.NumberFormat(locale, {style: 'percent'});
    this._strings = strings;
  }

  get strings() {
    return this._strings;
  }

  /**
   * Format number.
   * @param {number} number
   * @param {number=} granularity Number of decimal places to include. Defaults to 0.1.
   * @return {string}
   */
  formatNumber(number, granularity = 0.1) {
    const coarseValue = Math.round(number / granularity) * granularity;
    return this._numberFormatter.format(coarseValue);
  }

  /**
   * Format percent.
   * @param {number} number 0–1
   * @return {string}
   */
  formatPercent(number) {
    return this._percentFormatter.format(number);
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatBytesToKiB(size, granularity = 0.1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / 1024 / granularity) * granularity);
    return `${kbs}${NBSP2}KiB`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatBytesToMiB(size, granularity = 0.1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / 1024 ** 2 / granularity) * granularity);
    return `${kbs}${NBSP2}MiB`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 1
   * @return {string}
   */
  formatBytes(size, granularity = 1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / granularity) * granularity);
    return `${kbs}${NBSP2}bytes`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 1
   * @return {string}
   */
  formatBytesWithBestUnit(size, granularity = 1) {
    if (size >= MiB) return this.formatBytesToMiB(size, granularity);
    if (size >= KiB) return this.formatBytesToKiB(size, granularity);
    return this.formatNumber(size, granularity) + '\xa0B';
  }

  /**
   * Format bytes with a constant number of fractional digits, i.e for a granularity of 0.1, 10 becomes '10.0'
   * @param {number} granularity Controls how coarse the displayed value is
   * @return {Intl.NumberFormat}
   */
  _byteFormatterForGranularity(granularity) {
    // assume any granularity above 1 will not contain fractional parts, i.e. will never be 1.5
    let numberOfFractionDigits = 0;
    if (granularity < 1) {
      numberOfFractionDigits = -Math.floor(Math.log10(granularity));
    }

    return new Intl.NumberFormat(this._numberDateLocale, {
      ...this._numberFormatter.resolvedOptions(),
      maximumFractionDigits: numberOfFractionDigits,
      minimumFractionDigits: numberOfFractionDigits,
    });
  }

  /**
   * @param {number} ms
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 10
   * @return {string}
   */
  formatMilliseconds(ms, granularity = 10) {
    const coarseTime = Math.round(ms / granularity) * granularity;
    return coarseTime === 0
      ? `${this._numberFormatter.format(0)}${NBSP2}ms`
      : `${this._numberFormatter.format(coarseTime)}${NBSP2}ms`;
  }

  /**
   * @param {number} ms
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatSeconds(ms, granularity = 0.1) {
    const coarseTime = Math.round(ms / 1000 / granularity) * granularity;
    return `${this._numberFormatter.format(coarseTime)}${NBSP2}s`;
  }

  /**
   * Format time.
   * @param {string} date
   * @return {string}
   */
  formatDateTime(date) {
    /** @type {Intl.DateTimeFormatOptions} */
    const options = {
      month: 'short', day: 'numeric', year: 'numeric',
      hour: 'numeric', minute: 'numeric', timeZoneName: 'short',
    };

    // Force UTC if runtime timezone could not be detected.
    // See https://github.com/GoogleChrome/lighthouse/issues/1056
    // and https://github.com/GoogleChrome/lighthouse/pull/9822
    let formatter;
    try {
      formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
    } catch (err) {
      options.timeZone = 'UTC';
      formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
    }

    return formatter.format(new Date(date));
  }

  /**
   * Converts a time in milliseconds into a duration string, i.e. `1d 2h 13m 52s`
   * @param {number} timeInMilliseconds
   * @return {string}
   */
  formatDuration(timeInMilliseconds) {
    let timeInSeconds = timeInMilliseconds / 1000;
    if (Math.round(timeInSeconds) === 0) {
      return 'None';
    }

    /** @type {Array<string>} */
    const parts = [];
    /** @type {Record<string, number>} */
    const unitLabels = {
      d: 60 * 60 * 24,
      h: 60 * 60,
      m: 60,
      s: 1,
    };

    Object.keys(unitLabels).forEach(label => {
      const unit = unitLabels[label];
      const numberOfUnits = Math.floor(timeInSeconds / unit);
      if (numberOfUnits > 0) {
        timeInSeconds -= numberOfUnits * unit;
        parts.push(`${numberOfUnits}\xa0${label}`);
      }
    });

    return parts.join(' ');
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = I18n;
} else {
  self.I18n = I18n;
}
<commit_msg>misc(treemap): use 0.1 for default granularity (#12485)<commit_after>/**
 * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

/* globals self */

// Not named `NBSP` because that creates a duplicate identifier (util.js).
const NBSP2 = '\xa0';
const KiB = 1024;
const MiB = KiB * KiB;

/**
 * @template T
 */
class I18n {
  /**
   * @param {LH.Locale} locale
   * @param {T} strings
   */
  constructor(locale, strings) {
    // When testing, use a locale with more exciting numeric formatting.
    if (locale === 'en-XA') locale = 'de';

    this._numberDateLocale = locale;
    this._numberFormatter = new Intl.NumberFormat(locale);
    this._percentFormatter = new Intl.NumberFormat(locale, {style: 'percent'});
    this._strings = strings;
  }

  get strings() {
    return this._strings;
  }

  /**
   * Format number.
   * @param {number} number
   * @param {number=} granularity Number of decimal places to include. Defaults to 0.1.
   * @return {string}
   */
  formatNumber(number, granularity = 0.1) {
    const coarseValue = Math.round(number / granularity) * granularity;
    return this._numberFormatter.format(coarseValue);
  }

  /**
   * Format percent.
   * @param {number} number 0–1
   * @return {string}
   */
  formatPercent(number) {
    return this._percentFormatter.format(number);
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatBytesToKiB(size, granularity = 0.1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / 1024 / granularity) * granularity);
    return `${kbs}${NBSP2}KiB`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatBytesToMiB(size, granularity = 0.1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / 1024 ** 2 / granularity) * granularity);
    return `${kbs}${NBSP2}MiB`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 1
   * @return {string}
   */
  formatBytes(size, granularity = 1) {
    const formatter = this._byteFormatterForGranularity(granularity);
    const kbs = formatter.format(Math.round(size / granularity) * granularity);
    return `${kbs}${NBSP2}bytes`;
  }

  /**
   * @param {number} size
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatBytesWithBestUnit(size, granularity = 0.1) {
    if (size >= MiB) return this.formatBytesToMiB(size, granularity);
    if (size >= KiB) return this.formatBytesToKiB(size, granularity);
    return this.formatNumber(size, granularity) + '\xa0B';
  }

  /**
   * Format bytes with a constant number of fractional digits, i.e for a granularity of 0.1, 10 becomes '10.0'
   * @param {number} granularity Controls how coarse the displayed value is
   * @return {Intl.NumberFormat}
   */
  _byteFormatterForGranularity(granularity) {
    // assume any granularity above 1 will not contain fractional parts, i.e. will never be 1.5
    let numberOfFractionDigits = 0;
    if (granularity < 1) {
      numberOfFractionDigits = -Math.floor(Math.log10(granularity));
    }

    return new Intl.NumberFormat(this._numberDateLocale, {
      ...this._numberFormatter.resolvedOptions(),
      maximumFractionDigits: numberOfFractionDigits,
      minimumFractionDigits: numberOfFractionDigits,
    });
  }

  /**
   * @param {number} ms
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 10
   * @return {string}
   */
  formatMilliseconds(ms, granularity = 10) {
    const coarseTime = Math.round(ms / granularity) * granularity;
    return coarseTime === 0
      ? `${this._numberFormatter.format(0)}${NBSP2}ms`
      : `${this._numberFormatter.format(coarseTime)}${NBSP2}ms`;
  }

  /**
   * @param {number} ms
   * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
   * @return {string}
   */
  formatSeconds(ms, granularity = 0.1) {
    const coarseTime = Math.round(ms / 1000 / granularity) * granularity;
    return `${this._numberFormatter.format(coarseTime)}${NBSP2}s`;
  }

  /**
   * Format time.
   * @param {string} date
   * @return {string}
   */
  formatDateTime(date) {
    /** @type {Intl.DateTimeFormatOptions} */
    const options = {
      month: 'short', day: 'numeric', year: 'numeric',
      hour: 'numeric', minute: 'numeric', timeZoneName: 'short',
    };

    // Force UTC if runtime timezone could not be detected.
    // See https://github.com/GoogleChrome/lighthouse/issues/1056
    // and https://github.com/GoogleChrome/lighthouse/pull/9822
    let formatter;
    try {
      formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
    } catch (err) {
      options.timeZone = 'UTC';
      formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
    }

    return formatter.format(new Date(date));
  }

  /**
   * Converts a time in milliseconds into a duration string, i.e. `1d 2h 13m 52s`
   * @param {number} timeInMilliseconds
   * @return {string}
   */
  formatDuration(timeInMilliseconds) {
    let timeInSeconds = timeInMilliseconds / 1000;
    if (Math.round(timeInSeconds) === 0) {
      return 'None';
    }

    /** @type {Array<string>} */
    const parts = [];
    /** @type {Record<string, number>} */
    const unitLabels = {
      d: 60 * 60 * 24,
      h: 60 * 60,
      m: 60,
      s: 1,
    };

    Object.keys(unitLabels).forEach(label => {
      const unit = unitLabels[label];
      const numberOfUnits = Math.floor(timeInSeconds / unit);
      if (numberOfUnits > 0) {
        timeInSeconds -= numberOfUnits * unit;
        parts.push(`${numberOfUnits}\xa0${label}`);
      }
    });

    return parts.join(' ');
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = I18n;
} else {
  self.I18n = I18n;
}
<|endoftext|>


<commit_before>describe('Recipe controller', function() {
    'use strict';

    var createController, scope;
    var previousStep = {};
    var lastActiveStep = {inactive: false};

    beforeEach(module('data-prep.recipe'));

    beforeEach(inject(function($rootScope, $controller, $q, RecipeService, PlaygroundService, PreparationService) {
        scope = $rootScope.$new();

        createController = function() {
            var ctrl =  $controller('RecipeCtrl', {
                $scope: scope
            });
            return ctrl;
        };

        spyOn($rootScope, '$emit').and.callThrough();
        spyOn(RecipeService, 'getPreviousStep').and.returnValue(previousStep);
        spyOn(RecipeService, 'getActiveThresholdStepIndex').and.returnValue(3);
        spyOn(RecipeService, 'refresh').and.returnValue($q.when(true));
        spyOn(RecipeService, 'getStep').and.callFake(function(index, lastDefault) {
            return (index === 3 && lastDefault) ? lastActiveStep : null;
        });
        spyOn(PreparationService, 'updateStep').and.returnValue($q.when(true));
        spyOn(PlaygroundService, 'loadStep').and.returnValue($q.when(true));
    }));

    afterEach(inject(function(RecipeService) {
        var recipe = RecipeService.getRecipe();
        recipe.splice(0, recipe.length);
    }));

    it('should bind recipe getter with RecipeService', inject(function(RecipeService) {
        //given
        var ctrl = createController();
        expect(ctrl.recipe).toEqual([]);

        var column = {id: 'colId'};
        var transformation = {
            name: 'split',
            category: 'split',
            parameters: [],
            items: []
        };

        //when
        RecipeService.getRecipe().push({
            column: column,
            transformation: transformation
        });

        //then
        expect(ctrl.recipe.length).toBe(1);
        expect(ctrl.recipe[0].column).toBe(column);
        expect(ctrl.recipe[0].transformation).toEqual(transformation);
    }));

    it('should highlight active steps after the targeted one (included)', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {},
            {},
            {}
        );

        //when
        ctrl.stepHoverStart(1);

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeTruthy();
        expect(recipe[2].highlight).toBeTruthy();
        expect(recipe[3].highlight).toBeTruthy();
    }));

    it('should highlight inactive steps before the targeted one (included)', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {inactive: true},
            {inactive: true},
            {inactive: true}
        );

        //when
        ctrl.stepHoverStart(2);

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeTruthy();
        expect(recipe[2].highlight).toBeTruthy();
        expect(recipe[3].highlight).toBeFalsy();
    }));

    it('should remove highlight on mouse hover end', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {highlight: true},
            {highlight: true},
            {highlight: true}
        );

        //when
        ctrl.stepHoverEnd();

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeFalsy();
        expect(recipe[2].highlight).toBeFalsy();
        expect(recipe[3].highlight).toBeFalsy();
    }));

    it('should load current step content if the step is first inactive', inject(function(PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {inactive: true};

        //when
        ctrl.toggleStep(step);

        //then
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(step);
    }));

    it('should load previous step content if the step is first active', inject(function(PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {inactive: false};

        //when
        ctrl.toggleStep(step);

        //then
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(previousStep);
    }));

    it('should create a closure that update the step parameters', inject(function($rootScope, PreparationService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '-'};

        //when
        var updateClosure = ctrl.stepUpdateClosure(step);
        updateClosure(parameters);
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', parameters);
    }));

    it('should update step, refresh recipe, load last active step when parameters are different', inject(function($rootScope, PreparationService, RecipeService, PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '-'};

        //when
        ctrl.updateStep(step, parameters);
        expect($rootScope.$emit).toHaveBeenCalledWith('talend.loading.start');
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', parameters);
        expect(RecipeService.refresh).toHaveBeenCalled();
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(lastActiveStep);
        expect($rootScope.$emit).toHaveBeenCalledWith('talend.loading.stop');
    }));

    it('should init params object with column id if param is not defined', inject(function($rootScope, PreparationService, RecipeService, PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };

        //when
        ctrl.updateStep(step);
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', {column_name: 'state'});
    }));

    it('should do nothing if parameters are unchanged', inject(function($rootScope, PreparationService, RecipeService, PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '.'};

        //when
        ctrl.updateStep(step, parameters);
        $rootScope.$digest();

        //then
        expect($rootScope.$emit).not.toHaveBeenCalled();
        expect(PreparationService.updateStep).not.toHaveBeenCalled();
        expect(RecipeService.refresh).not.toHaveBeenCalled();
        expect(PlaygroundService.loadStep).not.toHaveBeenCalled();
    }));
});
<commit_msg>Jshint warnings fix<commit_after>/*jshint camelcase: false */

describe('Recipe controller', function() {
    'use strict';

    var createController, scope;
    var previousStep = {};
    var lastActiveStep = {inactive: false};

    beforeEach(module('data-prep.recipe'));

    beforeEach(inject(function($rootScope, $controller, $q, RecipeService, PlaygroundService, PreparationService) {
        scope = $rootScope.$new();

        createController = function() {
            var ctrl =  $controller('RecipeCtrl', {
                $scope: scope
            });
            return ctrl;
        };

        spyOn($rootScope, '$emit').and.callThrough();
        spyOn(RecipeService, 'getPreviousStep').and.returnValue(previousStep);
        spyOn(RecipeService, 'getActiveThresholdStepIndex').and.returnValue(3);
        spyOn(RecipeService, 'refresh').and.returnValue($q.when(true));
        spyOn(RecipeService, 'getStep').and.callFake(function(index, lastDefault) {
            return (index === 3 && lastDefault) ? lastActiveStep : null;
        });
        spyOn(PreparationService, 'updateStep').and.returnValue($q.when(true));
        spyOn(PlaygroundService, 'loadStep').and.returnValue($q.when(true));
    }));

    afterEach(inject(function(RecipeService) {
        var recipe = RecipeService.getRecipe();
        recipe.splice(0, recipe.length);
    }));

    it('should bind recipe getter with RecipeService', inject(function(RecipeService) {
        //given
        var ctrl = createController();
        expect(ctrl.recipe).toEqual([]);

        var column = {id: 'colId'};
        var transformation = {
            name: 'split',
            category: 'split',
            parameters: [],
            items: []
        };

        //when
        RecipeService.getRecipe().push({
            column: column,
            transformation: transformation
        });

        //then
        expect(ctrl.recipe.length).toBe(1);
        expect(ctrl.recipe[0].column).toBe(column);
        expect(ctrl.recipe[0].transformation).toEqual(transformation);
    }));

    it('should highlight active steps after the targeted one (included)', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {},
            {},
            {}
        );

        //when
        ctrl.stepHoverStart(1);

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeTruthy();
        expect(recipe[2].highlight).toBeTruthy();
        expect(recipe[3].highlight).toBeTruthy();
    }));

    it('should highlight inactive steps before the targeted one (included)', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {inactive: true},
            {inactive: true},
            {inactive: true}
        );

        //when
        ctrl.stepHoverStart(2);

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeTruthy();
        expect(recipe[2].highlight).toBeTruthy();
        expect(recipe[3].highlight).toBeFalsy();
    }));

    it('should remove highlight on mouse hover end', inject(function(RecipeService) {
        //given
        var ctrl = createController();

        var recipe = RecipeService.getRecipe();
        recipe.push(
            {},
            {highlight: true},
            {highlight: true},
            {highlight: true}
        );

        //when
        ctrl.stepHoverEnd();

        //then
        expect(recipe[0].highlight).toBeFalsy();
        expect(recipe[1].highlight).toBeFalsy();
        expect(recipe[2].highlight).toBeFalsy();
        expect(recipe[3].highlight).toBeFalsy();
    }));

    it('should load current step content if the step is first inactive', inject(function(PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {inactive: true};

        //when
        ctrl.toggleStep(step);

        //then
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(step);
    }));

    it('should load previous step content if the step is first active', inject(function(PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {inactive: false};

        //when
        ctrl.toggleStep(step);

        //then
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(previousStep);
    }));

    it('should create a closure that update the step parameters', inject(function($rootScope, PreparationService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '-'};

        //when
        var updateClosure = ctrl.stepUpdateClosure(step);
        updateClosure(parameters);
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', parameters);
    }));

    it('should update step, refresh recipe, load last active step when parameters are different', inject(function($rootScope, PreparationService, RecipeService, PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '-'};

        //when
        ctrl.updateStep(step, parameters);
        expect($rootScope.$emit).toHaveBeenCalledWith('talend.loading.start');
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', parameters);
        expect(RecipeService.refresh).toHaveBeenCalled();
        expect(PlaygroundService.loadStep).toHaveBeenCalledWith(lastActiveStep);
        expect($rootScope.$emit).toHaveBeenCalledWith('talend.loading.stop');
    }));

    it('should init params object with column id if param is not defined', inject(function($rootScope, PreparationService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };

        //when
        ctrl.updateStep(step);
        $rootScope.$digest();

        //then
        expect(PreparationService.updateStep).toHaveBeenCalledWith('a598bc83fc894578a8b823', 'cut', {column_name: 'state'});
    }));

    it('should do nothing if parameters are unchanged', inject(function($rootScope, PreparationService, RecipeService, PlaygroundService) {
        //given
        var ctrl = createController();
        var step = {
            column: {id: 'state'},
            transformation: {
                stepId: 'a598bc83fc894578a8b823',
                name: 'cut'
            },
            actionParameters: {
                action: 'cut',
                parameters: {pattern: '.', column_name: 'state'}
            }
        };
        var parameters = {pattern: '.'};

        //when
        ctrl.updateStep(step, parameters);
        $rootScope.$digest();

        //then
        expect($rootScope.$emit).not.toHaveBeenCalled();
        expect(PreparationService.updateStep).not.toHaveBeenCalled();
        expect(RecipeService.refresh).not.toHaveBeenCalled();
        expect(PlaygroundService.loadStep).not.toHaveBeenCalled();
    }));
});
<|endoftext|>


<commit_before>define([
    'app/config',
    'app/GraphicsController',
    'app/MapController',

    'dijit/_TemplatedMixin',
    'dijit/_WidgetBase',

    'dojo/dom-construct',
    'dojo/keys',
    'dojo/on',
    'dojo/text!app/templates/Query.html',
    'dojo/topic',
    'dojo/_base/declare',
    'dojo/_base/lang',

    'esri/layers/FeatureLayer',
    'esri/tasks/query'
], function (
    config,
    GraphicsController,
    MapController,

    _TemplatedMixin,
    _WidgetBase,

    domConstruct,
    keys,
    on,
    template,
    topic,
    declare,
    lang,

    FeatureLayer,
    Query
) {
    return declare([_WidgetBase, _TemplatedMixin], {
        // description:
        //      canned queries
        templateString: template,
        baseClass: 'query panel-body',

        cannedQueries: {
            number: '{term}={query}',
            string: 'UPPER({term}) LIKE UPPER(\'%{query}%\')'
        },

        // Properties to be sent into constructor

        // esri/layers that support query
        layers: null,

        postCreate: function () {
            // summary:
            //      Overrides method of same name in dijit._Widget.
            console.info('app.Query::postCreate', arguments);

            this.setupConnections();

            var layerType;
            this.layers.forEach(function (layer) {
                var name = layer.name.toLowerCase();
                if (name.indexOf('well') > -1) {
                    layerType = 'well';
                } else if (name.indexOf('facilit') > -1) {
                    layerType = 'facility';
                } else if (name.indexOf('permit') > -1) {
                    layerType = 'permit';
                } else {
                    return;
                }

                this.layer.appendChild(domConstruct.toDom('<option value="' +
                    layerType + '">' + layer.arcgisProps.title));
            }, this);

            var layerLookup = {};
            this.layers.forEach(function (layer) {
                layerLookup[layer.name] = layer;
            }, this);

            this.layers = layerLookup;

            this.updateQueriesForLayer();

            this.inherited(arguments);
        },
        setupConnections: function () {
            // summary:
            //      wire events, and such
            console.info('app.Query::setupConnections', arguments);

            on(this.input, 'keyup', lang.hitch(this, '_submitOnEnter'));
        },
        query: function () {
            // summary:
            //      description
            // param or return
            console.info('app.Query:query', arguments);

            this.activeLayer = this.layers[this.layer.options[this.layer.selectedIndex].text];
            this.activeLayer.setSelectionSymbol(config.symbols.point);

            var query = new Query();
            var queryInfo = config.queries[this.layer.value][this.type.value];
            var queryString = this.cannedQueries[queryInfo.type];
            query.where = lang.replace(queryString, {
                term: queryInfo.field,
                query: this.input.value
            });
            query.outFields = ['SHAPE'];

            this.activeLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW).then(
                function (graphics) {
                    if (graphics && graphics.length > 0) {
                        MapController.zoom(graphics);
                        if (graphics.length === config.maxResult) {
                            topic.publish(config.topics.toast,
                                'The max number of features was returned. The results are a partial view.');
                        }
                    } else {
                        topic.publish(config.topics.toast, 'No features found. Check your spelling.');
                    }
                },
                function (err) {
                    topic.publish(config.topics.toast, (err.details[0] || err || '') +
                        ' Make sure the layer you are querying contains the ' + queryInfo.field + ' field.');
                }
            );
        },
        updateQueriesForLayer: function () {
            // summary:
            //      changes the available queries for the new layerId
            //
            console.info('app/Query:updateQueriesForLayer', arguments);

            var queryKey = this.layer.value;
            var queries = config.queries[queryKey];

            this._rebuildQuerySelect(queries);
        },
        _rebuildQuerySelect: function (queries) {
            // summary:
            //      description
            // param or return
            console.info('app/Query:_rebuildQuerySelect', arguments);

            domConstruct.empty(this.type);

            Object.keys(queries).forEach(function (key) {
                this.type.appendChild(domConstruct.toDom('<option value="' +
                    key + '">' + this._toProperCase(key)));
            }, this);
        },
        _submitOnEnter: function (evt) {
            // summary:
            //      calls query if enter was pressed
            //
            console.info('app/Query:_submitOnEnter', arguments);

            if (evt.keyCode === keys.ENTER) {
                this.query();
            }
        },
        _toProperCase: function (str) {
            // summary:
            //      description
            // param or return
            console.info('app/Query::_toProperCase', arguments);

            str = str.charAt(0).toUpperCase() + str.substring(1, str.length);

            return str.replace(/([A-Z])/g, ' $1');
        },
        destroy: function () {
            // summary:
            //      overload destroy to clear selections
            console.info('app.Query:destroy', arguments);

            if (this.activeLayer) {
                this.activeLayer.clearSelection();
            }

            this.inherited(arguments);
        }
    });
});
<commit_msg>update text for how layers are found<commit_after>define([
    'app/config',
    'app/GraphicsController',
    'app/MapController',

    'dijit/_TemplatedMixin',
    'dijit/_WidgetBase',

    'dojo/dom-construct',
    'dojo/keys',
    'dojo/on',
    'dojo/text!app/templates/Query.html',
    'dojo/topic',
    'dojo/_base/declare',
    'dojo/_base/lang',

    'esri/layers/FeatureLayer',
    'esri/tasks/query'
], function (
    config,
    GraphicsController,
    MapController,

    _TemplatedMixin,
    _WidgetBase,

    domConstruct,
    keys,
    on,
    template,
    topic,
    declare,
    lang,

    FeatureLayer,
    Query
) {
    return declare([_WidgetBase, _TemplatedMixin], {
        // description:
        //      canned queries
        templateString: template,
        baseClass: 'query panel-body',

        cannedQueries: {
            number: '{term}={query}',
            string: 'UPPER({term}) LIKE UPPER(\'%{query}%\')'
        },

        // Properties to be sent into constructor

        // esri/layers that support query
        layers: null,

        postCreate: function () {
            // summary:
            //      Overrides method of same name in dijit._Widget.
            console.info('app.Query::postCreate', arguments);

            this.setupConnections();

            var layerType;
            this.layers.forEach(function (layer) {
                var name = layer.name.toLowerCase();
                if (name.indexOf('well') > -1) {
                    layerType = 'well';
                } else if (name.indexOf('facilit') > -1) {
                    layerType = 'facility';
                } else if (name.indexOf('permit') > -1) {
                    layerType = 'permit';
                } else {
                    return;
                }

                this.layer.appendChild(domConstruct.toDom('<option value="' +
                    layerType + '">' + layer.arcgisProps.title));
            }, this);

            var layerLookup = {};
            this.layers.forEach(function (layer) {
                layerLookup[layer.arcgisProps.title] = layer;
            }, this);

            this.layers = layerLookup;

            this.updateQueriesForLayer();

            this.inherited(arguments);
        },
        setupConnections: function () {
            // summary:
            //      wire events, and such
            console.info('app.Query::setupConnections', arguments);

            on(this.input, 'keyup', lang.hitch(this, '_submitOnEnter'));
        },
        query: function () {
            // summary:
            //      description
            // param or return
            console.info('app.Query:query', arguments);

            this.activeLayer = this.layers[this.layer.options[this.layer.selectedIndex].text];
            this.activeLayer.setSelectionSymbol(config.symbols.point);

            var query = new Query();
            var queryInfo = config.queries[this.layer.value][this.type.value];
            var queryString = this.cannedQueries[queryInfo.type];
            query.where = lang.replace(queryString, {
                term: queryInfo.field,
                query: this.input.value
            });
            query.outFields = ['SHAPE'];

            this.activeLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW).then(
                function (graphics) {
                    if (graphics && graphics.length > 0) {
                        MapController.zoom(graphics);
                        if (graphics.length === config.maxResult) {
                            topic.publish(config.topics.toast,
                                'The max number of features was returned. The results are a partial view.');
                        }
                    } else {
                        topic.publish(config.topics.toast, 'No features found. Check your spelling.');
                    }
                },
                function (err) {
                    topic.publish(config.topics.toast, (err.details[0] || err || '') +
                        ' Make sure the layer you are querying contains the ' + queryInfo.field + ' field.');
                }
            );
        },
        updateQueriesForLayer: function () {
            // summary:
            //      changes the available queries for the new layerId
            //
            console.info('app/Query:updateQueriesForLayer', arguments);

            var queryKey = this.layer.value;
            var queries = config.queries[queryKey];

            this._rebuildQuerySelect(queries);
        },
        _rebuildQuerySelect: function (queries) {
            // summary:
            //      description
            // param or return
            console.info('app/Query:_rebuildQuerySelect', arguments);

            domConstruct.empty(this.type);

            Object.keys(queries).forEach(function (key) {
                this.type.appendChild(domConstruct.toDom('<option value="' +
                    key + '">' + this._toProperCase(key)));
            }, this);
        },
        _submitOnEnter: function (evt) {
            // summary:
            //      calls query if enter was pressed
            //
            console.info('app/Query:_submitOnEnter', arguments);

            if (evt.keyCode === keys.ENTER) {
                this.query();
            }
        },
        _toProperCase: function (str) {
            // summary:
            //      description
            // param or return
            console.info('app/Query::_toProperCase', arguments);

            str = str.charAt(0).toUpperCase() + str.substring(1, str.length);

            return str.replace(/([A-Z])/g, ' $1');
        },
        destroy: function () {
            // summary:
            //      overload destroy to clear selections
            console.info('app.Query:destroy', arguments);

            if (this.activeLayer) {
                this.activeLayer.clearSelection();
            }

            this.inherited(arguments);
        }
    });
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 SUSE Linux
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

'use strict';

angular.module('janusHangouts', ['ngAnimate', 'ngCookies', 'ngTouch',
               'ngSanitize', 'blockUI', 'ui.router', 'ui.bootstrap', 'ngEmbed',
               'janusHangouts.config', 'cfp.hotkeys', 'gridster',
               'ngAudio', 'angular-extended-notifications', 'LocalStorageModule'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('signin', {
        url: '/sign_in?room',
        templateUrl: 'app/signin/signin.html',
        controller: 'SigninController',
        controllerAs: 'vm',
        resolve: {
          StatesService: 'StatesService',
          setRoomAndService: function (StatesService, $state) {
            return StatesService.setRoomAndUser($state.toParams);
          }
        }
      })
      .state('room', {
        url: '/rooms/:room?user',
        templateUrl: 'app/room/room.html',
        controller: 'RoomCtrl',
        resolve: {
          StatesService: 'StatesService',
          setRoomAndService: function (StatesService, $state) {
            return StatesService.setRoomAndUser($state.toParams);
          }
        },
        onEnter: function (UserService, RoomService) {
          UserService.setSetting("lastRoom", RoomService.getRoom().id);
        }
      });

    $urlRouterProvider.otherwise('/sign_in');
  })
  .config(function(blockUIConfig) {
    blockUIConfig.templateUrl = 'app/room/consent-dialog.html';
    blockUIConfig.cssClass = 'block-ui block-ui-anim-fade consent-dialog';
    blockUIConfig.autoBlock = false;
  })
  .config(function(notificationsProvider) {
    notificationsProvider.setDefaults({
      templatesDir: 'app/templates/',
      faIcons: true,
      closeOnRouteChange: 'state'
    });
  })
  .config(['localStorageServiceProvider', function (localStorageServiceProvider) {
    localStorageServiceProvider.setPrefix('jh');

  }])
  .config(function ($provide) {
    // Decorate $state with parameters from the URL
    // so they're available when 'resolving':
    // http://stackoverflow.com/questions/22985988/angular-ui-router-get-state-info-of-tostate-in-resolve
    $provide.decorator('$state', function ($delegate, $rootScope) {
      $rootScope.$on('$stateChangeStart', function (event, state, params) {
        $delegate.toParams = params;
      });
      return $delegate;
    });
  })
  .run(function($http, jhConfig) {
    //function to replace the placeholders
    function replacePlaceholder(value) {
      return value.replace("%{hostname}", window.location.host);
    }
    var request = new XMLHttpRequest();
    request.open('GET', 'config.json', false);
    request.send(null);
    if (request.status === 200) {
      var config = JSON.parse(request.responseText);
      angular.forEach(config, function (value, key) {
        //assigning config value with replaced value of placeholder
        jhConfig[key] = replacePlaceholder(value);
      });
    } else {
      console.warn('No configuration found');
    }
  })
  .run(function ($rootScope, $state, RoomService) {
    $rootScope.$on('$stateChangeStart', function () {
      // Before changing state, cleanup feeds
      RoomService.leave();
    });
    $rootScope.$on('$stateChangeError', function () {
      $state.go('signin');
    });
  });
<commit_msg>Solves Typeerror<commit_after>/*
 * Copyright (C) 2015 SUSE Linux
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

'use strict';

angular.module('janusHangouts', ['ngAnimate', 'ngCookies', 'ngTouch',
               'ngSanitize', 'blockUI', 'ui.router', 'ui.bootstrap', 'ngEmbed',
               'janusHangouts.config', 'cfp.hotkeys', 'gridster',
               'ngAudio', 'angular-extended-notifications', 'LocalStorageModule'])
  .config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('signin', {
        url: '/sign_in?room',
        templateUrl: 'app/signin/signin.html',
        controller: 'SigninController',
        controllerAs: 'vm',
        resolve: {
          StatesService: 'StatesService',
          setRoomAndService: function (StatesService, $state) {
            return StatesService.setRoomAndUser($state.toParams);
          }
        }
      })
      .state('room', {
        url: '/rooms/:room?user',
        templateUrl: 'app/room/room.html',
        controller: 'RoomCtrl',
        resolve: {
          StatesService: 'StatesService',
          setRoomAndService: function (StatesService, $state) {
            return StatesService.setRoomAndUser($state.toParams);
          }
        },
        onEnter: function (UserService, RoomService) {
          UserService.setSetting("lastRoom", RoomService.getRoom().id);
        }
      });

    $urlRouterProvider.otherwise('/sign_in');
  })
  .config(function(blockUIConfig) {
    blockUIConfig.templateUrl = 'app/room/consent-dialog.html';
    blockUIConfig.cssClass = 'block-ui block-ui-anim-fade consent-dialog';
    blockUIConfig.autoBlock = false;
  })
  .config(function(notificationsProvider) {
    notificationsProvider.setDefaults({
      templatesDir: 'app/templates/',
      faIcons: true,
      closeOnRouteChange: 'state'
    });
  })
  .config(['localStorageServiceProvider', function (localStorageServiceProvider) {
    localStorageServiceProvider.setPrefix('jh');

  }])
  .config(function ($provide) {
    // Decorate $state with parameters from the URL
    // so they're available when 'resolving':
    // http://stackoverflow.com/questions/22985988/angular-ui-router-get-state-info-of-tostate-in-resolve
    $provide.decorator('$state', function ($delegate, $rootScope) {
      $rootScope.$on('$stateChangeStart', function (event, state, params) {
        $delegate.toParams = params;
      });
      return $delegate;
    });
  })
  .run(function($http, jhConfig) {
    //function to replace the placeholders

    function replacePlaceholder(value) {
      if (typeof value === 'string') {
          return value.replace("%{hostname}", window.location.host);
      }

      return value;
    }

    var request = new XMLHttpRequest();
    request.open('GET', 'config.json', false);
    request.send(null);
    if (request.status === 200) {
      var config = JSON.parse(request.responseText);
      angular.forEach(config, function (value, key) {
        //assigning config value with replaced value of placeholder
        jhConfig[key] = replacePlaceholder(value);
      });
    } else {
      console.warn('No configuration found');
    }
  })
  .run(function ($rootScope, $state, RoomService) {
    $rootScope.$on('$stateChangeStart', function () {
      // Before changing state, cleanup feeds
      RoomService.leave();
    });
    $rootScope.$on('$stateChangeError', function () {
      $state.go('signin');
    });
  });
<|endoftext|>


<commit_before>// public functions:
var public = {
    loadPositions: function(){}
};

function initYourWork(){
    /* ============
    *  Your Work
    */
    var jForm = $('form.your-work');
    var jSelectPosition = $('select[name=select-position]', jForm);
    var jAttributes = $('.select-attributes', jForm);
    var jActions = $('fieldset.actions', jForm);
    $.blockUI.defaults.message = null;

    var loadPositions = public.loadPositions = function loadPositions() {
        // Lock element:
        jSelectPosition.parent().block(loadingBlock);

        // Reset positions, except first/value empty
        if (jSelectPosition.get(0).options.length > 1)
            jSelectPosition.find("option:not([value=''])").remove();

        /* Template to create a select option
        {0}: positionId
        {1}: position singular name
        */
        var tplOption = '<option value="{0}">{1}</option>';

        // Call ajax
        $.ajax({
            url: UrlUtil.JsonPath + "GetPositions/",
            dataType: 'json',
            success: function (data, textStatus, jqXHR) {
                // Check result
                if (data.Code == -1) {
                    jSelectPosition.parent().unblock().block(errorBlock(data.Result.ErrorMessage, 'public.loadPositions()'));
                    return;
                }

                // Iterate categories
                $.each(data.Result.Positions, function (iPos, pos) {
                    // Create and add the new option
                    jSelectPosition.append(tplOption
                        .replace('{0}', pos.PositionID)
                        .replace('{1}', pos.PositionSingular));
                });

                jSelectPosition.parent().unblock();
            },
            error: function (jqXHR, errorText) {
                jSelectPosition.parent().unblock().block(errorBlock(null, 'public.loadPositions()'));
            }
        });
    }

    function loadAttributes() {

        var selectedPosition = $(this).val();

        // No position:
        if (!selectedPosition) {
            jAttributes.block({ overlayCSS: { cursor: 'default'} });
            jActions.block({ overlayCSS: { cursor: 'default'} });
            return;
        }
        // Get all fieldsets and fieldset containers container
        var catscontainer = $('.select-attributes', jForm);
        // Get dynamic fieldsets container:
        var fscontainer = $('.service-attribute-categories', jForm);

        /* Template to create a fieldset for attribute-category
        @0 CategoryId
        @1 CategoryName
        @2 CategoryDescription
        */
        var tplCategory = '<fieldset class="service-attribute-category" id="service-attribute-category-@0"><legend title="@1" data-description="@2">@1:</legend></fieldset>';
        /* Template to create a label with checkbox, markers:
        @0 CategoryId
        @1 AttributeId
        @2 AttributeName
        @3 AttributeDescription
        */
        var tplAttCheck = '<input name="positionservices-category[@0]-attribute[@1]" type="checkbox" value="@1"/>@2';
        /* Template to create a select for attributes options, markers:
        @0 Form field name, using templates tplNameCat or tplNameCatAtt
        @1 CategoryName
        @2 CategoryDescription
        */
        var tplAttSelect = '<select name="@0" title="@1" data-description="@2"></select>';
        /* Template to create the select options for attributes, markers:
        @0 AttributeID
        @1 AttributeName
        @2 AttributeDescription
        */
        var tplNameCatAtt = 'positionservices-category[@0]-attribute[@1]';
        var tplNameCat = 'positionservices-category[@0]';
        var tplAttSelectOpt = '<option title="@1" data-description="@2" value="@0">@1</option>';

        function createAttCheckList(cat, selectNameSufix, checkSelectCat) {
            var c = $('

');
            // Iterate category attributes
            $.each(cat.ServiceAttributes, function (iAtt, att) {
                var hAtt = tplAttCheck
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    .replace(/@1/g, att.ServiceAttributeID)
                    .replace(/@2/g, att.ServiceAttribute)
                    .replace(/@3/g, att.ServiceAttributeDescription);

                // Add new attribute html
                var cli = $('
' + hAtt + '
');
                if (checkSelectCat) {
                    cli.append(createAttSelect(checkSelectCat, selectNameSufix, att.ServiceAttributeID));
                }
                c.append(cli);
            });
            return c;
        }
        function createAttSelect(cat, nameSufix, relatedAttID) {
            var name;
            if (relatedAttID)
                name = tplNameCatAtt
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    .replace(/@1/g, relatedAttID)
                    + nameSufix;
            else
                name = tplNameCatAtt
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    + nameSufix;

            var c = $(tplAttSelect
                .replace(/@0/g, name)
                .replace(/@1/g, cat.ServiceAttributeCategoryName)
                .replace(/@2/g, cat.ServiceAttributeCategoryDescription));
            $.each(cat.ServiceAttributes, function (iAtt, att) {
                c.append(tplAttSelectOpt
                    .replace(/@0/g, att.ServiceAttributeID)
                    .replace(/@1/g, att.ServiceAttribute)
                    .replace(/@2/g, att.ServiceAttributeDescription));
            });
            return c;
        }

        // Locking elements and showing loading message
        jAttributes.block(loadingBlock);
        jActions.block();

        // Do the ajax to load attributes
        $.ajax({
            url: UrlUtil.JsonPath + 'GetServiceAttributes/',
            data: { positionId: selectedPosition, filter: 'provider-services' },
            dataType: 'json',
            success: function (data, textStatus, jqXHR) {

                // Check result
                if (data.Code == -1) {
                    jAttributes.unblock().block(errorBlock(data.Result.ErrorMessage));
                    return;
                }

                // First, hide all fieldset (not all will be needed)
                catscontainer.find('fieldset').hide();

                // Iterate categories
                $.each(data.Result.ServiceAttributeCategories, function (iCat, cat) {
                    // Locate the category fieldset
                    var fs = $('#service-attribute-category-' + cat.ServiceAttributeCategoryID);

                    // Only if there are attributes on the category, else break into next
                    if (!cat.ServiceAttributes || cat.ServiceAttributes.length == 0
                        || cat.ServiceAttributeCategoryID == -5)
                        return; // break each

                    // If category fieldset doesn't exist, create it:
                    if (fs.length == 0) {
                        var fs = $(tplCategory
                            .replace(/@0/g, cat.ServiceAttributeCategoryID)
                            .replace(/@1/g, cat.ServiceAttributeCategoryName)
                            .replace(/@2/g, cat.ServiceAttributeCategoryDescription));
                        fscontainer.append(fs);
                    }

                    // Update Fieldset Label
                    fs.find('legend').text(cat.ServiceAttributeCategoryName)
                        .attr('title', cat.ServiceAttributeCategoryName)
                        .attr('data-description', cat.ServiceAttributeCategoryDescription);

                    // Remove existing attributes
                    fs.find('>*:not(legend)').remove();

                    switch (cat.ServiceAttributeCategoryID) {
                        /*case 2:
                        case 3:
                        case 7:*/
                        default:
                            fs.append(createAttCheckList(cat));
                            break;
                        case 5:
                            fs.append(createAttCheckList(cat, '-level', data.Result.ServiceAttributeCategories[-5]));
                            break;
                        case 1:
                            fs.append(createAttSelect(cat));
                            break;
                        case 4:
                            fs.append(createAttSelect(cat, '-level'));
                            break;
                        case -5:
                            // do nothing with virtual languages level category, is used on catID:5 for every language
                            break;
                    }

                    // Show it again
                    fs.show();
                });

                jAttributes.unblock();
                jActions.unblock();
            },
            error: function (jqXHR, errorText) {
                jAttributes.parent().unblock().block(errorBlock());
            }
        });
    }

    // Positions Load:
    //loadPositions();

    // First Attributes Load:
    if (jSelectPosition.length > 0)
        $.proxy(loadAttributes, jSelectPosition.get(0))();

    // Auto post back when select a position:
    jSelectPosition.change(loadAttributes);
};

$(document).ready(initYourWork);
$("#your-work").bind('endLoadWizardStep', initYourWork)
    .bind('endSubmitWizardStep', function (event, ok) { if (!ok) initYourWork(); });
<commit_msg>Fix #63, twice position attributes load requests each time a position is selected, meaning wasted ressources and a 'reset effect' on the form in the first seconds.<commit_after>// public functions:
var public = {
    loadPositions: function(){}
};

function initYourWork(){
    /* ============
    *  Your Work
    */
    var jForm = $('form.your-work');
    var jSelectPosition = $('select[name=select-position]', jForm);
    var jAttributes = $('.select-attributes', jForm);
    var jActions = $('fieldset.actions', jForm);
    $.blockUI.defaults.message = null;

    var loadPositions = public.loadPositions = function loadPositions() {
        // Lock element:
        jSelectPosition.parent().block(loadingBlock);

        // Reset positions, except first/value empty
        if (jSelectPosition.get(0).options.length > 1)
            jSelectPosition.find("option:not([value=''])").remove();

        /* Template to create a select option
        {0}: positionId
        {1}: position singular name
        */
        var tplOption = '<option value="{0}">{1}</option>';

        // Call ajax
        $.ajax({
            url: UrlUtil.JsonPath + "GetPositions/",
            dataType: 'json',
            success: function (data, textStatus, jqXHR) {
                // Check result
                if (data.Code == -1) {
                    jSelectPosition.parent().unblock().block(errorBlock(data.Result.ErrorMessage, 'public.loadPositions()'));
                    return;
                }

                // Iterate categories
                $.each(data.Result.Positions, function (iPos, pos) {
                    // Create and add the new option
                    jSelectPosition.append(tplOption
                        .replace('{0}', pos.PositionID)
                        .replace('{1}', pos.PositionSingular));
                });

                jSelectPosition.parent().unblock();
            },
            error: function (jqXHR, errorText) {
                jSelectPosition.parent().unblock().block(errorBlock(null, 'public.loadPositions()'));
            }
        });
    }

    function loadAttributes() {

        var selectedPosition = $(this).val();

        // No position:
        if (!selectedPosition) {
            jAttributes.block({ overlayCSS: { cursor: 'default'} });
            jActions.block({ overlayCSS: { cursor: 'default'} });
            return;
        }
        // Get all fieldsets and fieldset containers container
        var catscontainer = $('.select-attributes', jForm);
        // Get dynamic fieldsets container:
        var fscontainer = $('.service-attribute-categories', jForm);

        /* Template to create a fieldset for attribute-category
        @0 CategoryId
        @1 CategoryName
        @2 CategoryDescription
        */
        var tplCategory = '<fieldset class="service-attribute-category" id="service-attribute-category-@0"><legend title="@1" data-description="@2">@1:</legend></fieldset>';
        /* Template to create a label with checkbox, markers:
        @0 CategoryId
        @1 AttributeId
        @2 AttributeName
        @3 AttributeDescription
        */
        var tplAttCheck = '<input name="positionservices-category[@0]-attribute[@1]" type="checkbox" value="@1"/>@2';
        /* Template to create a select for attributes options, markers:
        @0 Form field name, using templates tplNameCat or tplNameCatAtt
        @1 CategoryName
        @2 CategoryDescription
        */
        var tplAttSelect = '<select name="@0" title="@1" data-description="@2"></select>';
        /* Template to create the select options for attributes, markers:
        @0 AttributeID
        @1 AttributeName
        @2 AttributeDescription
        */
        var tplNameCatAtt = 'positionservices-category[@0]-attribute[@1]';
        var tplNameCat = 'positionservices-category[@0]';
        var tplAttSelectOpt = '<option title="@1" data-description="@2" value="@0">@1</option>';

        function createAttCheckList(cat, selectNameSufix, checkSelectCat) {
            var c = $('

');
            // Iterate category attributes
            $.each(cat.ServiceAttributes, function (iAtt, att) {
                var hAtt = tplAttCheck
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    .replace(/@1/g, att.ServiceAttributeID)
                    .replace(/@2/g, att.ServiceAttribute)
                    .replace(/@3/g, att.ServiceAttributeDescription);

                // Add new attribute html
                var cli = $('
' + hAtt + '
');
                if (checkSelectCat) {
                    cli.append(createAttSelect(checkSelectCat, selectNameSufix, att.ServiceAttributeID));
                }
                c.append(cli);
            });
            return c;
        }
        function createAttSelect(cat, nameSufix, relatedAttID) {
            var name;
            if (relatedAttID)
                name = tplNameCatAtt
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    .replace(/@1/g, relatedAttID)
                    + nameSufix;
            else
                name = tplNameCatAtt
                    .replace(/@0/g, cat.ServiceAttributeCategoryID)
                    + nameSufix;

            var c = $(tplAttSelect
                .replace(/@0/g, name)
                .replace(/@1/g, cat.ServiceAttributeCategoryName)
                .replace(/@2/g, cat.ServiceAttributeCategoryDescription));
            $.each(cat.ServiceAttributes, function (iAtt, att) {
                c.append(tplAttSelectOpt
                    .replace(/@0/g, att.ServiceAttributeID)
                    .replace(/@1/g, att.ServiceAttribute)
                    .replace(/@2/g, att.ServiceAttributeDescription));
            });
            return c;
        }

        // Locking elements and showing loading message
        jAttributes.block(loadingBlock);
        jActions.block();

        // Do the ajax to load attributes
        $.ajax({
            url: UrlUtil.JsonPath + 'GetServiceAttributes/',
            data: { positionId: selectedPosition, filter: 'provider-services' },
            dataType: 'json',
            success: function (data, textStatus, jqXHR) {

                // Check result
                if (data.Code == -1) {
                    jAttributes.unblock().block(errorBlock(data.Result.ErrorMessage));
                    return;
                }

                // First, hide all fieldset (not all will be needed)
                catscontainer.find('fieldset').hide();

                // Iterate categories
                $.each(data.Result.ServiceAttributeCategories, function (iCat, cat) {
                    // Locate the category fieldset
                    var fs = $('#service-attribute-category-' + cat.ServiceAttributeCategoryID);

                    // Only if there are attributes on the category, else break into next
                    if (!cat.ServiceAttributes || cat.ServiceAttributes.length == 0
                        || cat.ServiceAttributeCategoryID == -5)
                        return; // break each

                    // If category fieldset doesn't exist, create it:
                    if (fs.length == 0) {
                        var fs = $(tplCategory
                            .replace(/@0/g, cat.ServiceAttributeCategoryID)
                            .replace(/@1/g, cat.ServiceAttributeCategoryName)
                            .replace(/@2/g, cat.ServiceAttributeCategoryDescription));
                        fscontainer.append(fs);
                    }

                    // Update Fieldset Label
                    fs.find('legend').text(cat.ServiceAttributeCategoryName)
                        .attr('title', cat.ServiceAttributeCategoryName)
                        .attr('data-description', cat.ServiceAttributeCategoryDescription);

                    // Remove existing attributes
                    fs.find('>*:not(legend)').remove();

                    switch (cat.ServiceAttributeCategoryID) {
                        /*case 2:
                        case 3:
                        case 7:*/
                        default:
                            fs.append(createAttCheckList(cat));
                            break;
                        case 5:
                            fs.append(createAttCheckList(cat, '-level', data.Result.ServiceAttributeCategories[-5]));
                            break;
                        case 1:
                            fs.append(createAttSelect(cat));
                            break;
                        case 4:
                            fs.append(createAttSelect(cat, '-level'));
                            break;
                        case -5:
                            // do nothing with virtual languages level category, is used on catID:5 for every language
                            break;
                    }

                    // Show it again
                    fs.show();
                });

                jAttributes.unblock();
                jActions.unblock();
            },
            error: function (jqXHR, errorText) {
                jAttributes.parent().unblock().block(errorBlock());
            }
        });
    }

    // Positions Load:
    //loadPositions();

    // First Attributes Load:
    if (jSelectPosition.length > 0)
        $.proxy(loadAttributes, jSelectPosition.get(0))();

    // Auto post back when select a position:
    jSelectPosition.change(loadAttributes);
};

$("#your-work").bind('endLoadWizardStep', initYourWork)
    .bind('endSubmitWizardStep', function (event, ok) { if (!ok) initYourWork(); });
<|endoftext|>


<commit_before>import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import morgan from 'morgan';

const app = express();
// const env = process.env.NODE_ENV = process.env.NODE_ENV || 'development';
// const dev = env === 'development';
const port = process.env.PORT || 8080;

app.use(express.static(`${__dirname}/../public`));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(morgan('dev'));

app.listen(port, () => {
    console.log(`Server is listening on port ${port}`);
});
<commit_msg>Changed path to public folder<commit_after>import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import morgan from 'morgan';

const app = express();
// const env = process.env.NODE_ENV = process.env.NODE_ENV || 'development';
// const dev = env === 'development';
const port = process.env.PORT || 8080;

app.use(express.static(`${__dirname}/public`));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(morgan('dev'));

app.listen(port, () => {
    console.log(`Server is listening on port ${port}`);
});
<|endoftext|>