<commit_before>const prompt = require('prompt')

exports.getUserInput = () => new Promise((resolve, reject) => {
  const schema = {
    properties: {
      ToolName: {
        description: 'Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      RootToolName: {
        description: 'Root Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Root Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      Version: {
        description: 'Tool Version',
        pattern: /^[\d]{0,25}$/,
        message: 'Must be written as a whole number and be 25 characters or less.',
        default: 1,
        required: false
      },
      Author: {
        description: 'Author',
        pattern: /^[a-zA-Z\s\d.\-_]{1,150}$/,
        message: 'Author is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: true
      },
      Company: {
        description: 'Company',
        pattern: /^[a-zA-Z\s\d.\-_]{0,150}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: false
      },
      Copyright: {
        description: 'Copyright',
        pattern: /^[\d]{4}$/,
        message: 'May only contain numbers and be 4 characters long.',
        required: false
      },
      Category: {
        description: 'Category',
        pattern: /^[a-zA-Z\s]{0,100}$/,
        message: 'May only contain letters or spaces and be 100 characters or less.',
        required: false
      },
      Description: {
        description: 'Description',
        pattern: /^[a-zA-Z\s\d.\-_]{0,1000}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 1,000 characters or less.',
        required: false
      },
      SearchTags: {
        description: 'Search Tags (comma separated)',
        pattern: /^[a-zA-Z\s,]{0,200}$/,
        message: 'May only contain letters, spaces, or commas and be 200 characters or less.',
        required: false
      },
      Backend: {
        description: 'Will the engine for your tool be a macro (M) or JavaScript (J) file?',
        pattern: /M{1}|J{1}|m{1}|j{1}/,
        message: 'Please enter M (macro) or J (JavaScript).',
        required: true
      },
      IconPath: {
        description: 'Path to Icon image file',
        pattern: /[A-Z]:\\[a-zA-Z\s\d.\-_\\]{0,246}.png$|[A-Z]:\\[a-zA-Z\s\d.\-_\\]{0,246}.PNG$/,
        message: 'Please select a png image file. Path must be 250 characters or less.',
        default: 'default_icon.png',
        required: false
      },
      InputConnections: {
        description: 'Number of input connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      },
      OutputConnections: {
        description: 'Number of output connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      }
    }
  }

  console.log('\nEnter the following to configure your project...\n')
  prompt.message = '' // removes prompt: from the front of each question

  prompt.start()
  prompt.get(schema, (err, result) => {
    if (err) {
      reject(err)
      return
    }

    const userInput = result

    const inputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.InputConnections; i += 1) {
      const name = `InputConnectionName_${i}`
      const nameValue = {
        description: `Input Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      inputSchema.properties[name] = nameValue

      const label = `InputConnectionLabel_${i}`
      const labelValue = {
        description: `Input Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      inputSchema.properties[label] = labelValue
    }

    const outputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.OutputConnections; i += 1) {
      const name = `OutputConnectionName_${i}`
      const nameValue = {
        description: `Output Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      outputSchema.properties[name] = nameValue

      const label = `OutputConnectionLabel_${i}`
      const labelValue = {
        description: `Output Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      outputSchema.properties[label] = labelValue
    }

    if (userInput.InputConnections > 0) {
      console.log(`\n${userInput.InputConnections} input connections specified. Please enter name and label for each (Optional).\n`)
    }
    // prompt.get needs to be nested otherwise it acts weird and results in duplicate entries
    prompt.get(inputSchema, (inputErr, inputResult) => {
      userInput.InputDetails = inputResult
      if (userInput.OutputConnections > 0) {
        console.log(`\n${userInput.OutputConnections} output connections specified. Please enter name and label for each (Optional).`)
      }
      prompt.get(outputSchema, (outputErr, outputResult) => {
        userInput.OutputDetails = outputResult
        console.log('\nProject Configuration:\n')
        console.log(JSON.stringify(userInput, null, 4))
        resolve(userInput)
      })
    })
  })
})
<commit_msg>US26183: added 'default_icon.png' as option in icon path regex<commit_after>const prompt = require('prompt')

exports.getUserInput = () => new Promise((resolve, reject) => {
  const schema = {
    properties: {
      ToolName: {
        description: 'Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      RootToolName: {
        description: 'Root Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Root Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      Version: {
        description: 'Tool Version',
        pattern: /^[\d]{0,25}$/,
        message: 'Must be written as a whole number and be 25 characters or less.',
        default: 1,
        required: false
      },
      Author: {
        description: 'Author',
        pattern: /^[a-zA-Z\s\d.\-_]{1,150}$/,
        message: 'Author is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: true
      },
      Company: {
        description: 'Company',
        pattern: /^[a-zA-Z\s\d.\-_]{0,150}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: false
      },
      Copyright: {
        description: 'Copyright',
        pattern: /^[\d]{4}$/,
        message: 'May only contain numbers and be 4 characters long.',
        required: false
      },
      Category: {
        description: 'Category',
        pattern: /^[a-zA-Z\s]{0,100}$/,
        message: 'May only contain letters or spaces and be 100 characters or less.',
        required: false
      },
      Description: {
        description: 'Description',
        pattern: /^[a-zA-Z\s\d.\-_]{0,1000}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 1,000 characters or less.',
        required: false
      },
      SearchTags: {
        description: 'Search Tags (comma separated)',
        pattern: /^[a-zA-Z\s,]{0,200}$/,
        message: 'May only contain letters, spaces, or commas and be 200 characters or less.',
        required: false
      },
      Backend: {
        description: 'Will the engine for your tool be a macro (M) or JavaScript (J) file?',
        pattern: /M{1}|J{1}|m{1}|j{1}/,
        message: 'Please enter M (macro) or J (JavaScript).',
        required: true
      },
      IconPath: {
        description: 'Path to Icon image file',
        pattern: /default_icon.png|[A-Z]:\\[a-zA-Z\s\d.\-_\\]{0,246}.png$|[A-Z]:\\[a-zA-Z\s\d.\-_\\]{0,246}.PNG$/,
        message: 'Please select a png image file. Path must be 250 characters or less.',
        default: 'default_icon.png',
        required: false
      },
      InputConnections: {
        description: 'Number of input connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      },
      OutputConnections: {
        description: 'Number of output connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      }
    }
  }

  console.log('\nEnter the following to configure your project...\n')
  prompt.message = '' // removes prompt: from the front of each question

  prompt.start()
  prompt.get(schema, (err, result) => {
    if (err) {
      reject(err)
      return
    }

    const userInput = result

    const inputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.InputConnections; i += 1) {
      const name = `InputConnectionName_${i}`
      const nameValue = {
        description: `Input Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      inputSchema.properties[name] = nameValue

      const label = `InputConnectionLabel_${i}`
      const labelValue = {
        description: `Input Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      inputSchema.properties[label] = labelValue
    }

    const outputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.OutputConnections; i += 1) {
      const name = `OutputConnectionName_${i}`
      const nameValue = {
        description: `Output Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      outputSchema.properties[name] = nameValue

      const label = `OutputConnectionLabel_${i}`
      const labelValue = {
        description: `Output Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      outputSchema.properties[label] = labelValue
    }

    if (userInput.InputConnections > 0) {
      console.log(`\n${userInput.InputConnections} input connections specified. Please enter name and label for each (Optional).\n`)
    }
    // prompt.get needs to be nested otherwise it acts weird and results in duplicate entries
    prompt.get(inputSchema, (inputErr, inputResult) => {
      userInput.InputDetails = inputResult
      if (userInput.OutputConnections > 0) {
        console.log(`\n${userInput.OutputConnections} output connections specified. Please enter name and label for each (Optional).`)
      }
      prompt.get(outputSchema, (outputErr, outputResult) => {
        userInput.OutputDetails = outputResult
        console.log('\nProject Configuration:\n')
        console.log(JSON.stringify(userInput, null, 4))
        resolve(userInput)
      })
    })
  })
})
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*
 * Dependencies
 *
 * /dev/lib/misc/trimpath.template.js (TrimpathTemplates)
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 */

/*global $, Config, sakai, History, opensocial, Widgets */
require(["jquery", "sakai/sakai.api.core"], function($, sakai) {
    /**
     * This is a widget that can be placed in other pages and widgets.
     * It shows an Add to contacts dialog.
     * All you have to do is provide a user object that contains:
     *  - first name
     *  - last name
     *  - uuid of the person you wish to add.
     *
     *  You can also set the personal note by using the setPersonalNote() method
     *  and select the type by using the setTypes().
     */
    sakai_global.addtocontacts = function(tuid, showSettings){

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        // Help variables
        var me = sakai.data.me;
        var friend = false;
        var callbackWhenDone = false;
        var fadeOutTime = 1000; // The amount of time it takes to fade out the message and hide the layover.
        // CSS selectors
        var addToContacts = "#addtocontacts";
        var addToContactsClass = ".addtocontacts";

        var addToContactsAdd = addToContacts + "_add";
        var addToContactsDialog = addToContacts + "_dialog";
        var addToContactsDone = addToContacts + "_done";
        var addToContactsDoneContainer = addToContacts + "_done_container";

        // Form elements
        var addToContactsForm = addToContacts + "_form";
        var addToContactsFormButtonInvite = addToContactsForm + "_invite";
        var addToContactsFormButtonCancel = addToContactsForm + "_cancel";
        var addToContactsFormPersonalNote = addToContactsForm + "_personalnote";
        var addToContactsFormPersonalNoteTemplate = addToContactsFormPersonalNote + "_template";
        var addToContactsFormType = addToContactsForm + "_type";
        var addToContactsFormTypeTemplate = addToContactsFormType + "_template";
        // Profile info
        var addToContactsInfoProfilePicture = addToContacts + "_profilepicture";
        var addToContactsInfoTypes = addToContacts + "_types";
        var addToContactsInfoDisplayName = addToContactsClass + "_displayname";

        // Error messages
        var addToContactsError = addToContacts + "_error";
        var addToContactsErrorMessage = addToContactsError + "_message";
        var addToContactsErrorRequest = addToContactsError + "_request";
        var addToContactsErrorNoTypeSelected = addToContactsError + "_noTypeSelected";

        var addToContactsResponse = addToContacts + "_response";

        ///////////////////
        // Functionality //
        ///////////////////

        /**
         * Render the templates that are needed for the add contacts widget.
         * It renders the contacts types and the personal note
         */
        var renderTemplates = function(){
            sakai.api.Util.TemplateRenderer(addToContactsFormTypeTemplate.replace(/#/gi, ""), sakai.config.Relationships, $(addToContactsInfoTypes));
            var json = {
                sakai: sakai,
                me: me
            };
            sakai.api.Util.TemplateRenderer(addToContactsFormPersonalNoteTemplate.replace(/#/gi, ""), json, $(addToContactsFormPersonalNote));
        };

        /**
         * This method will fill in the info for the user.
         * @param {Object} user The JSON object containing the user info. This follows the /rest/me format.
         */
        var fillInUserInfo = function(user){
            if (user) {

                $(addToContactsInfoDisplayName).text(sakai.api.User.getDisplayName(user));

                // Check for picture
                if (user.picture && $.parseJSON(user.picture).name) {
                    $(addToContactsInfoProfilePicture).html('');
                }
                else {
                    $(addToContactsInfoProfilePicture).html('');
                }
            }
        };

        /**
         * This function looks up and retrieves relationship information from a set of pre-defined relationships
         * @param {String} relationshipName
         */
        var getDefinedRelationship = function(relationshipName){
            for (var i = 0, j = sakai.config.Relationships.contacts.length; i < j; i++) {
                var definedRelationship = sakai.config.Relationships.contacts[i];
                if (definedRelationship.name === relationshipName) {
                    return definedRelationship;
                }
            }
            return null;
        };

        /**
         * Does the invitation stuff. Will send a request for an invitation and a message to the user.
         * @param {String} userid
         */
        var doInvite = function(userid){
            var formValues = $(addToContactsForm).serializeObject();
            var types = formValues[addToContactsFormType.replace(/#/gi, "")];
            $(addToContactsResponse).text("");
            if (types.length) {
                var fromRelationshipsToSend = [];
                var toRelationshipsToSend = [];
                for (var i = 0, j = types.length; i < j; i++) {
                    var type = types[i];
                    fromRelationshipsToSend.push(type);
                    var definedRelationshipToSend = getDefinedRelationship(type);
                    if (definedRelationshipToSend && definedRelationshipToSend.inverse) {
                        toRelationshipsToSend.push(definedRelationshipToSend.inverse);
                    }
                    else {
                        toRelationshipsToSend.push(type);
                    }
                }

                var personalnote = formValues[addToContactsFormPersonalNote.replace(/#/gi, '')];

                // send message to other person
                var userstring = sakai.api.User.getDisplayName(me.profile);

                var title = $("#addtocontacts_invitation_title_key").html().replace(/\$\{user\}/g, userstring);
                var message = $("#addtocontacts_invitation_body_key").html().replace(/\$\{user\}/g, userstring).replace(/\$\{br\}/g,"\n") + "," + personalnote;

                // Do the invite and send a message
                $.ajax({
                    url: "/~" + sakai.data.me.user.userid + "/contacts.invite.html",
                    type: "POST",
                    traditional: true,
                    data: {
                        "fromRelationships": fromRelationshipsToSend,
                        "toRelationships": toRelationshipsToSend,
                        "targetUserId": userid
                    },
                    success: function(data){
                        $(addToContactsDialog).jqmHide();
                        sakai.api.Communication.sendMessage(userid, sakai.data.me.user.userid, title, message, "invitation");
                        callbackWhenDone(friend);
                        //reset the form to set original note
                        $(addToContactsForm)[0].reset();
                        sakai.api.Util.notification.show("", $(addToContactsDone).text());
                        // record that user made contact request
                        sakai.api.User.addUserProgress("madeContactRequest");
                        // display tooltip
                        var tooltipData = {
                            "tooltipSelector":"#search_button",
                            "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                            "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P5",
                            "tooltipTop":-175,
                            "tooltipLeft":0,
                            "tooltipAutoClose":true
                        };
                        $(window).trigger("sakai-tooltip-update", tooltipData);
                    },
                    error: function(xhr, textStatus, thrownError){
                        $(addToContactsResponse).text(sakai.api.Security.saneHTML($(addToContactsErrorRequest).text()));
                    }
                });

            }
            else {
                $(addToContactsResponse).text(sakai.api.Security.saneHTML($(addToContactsErrorNoTypeSelected).text()));
            }
        };

        ///////////////////////
        // jqModal functions //
        ///////////////////////

        /**
         * This will load the overlay to add a new contact.
         * This method will fill in all the user info.
         * @param {Object} hash The layover object we get from jqModal
         */
        var loadDialog = function(hash){
            // Show the form
            $(addToContactsDoneContainer).hide();
            $(addToContactsAdd).show();

            hash.w.show();
        };

        ////////////////////
        // Public methods //
        ////////////////////

        /**
         * Set a personal note.
         * @param {string} note The text you wish to display in the note.
         */
        var setPersonalNote = function(note){
            $(addToContactsFormPersonalNote).val(note);
        };

        /////////////////////////
        // Initialise function //
        /////////////////////////

        /**
         * People should call this function if they want to initiate the widget
         * @param {Object} user The userid or the /rest/me info for this user.
         * @param {Function} callback The callback function that will be executed after the request.
         */
        var initialize = function(user, callback){
            callbackWhenDone = callback;
            // Check if we have a JSON object or a userid String.
            if (!user.preferences) {
                // This is a uuid. Fetch the info from /rest/me
                $.ajax({
                    url: "/~" + user + "/public/authprofile.infinity.json",
                    success: function(data){

                        friend = $.extend(data, {}, true);
                        friend.uuid = user;

                        // We have the data, render it.
                        fillInUserInfo(friend);
                    }
                });
            }
            else {
                friend = user;
                friend.uuid = user.preferences.uuid;
                fillInUserInfo(friend);
            }

            // Render the templates
            renderTemplates();

            // Show the layover
            $(addToContactsDialog).jqmShow();

            // Give the user options to manipulate this widget.
            return {
                "setPersonalNote": setPersonalNote
            };
        };

        $(window).bind("initialize.addToContacts.sakai", function(e, obj) {
            var user = obj.user;
            var callback = obj.callback;
            initialize(user, callback);
        });

        /////////////////////
        // Event listeners //
        /////////////////////

        // Bind the invite button
        $(addToContactsFormButtonInvite).bind("click", function(){
            // Invite this person.
            doInvite(friend.uuid);
        });

        // Bind the cancel button
        $(addToContactsFormButtonCancel).click(function(){
            $(addToContactsForm)[0].reset();

            // display tooltip
            var tooltipData = {
                "tooltipSelector":"#search_button",
                "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P3",
                "tooltipTop":-150,
                "tooltipLeft":-200
            };
            $(window).trigger("sakai-tooltip-update", tooltipData);
        });

        $(".jqmClose").bind("click", function(){
            // display tooltip
            var tooltipData = {
                "tooltipSelector":"#search_button",
                "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P3",
                "tooltipTop":-150,
                "tooltipLeft":-200
            };
            $(window).trigger("sakai-tooltip-update", tooltipData);
        });

        // Bind the jqModal
        $(addToContactsDialog).jqm({
            modal: true,
            overlay: 20,
            toTop: true,
            onShow: loadDialog
        });
    };

    sakai.api.Widgets.widgetLoader.informOnLoad("addtocontacts");
});
<commit_msg>SAKIII-2299 modified contact request message<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*
 * Dependencies
 *
 * /dev/lib/misc/trimpath.template.js (TrimpathTemplates)
 * /dev/lib/jquery/plugins/jqmodal.sakai-edited.js
 */

/*global $, Config, sakai, History, opensocial, Widgets */
require(["jquery", "sakai/sakai.api.core"], function($, sakai) {
    /**
     * This is a widget that can be placed in other pages and widgets.
     * It shows an Add to contacts dialog.
     * All you have to do is provide a user object that contains:
     *  - first name
     *  - last name
     *  - uuid of the person you wish to add.
     *
     *  You can also set the personal note by using the setPersonalNote() method
     *  and select the type by using the setTypes().
     */
    sakai_global.addtocontacts = function(tuid, showSettings){

        /////////////////////////////
        // Configuration variables //
        /////////////////////////////

        // Help variables
        var me = sakai.data.me;
        var friend = false;
        var callbackWhenDone = false;
        var fadeOutTime = 1000; // The amount of time it takes to fade out the message and hide the layover.
        // CSS selectors
        var addToContacts = "#addtocontacts";
        var addToContactsClass = ".addtocontacts";

        var addToContactsAdd = addToContacts + "_add";
        var addToContactsDialog = addToContacts + "_dialog";
        var addToContactsDone = addToContacts + "_done";
        var addToContactsDoneContainer = addToContacts + "_done_container";

        // Form elements
        var addToContactsForm = addToContacts + "_form";
        var addToContactsFormButtonInvite = addToContactsForm + "_invite";
        var addToContactsFormButtonCancel = addToContactsForm + "_cancel";
        var addToContactsFormPersonalNote = addToContactsForm + "_personalnote";
        var addToContactsFormPersonalNoteTemplate = addToContactsFormPersonalNote + "_template";
        var addToContactsFormType = addToContactsForm + "_type";
        var addToContactsFormTypeTemplate = addToContactsFormType + "_template";
        // Profile info
        var addToContactsInfoProfilePicture = addToContacts + "_profilepicture";
        var addToContactsInfoTypes = addToContacts + "_types";
        var addToContactsInfoDisplayName = addToContactsClass + "_displayname";

        // Error messages
        var addToContactsError = addToContacts + "_error";
        var addToContactsErrorMessage = addToContactsError + "_message";
        var addToContactsErrorRequest = addToContactsError + "_request";
        var addToContactsErrorNoTypeSelected = addToContactsError + "_noTypeSelected";

        var addToContactsResponse = addToContacts + "_response";

        ///////////////////
        // Functionality //
        ///////////////////

        /**
         * Render the templates that are needed for the add contacts widget.
         * It renders the contacts types and the personal note
         */
        var renderTemplates = function(){
            sakai.api.Util.TemplateRenderer(addToContactsFormTypeTemplate.replace(/#/gi, ""), sakai.config.Relationships, $(addToContactsInfoTypes));
            var json = {
                sakai: sakai,
                me: me
            };
            sakai.api.Util.TemplateRenderer(addToContactsFormPersonalNoteTemplate.replace(/#/gi, ""), json, $(addToContactsFormPersonalNote));
        };

        /**
         * This method will fill in the info for the user.
         * @param {Object} user The JSON object containing the user info. This follows the /rest/me format.
         */
        var fillInUserInfo = function(user){
            if (user) {

                $(addToContactsInfoDisplayName).text(sakai.api.User.getDisplayName(user));

                // Check for picture
                if (user.picture && $.parseJSON(user.picture).name) {
                    $(addToContactsInfoProfilePicture).html('');
                }
                else {
                    $(addToContactsInfoProfilePicture).html('');
                }
            }
        };

        /**
         * This function looks up and retrieves relationship information from a set of pre-defined relationships
         * @param {String} relationshipName
         */
        var getDefinedRelationship = function(relationshipName){
            for (var i = 0, j = sakai.config.Relationships.contacts.length; i < j; i++) {
                var definedRelationship = sakai.config.Relationships.contacts[i];
                if (definedRelationship.name === relationshipName) {
                    return definedRelationship;
                }
            }
            return null;
        };

        /**
         * Does the invitation stuff. Will send a request for an invitation and a message to the user.
         * @param {String} userid
         */
        var doInvite = function(userid){
            var formValues = $(addToContactsForm).serializeObject();
            var types = formValues[addToContactsFormType.replace(/#/gi, "")];
            $(addToContactsResponse).text("");
            if (types.length) {
                var fromRelationshipsToSend = [];
                var toRelationshipsToSend = [];
                for (var i = 0, j = types.length; i < j; i++) {
                    var type = types[i];
                    fromRelationshipsToSend.push(type);
                    var definedRelationshipToSend = getDefinedRelationship(type);
                    if (definedRelationshipToSend && definedRelationshipToSend.inverse) {
                        toRelationshipsToSend.push(definedRelationshipToSend.inverse);
                    }
                    else {
                        toRelationshipsToSend.push(type);
                    }
                }

                var personalnote = formValues[addToContactsFormPersonalNote.replace(/#/gi, '')];

                // send message to other person
                var userstring = sakai.api.User.getDisplayName(me.profile);

                var title = $("#addtocontacts_invitation_title_key").html().replace(/\$\{user\}/g, userstring);
                var message = $("#addtocontacts_invitation_body_key").html().replace(/\$\{user\}/g, userstring).replace(/\$\{comment\}/g, personalnote).replace(/\$\{br\}/g,"\n") + ",";

                // Do the invite and send a message
                $.ajax({
                    url: "/~" + sakai.data.me.user.userid + "/contacts.invite.html",
                    type: "POST",
                    traditional: true,
                    data: {
                        "fromRelationships": fromRelationshipsToSend,
                        "toRelationships": toRelationshipsToSend,
                        "targetUserId": userid
                    },
                    success: function(data){
                        $(addToContactsDialog).jqmHide();
                        sakai.api.Communication.sendMessage(userid, sakai.data.me.user.userid, title, message, "invitation");
                        callbackWhenDone(friend);
                        //reset the form to set original note
                        $(addToContactsForm)[0].reset();
                        sakai.api.Util.notification.show("", $(addToContactsDone).text());
                        // record that user made contact request
                        sakai.api.User.addUserProgress("madeContactRequest");
                        // display tooltip
                        var tooltipData = {
                            "tooltipSelector":"#search_button",
                            "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                            "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P5",
                            "tooltipTop":-175,
                            "tooltipLeft":0,
                            "tooltipAutoClose":true
                        };
                        $(window).trigger("sakai-tooltip-update", tooltipData);
                    },
                    error: function(xhr, textStatus, thrownError){
                        $(addToContactsResponse).text(sakai.api.Security.saneHTML($(addToContactsErrorRequest).text()));
                    }
                });

            }
            else {
                $(addToContactsResponse).text(sakai.api.Security.saneHTML($(addToContactsErrorNoTypeSelected).text()));
            }
        };

        ///////////////////////
        // jqModal functions //
        ///////////////////////

        /**
         * This will load the overlay to add a new contact.
         * This method will fill in all the user info.
         * @param {Object} hash The layover object we get from jqModal
         */
        var loadDialog = function(hash){
            // Show the form
            $(addToContactsDoneContainer).hide();
            $(addToContactsAdd).show();

            hash.w.show();
        };

        ////////////////////
        // Public methods //
        ////////////////////

        /**
         * Set a personal note.
         * @param {string} note The text you wish to display in the note.
         */
        var setPersonalNote = function(note){
            $(addToContactsFormPersonalNote).val(note);
        };

        /////////////////////////
        // Initialise function //
        /////////////////////////

        /**
         * People should call this function if they want to initiate the widget
         * @param {Object} user The userid or the /rest/me info for this user.
         * @param {Function} callback The callback function that will be executed after the request.
         */
        var initialize = function(user, callback){
            callbackWhenDone = callback;
            // Check if we have a JSON object or a userid String.
            if (!user.preferences) {
                // This is a uuid. Fetch the info from /rest/me
                $.ajax({
                    url: "/~" + user + "/public/authprofile.infinity.json",
                    success: function(data){

                        friend = $.extend(data, {}, true);
                        friend.uuid = user;

                        // We have the data, render it.
                        fillInUserInfo(friend);
                    }
                });
            }
            else {
                friend = user;
                friend.uuid = user.preferences.uuid;
                fillInUserInfo(friend);
            }

            // Render the templates
            renderTemplates();

            // Show the layover
            $(addToContactsDialog).jqmShow();

            // Give the user options to manipulate this widget.
            return {
                "setPersonalNote": setPersonalNote
            };
        };

        $(window).bind("initialize.addToContacts.sakai", function(e, obj) {
            var user = obj.user;
            var callback = obj.callback;
            initialize(user, callback);
        });

        /////////////////////
        // Event listeners //
        /////////////////////

        // Bind the invite button
        $(addToContactsFormButtonInvite).bind("click", function(){
            // Invite this person.
            doInvite(friend.uuid);
        });

        // Bind the cancel button
        $(addToContactsFormButtonCancel).click(function(){
            $(addToContactsForm)[0].reset();

            // display tooltip
            var tooltipData = {
                "tooltipSelector":"#search_button",
                "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P3",
                "tooltipTop":-150,
                "tooltipLeft":-200
            };
            $(window).trigger("sakai-tooltip-update", tooltipData);
        });

        $(".jqmClose").bind("click", function(){
            // display tooltip
            var tooltipData = {
                "tooltipSelector":"#search_button",
                "tooltipTitle":"TOOLTIP_ADD_CONTACTS",
                "tooltipDescription":"TOOLTIP_ADD_CONTACTS_P3",
                "tooltipTop":-150,
                "tooltipLeft":-200
            };
            $(window).trigger("sakai-tooltip-update", tooltipData);
        });

        // Bind the jqModal
        $(addToContactsDialog).jqm({
            modal: true,
            overlay: 20,
            toTop: true,
            onShow: loadDialog
        });
    };

    sakai.api.Widgets.widgetLoader.informOnLoad("addtocontacts");
});
<|endoftext|>


<commit_before>import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { connect } from 'react-redux'

import { toggleSidebar } from '../actions/drawer'

class SidebarToggle extends Component {
  static propTypes = {
    isSidebarExpanded: PropTypes.bool.isRequired,
    pendingRequests: PropTypes.number.isRequired,
    toggleSidebar: PropTypes.func.isRequired,
  }

  render() {
    const hasPendingRequests = this.props.pendingRequests > 0
    const sidebarToggleClasses = classnames(
      'sidebar-toggle', {
        'sidebar-toggle--active': this.props.isSidebarExpanded,
        'sidebar-toggle--spinning': hasPendingRequests,
        'sidebar-toggle--notified': false,
      }
    )

    return (
      <button
        className="button button--clear navigation__toggle navigation__toggle--right"
        onClick={this.props.toggleSidebar}
      >

      </button>
    )
  }
}

function mapStateToProps(state) {
  return {
    isSidebarExpanded: state.drawer.isSidebarExpanded,
    pendingRequests: state.api.pendingRequests,
  }
}

export default connect(
  mapStateToProps, {
    toggleSidebar,
  }
)(SidebarToggle)
<commit_msg>Do not show spinner when loading time was too short<commit_after>import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { connect } from 'react-redux'

import { toggleSidebar } from '../actions/drawer'

const SPINNER_TRESHOLD = 100

let timeout

class SidebarToggle extends Component {
  static propTypes = {
    isSidebarExpanded: PropTypes.bool.isRequired,
    pendingRequests: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
    toggleSidebar: PropTypes.func.isRequired,
  }

  componentWillUpdate(nextProps, nextState) {
    const isPending = nextProps.pendingRequests > 0

    if (isPending && !nextState.hasPendingRequests) {
      timeout = setTimeout(() => {
        this.setState({
          hasPendingRequests: true,
        })
      }, SPINNER_TRESHOLD)
    } else if (!isPending && nextState.hasPendingRequests) {
      clearTimeout(timeout)

      this.setState({
        hasPendingRequests: false,
      })
    }
  }

  render() {
    const sidebarToggleClasses = classnames(
      'sidebar-toggle', {
        'sidebar-toggle--active': this.props.isSidebarExpanded,
        'sidebar-toggle--spinning': this.state.hasPendingRequests,
        'sidebar-toggle--notified': false,
      }
    )

    return (
      <button
        className="button button--clear navigation__toggle navigation__toggle--right"
        onClick={this.props.toggleSidebar}
      >

      </button>
    )
  }

  constructor(props) {
    super(props)

    this.state = {
      hasPendingRequests: false,
    }
  }
}

function mapStateToProps(state) {
  return {
    isSidebarExpanded: state.drawer.isSidebarExpanded,
    pendingRequests: state.api.pendingRequests,
  }
}

export default connect(
  mapStateToProps, {
    toggleSidebar,
  }
)(SidebarToggle)
<|endoftext|>


<commit_before>const prompt = require('prompt')

exports.getUserInput = () => new Promise((resolve, reject) => {
  const schema = {
    properties: {
      ToolName: {
        description: 'Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 100 characters.',
        required: true
      },
      RootToolName: {
        description: 'Root Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Root Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 100 characters.',
        required: true
      },
      Version: {
        description: 'Tool Version',
        pattern: /^[\d]{0,25}$/,
        message: 'Must be written as a whole number and be fewer than 25 characters.',
        default: 1,
        required: false
      },
      Author: {
        description: 'Author',
        pattern: /^[a-zA-Z\s\d.\-_]{1,150}$/,
        message: 'Author is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 150 characters.',
        required: true
      },
      Company: {
        description: 'Company',
        pattern: /^[a-zA-Z\s\d.\-_]{0,150}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 150 characters.',
        required: false
      },
      Copyright: {
        description: 'Copyright',
        pattern: /^[\d]{4}$/,
        message: 'May only contain numbers and be 4 characters long.',
        required: false
      },
      Category: {
        description: 'Category',
        pattern: /^[a-zA-Z\s]{0,100}$/,
        message: 'May only contain letters or spaces and be fewer than 100 characters.',
        required: false
      },
      Description: {
        description: 'Description',
        pattern: /^[a-zA-Z\s\d.\-_]{0,1000}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 1,000 characters.',
        required: false
      },
      SearchTags: {
        description: 'Search Tags (comma separated)',
        pattern: /^[a-zA-Z\s,]{0,200}$/,
        message: 'May only contain letters, spaces, or commas and be fewer than 200 characters.',
        required: false
      },
      Backend: {
        description: 'Will the engine for your tool be a macro (M) or JavaScript (J) file?',
        pattern: /M{1}|J{1}/,
        message: 'Please enter M (macro) or J (JavaScript).',
        required: true
      },
      IconPath: {
        description: 'Path to Icon image file',
        pattern: /(.png$)|(.PNG$){0,250}/,
        message: 'Please select a png image file. Path must be fewer than 250 characters long.',
        default: 'default_icon.png',
        required: false
      },
      InputConnections: {
        description: 'Number of input connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      },
      OutputConnections: {
        description: 'Number of output connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      }
    }
  }

  console.log('\nEnter the following to configure your project...\n')
  prompt.message = '' // removes prompt: from the front of each question

  prompt.start()
  prompt.get(schema, (err, result) => {
    if (err) {
      reject(err)
      return
    }

    const userInput = result

    const inputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.InputConnections; i += 1) {
      const name = `InputConnectionName_${i}`
      const nameValue = {
        description: `Input Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 100 characters.',
        type: 'string',
        required: false
      }
      inputSchema.properties[name] = nameValue

      const label = `InputConnectionLabel_${i}`
      const labelValue = {
        description: `Input Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      inputSchema.properties[label] = labelValue
    }

    const outputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.OutputConnections; i += 1) {
      const name = `OutputConnectionName_${i}`
      const nameValue = {
        description: `Output Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be fewer than 100 characters.',
        type: 'string',
        required: false
      }
      outputSchema.properties[name] = nameValue

      const label = `OutputConnectionLabel_${i}`
      const labelValue = {
        description: `Output Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      outputSchema.properties[label] = labelValue
    }

    if (userInput.InputConnections > 0) {
      console.log(`\n${userInput.InputConnections} input connections specified. Please enter name and label for each (Optional).\n`)
    }
    // prompt.get needs to be nested otherwise it acts weird and results in duplicate entries
    prompt.get(inputSchema, (inputErr, inputResult) => {
      userInput.InputDetails = inputResult
      if (userInput.OutputConnections > 0) {
        console.log(`\n${userInput.OutputConnections} output connections specified. Please enter name and label for each (Optional).`)
      }
      prompt.get(outputSchema, (outputErr, outputResult) => {
        userInput.OutputDetails = outputResult
        console.log('\nProject Configuration:\n')
        console.log(JSON.stringify(userInput, null, 4))
        resolve(userInput)
      })
    })
  })
})
<commit_msg>US26183: updated error messaging to include character limit <commit_after>const prompt = require('prompt')

exports.getUserInput = () => new Promise((resolve, reject) => {
  const schema = {
    properties: {
      ToolName: {
        description: 'Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      RootToolName: {
        description: 'Root Tool Name',
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'Root Tool Name is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        required: true
      },
      Version: {
        description: 'Tool Version',
        pattern: /^[\d]{0,25}$/,
        message: 'Must be written as a whole number and be 25 characters or less.',
        default: 1,
        required: false
      },
      Author: {
        description: 'Author',
        pattern: /^[a-zA-Z\s\d.\-_]{1,150}$/,
        message: 'Author is required. May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: true
      },
      Company: {
        description: 'Company',
        pattern: /^[a-zA-Z\s\d.\-_]{0,150}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 150 characters or less.',
        required: false
      },
      Copyright: {
        description: 'Copyright',
        pattern: /^[\d]{4}$/,
        message: 'May only contain numbers and be 4 characters long.',
        required: false
      },
      Category: {
        description: 'Category',
        pattern: /^[a-zA-Z\s]{0,100}$/,
        message: 'May only contain letters or spaces and be 100 characters or less.',
        required: false
      },
      Description: {
        description: 'Description',
        pattern: /^[a-zA-Z\s\d.\-_]{0,1000}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 1,000 characters or less.',
        required: false
      },
      SearchTags: {
        description: 'Search Tags (comma separated)',
        pattern: /^[a-zA-Z\s,]{0,200}$/,
        message: 'May only contain letters, spaces, or commas and be 200 characters or less.',
        required: false
      },
      Backend: {
        description: 'Will the engine for your tool be a macro (M) or JavaScript (J) file?',
        pattern: /M{1}|J{1}/,
        message: 'Please enter M (macro) or J (JavaScript).',
        required: true
      },
      IconPath: {
        description: 'Path to Icon image file',
        pattern: /(.png$)|(.PNG$){0,250}/,
        message: 'Please select a png image file. Path must be 250 characters or less.',
        default: 'default_icon.png',
        required: false
      },
      InputConnections: {
        description: 'Number of input connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      },
      OutputConnections: {
        description: 'Number of output connections',
        pattern: /^[0-5]{1}$/,
        message: 'Must be written as a whole number between 0 and 5.',
        required: true
      }
    }
  }

  console.log('\nEnter the following to configure your project...\n')
  prompt.message = '' // removes prompt: from the front of each question

  prompt.start()
  prompt.get(schema, (err, result) => {
    if (err) {
      reject(err)
      return
    }

    const userInput = result

    const inputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.InputConnections; i += 1) {
      const name = `InputConnectionName_${i}`
      const nameValue = {
        description: `Input Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      inputSchema.properties[name] = nameValue

      const label = `InputConnectionLabel_${i}`
      const labelValue = {
        description: `Input Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      inputSchema.properties[label] = labelValue
    }

    const outputSchema = {
      'properties': {}
    }

    for (let i = 1; i <= userInput.OutputConnections; i += 1) {
      const name = `OutputConnectionName_${i}`
      const nameValue = {
        description: `Output Connection Name ${i}`,
        pattern: /^[a-zA-Z\s\d.\-_]{1,100}$/,
        message: 'May only contain letters, numbers, spaces, periods, underscores, or dashes and be 100 characters or less.',
        type: 'string',
        required: false
      }
      outputSchema.properties[name] = nameValue

      const label = `OutputConnectionLabel_${i}`
      const labelValue = {
        description: `Output Connection Label ${i}`,
        pattern: /^[a-zA-Z\d]{1}$/,
        message: 'May only contain one letter or number.',
        type: 'string',
        required: false
      }
      outputSchema.properties[label] = labelValue
    }

    if (userInput.InputConnections > 0) {
      console.log(`\n${userInput.InputConnections} input connections specified. Please enter name and label for each (Optional).\n`)
    }
    // prompt.get needs to be nested otherwise it acts weird and results in duplicate entries
    prompt.get(inputSchema, (inputErr, inputResult) => {
      userInput.InputDetails = inputResult
      if (userInput.OutputConnections > 0) {
        console.log(`\n${userInput.OutputConnections} output connections specified. Please enter name and label for each (Optional).`)
      }
      prompt.get(outputSchema, (outputErr, outputResult) => {
        userInput.OutputDetails = outputResult
        console.log('\nProject Configuration:\n')
        console.log(JSON.stringify(userInput, null, 4))
        resolve(userInput)
      })
    })
  })
})
<|endoftext|>


<commit_before>
(function(undefined) {

  // Calls Object.definePropert(y,ies) with configurable:true on all properties
  function define(obj, key, value) {
    if (typeof key == 'object') {
      Object.keys(key).forEach(function(name) {
        define(obj, name, key[name]);
      });
    } else {
      Object.defineProperty(obj, key, {
        configurable: true,
        value: value
      })
    }
  }

  // Add Array.prototype.view to create new views and
  // Array.prototype.updateViews to update all the views attached
  define(Array.prototype, {
    view: function view() {
      return createView(this);
    },
    updateViews: function updateViews() {
      if (!Array.isArray(this._views)) return false;
      this._views.forEach(function(view) {
        view.update();
      });
      return true;
    }
  });

  // create a new view on the source array (which may be a view itself)
  function createView(source) {
    var view = [];
    if (!source._views) initSource(source);
    initView(source, view);
    return view;
  }

  // add a _views array for updates and wrap the modifying functions
  function initSource(source) {
    define(source, '_views', []);
    define(source, modifiers);
  }

  // When the source is modified the views need to be updated
  function wrapModifier(modifier) {
    return function() {
      var results = modifier.apply(this, arguments);
      this.updateViews();
      return results;
    };
  }

  var proto = Array.prototype;
  var modifiers = {
    push: wrapModifier(proto.push),
    pop: wrapModifier(proto.pop),
    shift: wrapModifier(proto.shift),
    unshift: wrapModifier(proto.unshift),
    splice: wrapModifier(proto.splice)
  };

  // add view functions to the view array
  function initView(source, view) {
    attachToSource(source, view);
    define(view, viewMethods);
    view.update();
  }

  function attachToSource(source, view) {
    define(view, '_source', source);
    source._views.push(view);
    bindModifiers(source, view);
  }

  // bind the view's modifiers to the source, never modify the view
  function bindModifiers(source, view) {
    Object.keys(modifiers).forEach(function(modifier) {
      define(view, modifier, source[modifier].bind(source));
    });
  }

  // Remove an item from an array
  function remove(array, item) {
    var index = array.indexOf(item);
    if (index != -1) {
      array.splice(index, 1);
      return index;
    }
  }

  var sortDir = {
    asc: 1,
    desc: -1
  }

  function createGetter(field, dir) {
    var fields = field.split('.'),
        body = 'return ',
        ref = 0,
        refs = [],
        prevRef = 'item.';

    while (field = fields.shift()) {
      if (fields.length) {
        curRef = '_ref' + ref++;
        refs.push(curRef);
        body += '(' + curRef + ' = ' + prevRef + field + ') == null ? null : ';
        prevRef = curRef + '.';
      } else {
        body += prevRef + field;
      }
    }
    if (refs.length) body = 'var ' + refs.join(',') + ';\n' + body;
    var getter = new Function('item', body);
    getter.dir = dir || 1;
    return getter;
  }

  // create a sort function from arguments, see comments on `sort` below
  function createSort(sorts) {
    sorts = proto.slice.call(arguments);
    if (sorts[1] == 'asc' || sorts[1] == 'desc') sorts = [sorts];
    var getters = sorts.map(function(field) {
      var dir = 1;
      if (Array.isArray(field)) {
        dir = sortDir[field[1]] || field[1];
        field = field[0];
      }
      return createGetter(field, dir);
    });

    return function(a, b) {
      for (var i = 0; i < getters.length; i++) {
        var getter = getters[i];
        var valueA = getter(a);
        var valueB = getter(b);
        // handle nulls as less than
        if (valueB != null && valueA == null) return getter.dir;
        else if (valueA != null && valueB == null) return -getter.dir;
        else if (valueA > valueB) return getter.dir;
        else if (valueB > valueA) return -getter.dir;
      }
      return 0;
    }
  }

  // Adds the incredibly useful `createSort` function for any array to use.
  define(Array, 'createSort', createSort);

  var viewMethods = {

    // Adds a persistent filter function which takes a single argument, `item`,
    // and returns true or false if item should be allowed in the array. More
    // than one filter may be added and `name` (optional) can be used to remove
    // the filter later.
    addFilter: function addFilter(name, filter) {
      if (!this._filters) define(this, '_filters', []);
      if (typeof name == 'function') {
        filter = name;
        name = null;
      }
      if (typeof filter != 'function') return this;
      if (name) this._filters[name] = filter;
      this._filters.push(filter);
      return this.update();
    },

    // Removes a persistent filter which has been added. `filter` may be the
    // filter function or the name if `name` was used in `addFilter`. If
    // `filter` is not provided then `removeFilter()` will remove the last
    // filter added.
    removeFilter: function removeFilter(filter) {
      if (!this._filters) return this;
      if (typeof filter == 'string') {
        var name = filter;
        filter = this._filters[name];
        delete this._filters[name];
      } else if (typeof filter == 'undefined') {
        filter = this._filters[this._filters.length - 1];
      }
      if (typeof filter != 'function') return this;
      remove(this._filters, filter);
      return this.update();
    },

    // Sets a persistent sort on the array. Objects added or removed will
    // maintain the sort order of this sort function. Pass null to remove the
    // sort. You may use a sort function as Array.prototype.sort takes, or you
    // may use a special array view as the following examples:
    // ```
    // view.sort('dateCreated', 'desc');
    // view.sort('lastName', 'firstName', ['age', 'desc']);
    // view.sort('comments.length', ['postDate', 'desc']);
    // view.sort(['dateCreated', 'desc'], ['title', 'asc']);
    // ```
    // When only sorting by one property, you may use asc/desc as the second
    // parameter. 'asc' is always the default and is not necessary. When sorting
    // by multiple properties an array is required to add the sort order 'desc'.
    // chained properties (such as name.first or comments.length) may be as deep
    // as needed and will null-terminate, so no errors will occur. A null will
    // be treated as LESS THAN a non-null value.
    setSort: function setSort(sort) {
      if (sort && typeof sort != 'function') {
        sort = createSort.apply(null, arguments);
      }
      define(this, '_sort', sort || true);
      return this.update();
    },

    removeSort: function removeSort() {
      define(this, '_sort', undefined);
      return this.update();
    },

    sort: function sort() {
      var copy = this.slice();
      return copy.sort.apply(copy, arguments);
    },

    // Paginates the view by splitting it up into pages. Each page has
    // `pageSize` items in it--or fewer if it is the last page. `page` is the
    // initial page the view will display (default is 1). The page can be set
    // later with the `page` method. Pass in null to remove pagination.
    paginate: function paginate(pageSize, page) {
      define(this, '_pageSize', pageSize);
      define(this, '_page', page || 1);
      return this.updatePagination();
    },

    // Sets or gets the page number of a paginated array view. If you pass in
    // a number it will set it, otherwise it will return the current page
    // number. Pages are 1-based, not zero-based. I.e. the first page is page 1.
    pageNumber: function pageNumber(page) {
      if (typeof page != 'undefined') {
        define(this, '_page', page || 1);
        return this.updatePagination();
      } else if (this._pageSize) {
        return this._page || 1;
      } else {
        return 1;
      }
    },

    // Gets the page count for a paginated array view.
    pageCount: function pageCount() {
      if (!this._pageSize) return 1;
      return Math.ceil(this._unpaginated.length / this._pageSize);
    },

    // Returns the total length of the array, before pagination
    total: function total() {
      return this._unpaginated.length;
    },

    // Returns the page number which an item will appear on for a paingated
    // array view.
    itemPageNumber: function itemsPageNumber(item) {
      var index = this._unpaginated.indexOf(item);
      if (index == -1) return 0;
      if (!this._pageSize) return 1;
      return Math.floor(index / this._pageSize) + 1;
    },

    // Updates the view to reflect the latest state. This array will contain
    // only items from the source that haven't been filtered if any filters were
    // added, in the sort order that has been set if any, and only the items on
    // the current page if pagination was set.
    update: function update() {
      var array = this._source.slice();
      if (this._filters) this._filters.forEach(function(filter) {
        array = array.filter(filter);
      });
      if (this._sort) array.sort(this._sort);
      define(this, '_unpaginated', array);
      return this.updatePagination();
    },

    // Updates only the pagination, skipping the filtering and sorting steps
    // for simple page changes.
    updatePagination: function updatePagination() {
      var array = this._unpaginated;
      if (this._pageSize) {
        var count = this.pageCount();
        define(this, '_page', Math.min(count, Math.max(1, this._page || 1)));
        var begin = (this._page - 1) * this._pageSize;
        var end = this._page * this._pageSize;
        array = array.slice(begin, end);
      }

      this.length = 0;
      // Use proto.push because this.push is bound to the source array
      proto.push.apply(this, array);
      // Allow views to have views
      this.updateViews();
      return this;
    },

    // Detach the view from its source array. Use this to clean up the view as
    // it will not be garbage collected as long as the source array is kept in
    // memory. If the view and source arrays are no longer referenced, both
    // should be garbage collected. This creates a new source array that can be
    // used to update the view or discarded for garbage collection.
    detach: function detach() {
      remove(this._source._views, this);
      var newSource = this._source.slice();
      initSource(newSource);
      attachToSource(newSource, this);
      return newSource;
    }
  }

})()
<commit_msg>Removes Array API altering method.<commit_after>
(function(undefined) {

  // Calls Object.definePropert(y,ies) with configurable:true on all properties
  function define(obj, key, value) {
    if (typeof key == 'object') {
      Object.keys(key).forEach(function(name) {
        define(obj, name, key[name]);
      });
    } else {
      Object.defineProperty(obj, key, {
        configurable: true,
        value: value
      })
    }
  }

  // Add Array.prototype.view to create new views and
  // Array.prototype.updateViews to update all the views attached
  define(Array.prototype, {
    view: function view() {
      return createView(this);
    },
    updateViews: function updateViews() {
      if (!Array.isArray(this._views)) return false;
      this._views.forEach(function(view) {
        view.update();
      });
      return true;
    }
  });

  // create a new view on the source array (which may be a view itself)
  function createView(source) {
    var view = [];
    if (!source._views) initSource(source);
    initView(source, view);
    return view;
  }

  // add a _views array for updates and wrap the modifying functions
  function initSource(source) {
    define(source, '_views', []);
    define(source, modifiers);
  }

  // When the source is modified the views need to be updated
  function wrapModifier(modifier) {
    return function() {
      var results = modifier.apply(this, arguments);
      this.updateViews();
      return results;
    };
  }

  var proto = Array.prototype;
  var modifiers = {
    push: wrapModifier(proto.push),
    pop: wrapModifier(proto.pop),
    shift: wrapModifier(proto.shift),
    unshift: wrapModifier(proto.unshift),
    splice: wrapModifier(proto.splice)
  };

  // add view functions to the view array
  function initView(source, view) {
    attachToSource(source, view);
    define(view, viewMethods);
    view.update();
  }

  function attachToSource(source, view) {
    define(view, '_source', source);
    source._views.push(view);
    bindModifiers(source, view);
  }

  // bind the view's modifiers to the source, never modify the view
  function bindModifiers(source, view) {
    Object.keys(modifiers).forEach(function(modifier) {
      define(view, modifier, source[modifier].bind(source));
    });
  }

  // Remove an item from an array
  function remove(array, item) {
    var index = array.indexOf(item);
    if (index != -1) {
      array.splice(index, 1);
      return index;
    }
  }

  var sortDir = {
    asc: 1,
    desc: -1
  }

  function createGetter(field, dir) {
    var fields = field.split('.'),
        body = 'return ',
        ref = 0,
        refs = [],
        prevRef = 'item.';

    while (field = fields.shift()) {
      if (fields.length) {
        curRef = '_ref' + ref++;
        refs.push(curRef);
        body += '(' + curRef + ' = ' + prevRef + field + ') == null ? null : ';
        prevRef = curRef + '.';
      } else {
        body += prevRef + field;
      }
    }
    if (refs.length) body = 'var ' + refs.join(',') + ';\n' + body;
    var getter = new Function('item', body);
    getter.dir = dir || 1;
    return getter;
  }

  // create a sort function from arguments, see comments on `sort` below
  function createSort(sorts) {
    sorts = proto.slice.call(arguments);
    if (sorts[1] == 'asc' || sorts[1] == 'desc') sorts = [sorts];
    var getters = sorts.map(function(field) {
      var dir = 1;
      if (Array.isArray(field)) {
        dir = sortDir[field[1]] || field[1];
        field = field[0];
      }
      return createGetter(field, dir);
    });

    return function(a, b) {
      for (var i = 0; i < getters.length; i++) {
        var getter = getters[i];
        var valueA = getter(a);
        var valueB = getter(b);
        // handle nulls as less than
        if (valueB != null && valueA == null) return getter.dir;
        else if (valueA != null && valueB == null) return -getter.dir;
        else if (valueA > valueB) return getter.dir;
        else if (valueB > valueA) return -getter.dir;
      }
      return 0;
    }
  }

  // Adds the incredibly useful `createSort` function for any array to use.
  define(Array, 'createSort', createSort);

  var viewMethods = {

    // Adds a persistent filter function which takes a single argument, `item`,
    // and returns true or false if item should be allowed in the array. More
    // than one filter may be added and `name` (optional) can be used to remove
    // the filter later.
    addFilter: function addFilter(name, filter) {
      if (!this._filters) define(this, '_filters', []);
      if (typeof name == 'function') {
        filter = name;
        name = null;
      }
      if (typeof filter != 'function') return this;
      if (name) this._filters[name] = filter;
      this._filters.push(filter);
      return this.update();
    },

    // Removes a persistent filter which has been added. `filter` may be the
    // filter function or the name if `name` was used in `addFilter`. If
    // `filter` is not provided then `removeFilter()` will remove the last
    // filter added.
    removeFilter: function removeFilter(filter) {
      if (!this._filters) return this;
      if (typeof filter == 'string') {
        var name = filter;
        filter = this._filters[name];
        delete this._filters[name];
      } else if (typeof filter == 'undefined') {
        filter = this._filters[this._filters.length - 1];
      }
      if (typeof filter != 'function') return this;
      remove(this._filters, filter);
      return this.update();
    },

    // Sets a persistent sort on the array. Objects added or removed will
    // maintain the sort order of this sort function. Pass null to remove the
    // sort. You may use a sort function as Array.prototype.sort takes, or you
    // may use a special array view as the following examples:
    // ```
    // view.sort('dateCreated', 'desc');
    // view.sort('lastName', 'firstName', ['age', 'desc']);
    // view.sort('comments.length', ['postDate', 'desc']);
    // view.sort(['dateCreated', 'desc'], ['title', 'asc']);
    // ```
    // When only sorting by one property, you may use asc/desc as the second
    // parameter. 'asc' is always the default and is not necessary. When sorting
    // by multiple properties an array is required to add the sort order 'desc'.
    // chained properties (such as name.first or comments.length) may be as deep
    // as needed and will null-terminate, so no errors will occur. A null will
    // be treated as LESS THAN a non-null value.
    setSort: function setSort(sort) {
      if (sort && typeof sort != 'function') {
        sort = createSort.apply(null, arguments);
      }
      define(this, '_sort', sort || true);
      return this.update();
    },

    removeSort: function removeSort() {
      define(this, '_sort', undefined);
      return this.update();
    },

    // Paginates the view by splitting it up into pages. Each page has
    // `pageSize` items in it--or fewer if it is the last page. `page` is the
    // initial page the view will display (default is 1). The page can be set
    // later with the `page` method. Pass in null to remove pagination.
    paginate: function paginate(pageSize, page) {
      define(this, '_pageSize', pageSize);
      define(this, '_page', page || 1);
      return this.updatePagination();
    },

    // Sets or gets the page number of a paginated array view. If you pass in
    // a number it will set it, otherwise it will return the current page
    // number. Pages are 1-based, not zero-based. I.e. the first page is page 1.
    pageNumber: function pageNumber(page) {
      if (typeof page != 'undefined') {
        define(this, '_page', page || 1);
        return this.updatePagination();
      } else if (this._pageSize) {
        return this._page || 1;
      } else {
        return 1;
      }
    },

    // Gets the page count for a paginated array view.
    pageCount: function pageCount() {
      if (!this._pageSize) return 1;
      return Math.ceil(this._unpaginated.length / this._pageSize);
    },

    // Returns the total length of the array, before pagination
    total: function total() {
      return this._unpaginated.length;
    },

    // Returns the page number which an item will appear on for a paingated
    // array view.
    itemPageNumber: function itemsPageNumber(item) {
      var index = this._unpaginated.indexOf(item);
      if (index == -1) return 0;
      if (!this._pageSize) return 1;
      return Math.floor(index / this._pageSize) + 1;
    },

    // Updates the view to reflect the latest state. This array will contain
    // only items from the source that haven't been filtered if any filters were
    // added, in the sort order that has been set if any, and only the items on
    // the current page if pagination was set.
    update: function update() {
      var array = this._source.slice();
      if (this._filters) this._filters.forEach(function(filter) {
        array = array.filter(filter);
      });
      if (this._sort) array.sort(this._sort);
      define(this, '_unpaginated', array);
      return this.updatePagination();
    },

    // Updates only the pagination, skipping the filtering and sorting steps
    // for simple page changes.
    updatePagination: function updatePagination() {
      var array = this._unpaginated;
      if (this._pageSize) {
        var count = this.pageCount();
        define(this, '_page', Math.min(count, Math.max(1, this._page || 1)));
        var begin = (this._page - 1) * this._pageSize;
        var end = this._page * this._pageSize;
        array = array.slice(begin, end);
      }

      this.length = 0;
      // Use proto.push because this.push is bound to the source array
      proto.push.apply(this, array);
      // Allow views to have views
      this.updateViews();
      return this;
    },

    // Detach the view from its source array. Use this to clean up the view as
    // it will not be garbage collected as long as the source array is kept in
    // memory. If the view and source arrays are no longer referenced, both
    // should be garbage collected. This creates a new source array that can be
    // used to update the view or discarded for garbage collection.
    detach: function detach() {
      remove(this._source._views, this);
      var newSource = this._source.slice();
      initSource(newSource);
      attachToSource(newSource, this);
      return newSource;
    }
  }

})()
<|endoftext|>


<commit_before>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2020
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */
/**
 *  FormulaWizard.js
 *
 *  Created by Julia Radzhabova on 17.04.20
 *  Copyright (c) 2020 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'common/main/lib/component/Window',
    'common/main/lib/component/MetricSpinner'
], function () { 'use strict';

    SSE.Views.FormulaWizard = Common.UI.Window.extend(_.extend({
        options: {
            width: 420,
            height: 460,
            header: true,
            style: 'min-width: 350px;',
            cls: 'modal-dlg',
            buttons: ['ok', 'cancel']
        },

        initialize : function(options) {
            _.extend(this.options, {
                title: this.textTitle
            }, options || {});

            this.template = [
                '
',
                    '',
                    '
',
                        '
',
                            '
',
                            '
',
                        '
',
                    '
',
                    '
',
                        'Formula result:',
                        'Argument 1: some argument description',
                        '',
                        '',
                        ''+ this.textHelp +'',
                    '
',
                '
',
                '
'
            ].join('');

            this.options.tpl = _.template(this.template)(this.options);
            this.props = this.options.props;
            this.funcprops = this.options.funcprops;
            this.api = this.options.api;
            this._noApply = false;
            this.args = [];

            Common.UI.Window.prototype.initialize.call(this, this.options);
        },

        render: function() {
            Common.UI.Window.prototype.render.call(this);

            this.txtArg1 = new Common.UI.InputFieldBtn({
                el          : $('#formula-wizard-txt-arg1'),
                style       : 'width: 100%;',
                allowBlank  : true,
                validateOnChange: false
            });

            this.txtArg2 = new Common.UI.InputFieldBtn({
                el          : $('#formula-wizard-txt-arg2'),
                style       : 'width: 100%;',
                allowBlank  : true,
                validateOnChange: false
            });

            var $window = this.getChild();
            $window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
            $window.find('input').on('keypress', _.bind(this.onKeyPress, this));

            this.panelArgs = $window.find('#formula-wizard-panel-args');
            this.tableArgs = $window.find('#formula-wizard-tbl-args').parent();
            this.panelDesc = $window.find('#formula-wizard-panel-desc');

            this._preventCloseCellEditor = false;

            this.afterRender();
        },

        afterRender: function() {
            this.setSettings();
        },

        _handleInput: function(state) {
            if (this.options.handler)
                this.options.handler.call(this, state, (state == 'ok') ? this.getSettings() : undefined);
            this._preventCloseCellEditor = (state == 'ok');
            this.close();
        },

        onBtnClick: function(event) {
            this._handleInput(event.currentTarget.attributes['result'].value);
        },

        onKeyPress: function(event) {
            if (event.keyCode == Common.UI.Keys.RETURN) {
                this._handleInput('ok');
            }
        },

        setSettings: function () {
            var me = this;
            if (this.funcprops) {
                var props = this.funcprops;
                props.args ? $('#formula-wizard-args').html('' + props.name + '' + props.args) : $('#formula-wizard-args').addClass('hidden');
                props.desc ? $('#formula-wizard-desc').text(props.desc) : $('#formula-wizard-desc').addClass('hidden');
                props.name ? $('#formula-wizard-name').html('' + this.textFunction + ': ' + props.name) : $('#formula-wizard-name').addClass('hidden');

                this.$window.find('#formula-wizard-help').on('click', function (e) {
                    // me.close();
                    SSE.getController('LeftMenu').getView('LeftMenu').showMenu('file:help', 'Functions\/' + me.funcprops.origin.toLocaleLowerCase() + '.htm');
                })
            }
            var height = this.panelDesc.outerHeight();
            height = this.$window.find('.box').height() - height - 23;// #formula-wizard-name height
            this.panelArgs.height(height);
            height = parseInt((height-8)/30) * 30;
            this.tableArgs.height(height);
            this.scrollerY = new Common.UI.Scroller({
                el: this.tableArgs,
                minScrollbarLength  : 20,
                alwaysVisibleY: true
            });

            if (this.props) {
                // fill arguments
                var props = this.props;
                var result = props.asc_getFormulaResult();
                $('#formula-wizard-value').html('' + this.textValue + ': ' + ((result!==undefined && result!==null)? result : ''));

                var tbl = this.$window.find('#formula-wizard-tbl-args');
                var argtpl = '

	' +
                            '

	' +
                            '

';
                var argmin = props.asc_getArgumentMin(),
                    argmax = props.asc_getArgumentMax(),
                    argres = props.asc_getArgumentsResult(),
                    argtype = props.asc_getArgumentsType(),
                    argval = props.asc_getArgumentsValue(),
                    argcount = 0,
                    lasttype;
                for (var i=0; i<argtype.length; i++) {
                    var type = (argtype[i]==undefined) ? lasttype : argtype[i],
                        types = [];
                    if (type==undefined) break;
                    lasttype = type;

                    if (typeof type == 'object')
                        types = type;
                    else
                        types.push(type);
                    for (var j=0; j<types.length; j++) {
                        var div = $(Common.Utils.String.format(argtpl, argcount));
                        tbl.append(div);
                        var txt = new Common.UI.InputFieldBtn({
                            el: div.find('#formula-wizard-txt-arg'+argcount),
                            index: argcount,
                            validateOnChange: true,
                            validateOnBlur: false
                        }).on('changed:after', function(input, newValue, oldValue, e) {
                            if (newValue == oldValue) return;
                        }).on('changing', function(input, newValue, oldValue, e) {
                            if (newValue == oldValue) return;
                            var index = input.options.index,
                                arg = me.args[index],
                                res = me.api.asc_insertFormulaArgument(newValue, index, arg.argType);
                            arg.lblValue.text(' = '+ (res!==null && res !==undefined ? res : arg.argTypeName));
                        });
                        txt.setValue((argval[argcount]!==undefined && argval[argcount]!==null) ? argval[argcount] : '');
                        this.args.push({
                            index: argcount,
                            lblName: div.find('#formula-wizard-lbl-name-arg'+argcount),
                            lblValue: div.find('#formula-wizard-lbl-val-arg'+argcount),
                            argInput: txt,
                            argName: 'Argument ' + (argcount+1),
                            argType: types[j],
                            argTypeName: this.getArgType(types[j])
                        });
                        if (i<argmin)
                            this.args[argcount].lblName.html('' + this.args[argcount].argName + '');
                        else
                            this.args[argcount].lblName.html(this.args[argcount].argName);
                        this.args[argcount].lblValue.text(' = '+ (argres && (argres.length>argcount) && argres[argcount]!==null ? argres[argcount] : this.args[argcount].argTypeName));
                        argcount++;
                    }
                }
                this.scrollerY.update();
                this.scrollerY.scrollTop(0);
            }
        },

        getArgType: function(type) {
            var str = '';
            switch (type) {
                case Asc.c_oAscFormulaArgumentType.number:
                    str = 'number';
                    break;
                case Asc.c_oAscFormulaArgumentType.text:
                    str = 'text';
                    break;
                case Asc.c_oAscFormulaArgumentType.reference:
                    str = 'reference';
                    break;
                case Asc.c_oAscFormulaArgumentType.any:
                    str = 'any';
                    break;
                case Asc.c_oAscFormulaArgumentType.logical:
                    str = 'logical';
                    break;
            }
            return str;
        },

        getSettings: function() {
            return {};
        },

        close: function () {
            Common.UI.Window.prototype.close.call(this);
            this.api.asc_closeCellEditor(!this._preventCloseCellEditor);
        },

        textTitle: 'Function Argumens',
        textValue: 'Formula result',
        textFunction: 'Function',
        textHelp: 'Help on this function'

    }, SSE.Views.FormulaWizard || {}))
});<commit_msg>[SSE] Formula wizard: add arguments, set values<commit_after>/*
 *
 * (c) Copyright Ascensio System SIA 2010-2020
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */
/**
 *  FormulaWizard.js
 *
 *  Created by Julia Radzhabova on 17.04.20
 *  Copyright (c) 2020 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'common/main/lib/component/Window',
    'common/main/lib/component/MetricSpinner'
], function () { 'use strict';

    SSE.Views.FormulaWizard = Common.UI.Window.extend(_.extend({
        options: {
            width: 420,
            height: 460,
            header: true,
            style: 'min-width: 350px;',
            cls: 'modal-dlg',
            buttons: ['ok', 'cancel']
        },

        initialize : function(options) {
            _.extend(this.options, {
                title: this.textTitle
            }, options || {});

            this.template = [
                '
',
                    '',
                    '
',
                        '
',
                            '
',
                            '
',
                        '
',
                    '
',
                    '
',
                        'Formula result:',
                        'Argument 1:',
                        '',
                        '',
                        ''+ this.textHelp +'',
                    '
',
                '
',
                '
'
            ].join('');

            this.options.tpl = _.template(this.template)(this.options);
            this.props = this.options.props;
            this.funcprops = this.options.funcprops;
            this.api = this.options.api;
            this._noApply = false;
            this.args = [];
            this.repeatedArg = undefined;
            this.minArgCount = 1;
            this.maxArgCount = 1;

            Common.UI.Window.prototype.initialize.call(this, this.options);
        },

        render: function() {
            Common.UI.Window.prototype.render.call(this);

            var $window = this.getChild();
            $window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
            $window.find('input').on('keypress', _.bind(this.onKeyPress, this));

            this.panelArgs = $window.find('#formula-wizard-panel-args');
            this.tableArgs = $window.find('#formula-wizard-tbl-args');
            this.panelDesc = $window.find('#formula-wizard-panel-desc');
            this.lblArgDesc = $window.find('#formula-wizard-arg-desc');

            this._preventCloseCellEditor = false;

            this.afterRender();
        },

        afterRender: function() {
            this.setSettings();
        },

        _handleInput: function(state) {
            if (this.options.handler)
                this.options.handler.call(this, state, (state == 'ok') ? this.getSettings() : undefined);
            this._preventCloseCellEditor = (state == 'ok');
            this.close();
        },

        onBtnClick: function(event) {
            this._handleInput(event.currentTarget.attributes['result'].value);
        },

        onKeyPress: function(event) {
            if (event.keyCode == Common.UI.Keys.RETURN) {
                this._handleInput('ok');
            }
        },

        setSettings: function () {
            var me = this;
            if (this.funcprops) {
                var props = this.funcprops;
                props.args ? $('#formula-wizard-args').html('' + props.name + '' + props.args) : $('#formula-wizard-args').addClass('hidden');
                props.desc ? $('#formula-wizard-desc').text(props.desc) : $('#formula-wizard-desc').addClass('hidden');
                props.name ? $('#formula-wizard-name').html('' + this.textFunction + ': ' + props.name) : $('#formula-wizard-name').addClass('hidden');

                this.$window.find('#formula-wizard-help').on('click', function (e) {
                    // me.close();
                    SSE.getController('LeftMenu').getView('LeftMenu').showMenu('file:help', 'Functions\/' + me.funcprops.origin.toLocaleLowerCase() + '.htm');
                })
            }
            var height = this.panelDesc.outerHeight();
            height = this.$window.find('.box').height() - height - 33;// #formula-wizard-name height
            this.panelArgs.height(height);
            height = parseInt((height-8)/30) * 30;
            this.tableArgs.parent().height(height);
            this.scrollerY = new Common.UI.Scroller({
                el: this.tableArgs.parent(),
                minScrollbarLength  : 20,
                alwaysVisibleY: true
            });

            if (this.props) {
                // fill arguments
                var props = this.props;
                this.minArgCount = props.asc_getArgumentMin();
                this.maxArgCount = props.asc_getArgumentMax();

                var result = props.asc_getFormulaResult();
                $('#formula-wizard-value').html('' + this.textValue + ': ' + ((result!==undefined && result!==null)? result : ''));

                var argres = props.asc_getArgumentsResult(),
                    argtype = props.asc_getArgumentsType(),
                    argval = props.asc_getArgumentsValue();

                if (argtype) {
                    for (var i=0; i<argtype.length; i++) {
                        var type = argtype[i],
                            types = [];

                        if (typeof type == 'object') {
                            this.repeatedArg = type;
                            types = type;
                        } else
                            types.push(type);
                        this.fillArgs(types, argval, argres);
                    }
                    if (argval && this.args.length<argval.length && this.repeatedArg) { // add repeated
                        while (this.args.length<argval.length) {
                            this.fillArgs(this.repeatedArg, argval, argres);
                        }
                    }
                    this.scrollerY.update();
                    this.scrollerY.scrollTop(0);
                }
            }
            if (this.args.length<1) {
                this.panelArgs.text('This function has no arguments');
                this.lblArgDesc.addClass('hidden');
            } else {
                if (this.args.length==1 && this.repeatedArg && this.repeatedArg.length<this.maxArgCount) {// add new repeated arguments
                    this.fillArgs(this.repeatedArg);
                    this.scrollerY.update();
                }

                _.delay(function(){
                    me._noApply = true;
                    me.args[0].argInput.cmpEl.find('input').focus();
                    me._noApply = false;
                },100);
            }
        },

        fillArgs: function (types, argval, argres) {
            var argcount = this.args.length;
            for (var j=0; j<types.length; j++)
                this.setControls(argcount, types[j], argval ? argval[argcount] : undefined, argres ? argres[argcount] : undefined);
        },

        setControls: function(argcount, argtype, argval, argres) {
            var me = this,
                argtpl = '

	' +
                        '

	' +
                        '

',
                div = $(Common.Utils.String.format(argtpl, argcount));
            this.tableArgs.append(div);

            var txt = new Common.UI.InputFieldBtn({
                el: div.find('#formula-wizard-txt-arg'+argcount),
                index: argcount,
                validateOnChange: true,
                validateOnBlur: false
            }).on('changed:after', function(input, newValue, oldValue, e) {
                if (newValue == oldValue) return;
            }).on('changing', function(input, newValue, oldValue, e) {
                if (newValue == oldValue) return;
                var index = input.options.index,
                    arg = me.args[index],
                    res = me.api.asc_insertFormulaArgument(newValue, index, arg.argType);
                arg.lblValue.text(' = '+ (res!==null && res !==undefined ? res : arg.argTypeName));
            });
            txt.setValue((argval!==undefined && argval!==null) ? argval : '');
            txt._input.on('focus', _.bind(this.onSelectArgument, this, txt));
            txt.on('button:click', _.bind(this.onSelectData, this));

            me.args.push({
                index: argcount,
                lblName: div.find('#formula-wizard-lbl-name-arg'+argcount),
                lblValue: div.find('#formula-wizard-lbl-val-arg'+argcount),
                argInput: txt,
                argName: 'Argument ' + (argcount+1),
                argDesc: 'some argument description',
                argType: argtype,
                argTypeName: me.getArgType(argtype)
            });
            if (argcount<me.minArgCount)
                me.args[argcount].lblName.html('' + me.args[argcount].argName + '');
            else
                me.args[argcount].lblName.html(me.args[argcount].argName);
            me.args[argcount].lblValue.text(' = '+ ( argres!==null && argres!==undefined ? argres : me.args[argcount].argTypeName));
        },

        getArgType: function(type) {
            var str = '';
            switch (type) {
                case Asc.c_oAscFormulaArgumentType.number:
                    str = 'number';
                    break;
                case Asc.c_oAscFormulaArgumentType.text:
                    str = 'text';
                    break;
                case Asc.c_oAscFormulaArgumentType.reference:
                    str = 'reference';
                    break;
                case Asc.c_oAscFormulaArgumentType.any:
                    str = 'any';
                    break;
                case Asc.c_oAscFormulaArgumentType.logical:
                    str = 'logical';
                    break;
            }
            return str;
        },

        onSelectArgument: function(input) {
            var index = input.options.index,
                arg = this.args[index];
            arg.argDesc ? this.lblArgDesc.html('' + arg.argName + ': ' + arg.argDesc) : this.lblArgDesc.addClass('hidden');
            if (!this._noApply && index==this.args.length-1 && this.repeatedArg && index+this.repeatedArg.length<this.maxArgCount) {// add new repeated arguments
                this.fillArgs(this.repeatedArg);
                this.scrollerY.update();
            }
        },

        onSelectData: function(input) {
            this.onSelectArgument(input);

            var me = this;
            if (me.api) {
                var handlerDlg = function(dlg, result) {
                    if (result == 'ok') {
                        input.setValue(dlg.getSettings());
                    }
                };

                var win = new SSE.Views.CellRangeDialog({
                    handler: handlerDlg
                }).on('close', function() {
                    me.show();
                });

                var xy = me.$window.offset();
                me.hide();
                win.show(xy.left + 65, xy.top + 77);
                win.setSettings({
                    api     : me.api,
                    range   : !_.isEmpty(input.getValue()) ? input.getValue() : '',
                    type    : Asc.c_oAscSelectionDialogType.Chart
                });
            }
        },

        getSettings: function() {
            return {};
        },

        close: function () {
            Common.UI.Window.prototype.close.call(this);
            this.api.asc_closeCellEditor(!this._preventCloseCellEditor);
        },

        textTitle: 'Function Argumens',
        textValue: 'Formula result',
        textFunction: 'Function',
        textHelp: 'Help on this function'

    }, SSE.Views.FormulaWizard || {}))
});<|endoftext|>