JS-WIDGET

Developing script.js

Let's take a look at the general structure of script.js:

This part of the widget consists of the main required parts, which we will consider. Also, script.js can contain additional functions. Let's analyze the initial frame of this file.

The entire widget is represented as an object. When the system loads widgets, it extends the existing Widget system object with the functionality described in script.js. In this way, the CustomWidget object inherits properties and methods that are useful for work and are parsed further. The object has callback functions that are called under certain conditions. These functions are listed in the table after the sample code script.js.

General view of script.js
  1. define(['jquery'], function($){
  2.   var CustomWidget = function () {
  3.         var self = this, // to access an object from methods
  4.         system = self.system(), // This method returns an object with system variables.
  5.         langs = self.langs;  // Localization object with data from the localization file (i18n folder)
  6.        
  7.         this.callbacks = {
  8.               settings: function(){
  9.               },
  10.               init: function(){      
  11.                     return true;
  12.               },
  13.               bind_actions: function(){        
  14.                     return true;
  15.               },
  16.               render: function(){      
  17.                     return true;
  18.               },            
  19.               dpSettings: function(){              
  20.               },
  21.               advancedSettings: function() {
  22.               },
  23.               destroy: function(){              
  24.               },    
  25.               contacts: { selected: function() {                  
  26.                     }
  27.               },
  28.               leads: { selected: function() {                  
  29.                     }
  30.               },
  31.               onSave: function(){        
  32.               }
  33.         };
  34.         return this;
  35.     };
  36.   return CustomWidget;
  37. });
Callback functions, callbacks object
FunctionDescription
render: When assembling the widget, callbacks.render is first called. This method usually describes the actions for displaying the widget. The widget will be displayed alone only in the settings menu, for displaying the widget in other areas, for example in the right column, you need to use special methods in this function , for example, the methods of the render () and / or render_template () object, which are parsed further. It is necessary that callbacks.render returns true. This is important, because without this, the callbacks.init and callbacks.bind_actions methods will not start.
init: Runs immediately after callbacks.render at the same time as callbacks.bind_actions. The init () method is usually used to collect necessary information and other actions, for example, communication with a third-party server and API authorization if the widget is used to send or request information to a third-party server. In the simplest In this case, it can, for example, determine the current location where the user is located. callbacks.init must return true for further work.
bind_actions: The callbacks.bind_actions method is used to hover events to actions taken by the user, for example, clicking a user on a button. callbacks.bind_actions must return true.
settings: The callbacks.settings method is called when the widget icon is clicked in the settings area. It can be used to add a modal window to the page, which is discussed in more detail below.
The public widget should not hide / affect the rating and reviews of the widget.
dpSettings: The callbacks.dpSettings method is similar to callbacks.settings, but it is called in the scope of the digital_pipeline (more details Digital pipeline)
advancedSettings: The callbacks.advancedSettings method is called on the widget's advanced settings page. For the functioning of this callback, you must specify the connection area of the widget advanced_settings.
onSave: callbacks.onSave is called when the user clicks the "Save" button in the widget's settings. You can use to send the data entered into the form and change the status of the widget.
leads:selected This function is called when you select items from the list of leads, using the checkbox, and then click on the widget name in the extension menu. Used when you need to take any action with the selected objects. Examples are discussed below.
contacts:selected This function is called when you select items from the list of leads, using the checkbox, and then click on the widget name in the extension menu. Used when you need to take any action with the selected objects. Examples are discussed below.
destroy: This function is called when the widget is disabled through its settings menu. For example, you need to remove all widget elements from the DOM, if it was disabled, or take any further action.
Example of the widget's JS-code:

The following example demonstrates the use of an object callback functions with additional functions, as well as the use of some functions of the object widget. All these functions are discussed in the examples below. Let's just look at this code, and for details, refer to the description of the functions of the widget object.

This widget will select from the contact list the marked contacts and transfer the phones and e-mail addresses to a third-party server.

The functions used in this example are discussed in more detail below. First of all, you should pay attention to the object callbacks.

  1. define(['jquery'], function ($) {
  2.   var CustomWidget = function () {
  3.     var self = this,
  4.       system = self.system;
  5.  
  6.     this.get_ccard_info = function () // Collecting information from a contact card
  7.     {
  8.       if (self.system().area == 'ccard') {
  9.         var phones = $('.card-cf-table-main-entity .phone_wrapper input[type=text]:visible'),
  10.           emails = $('.card-cf-table-main-entity .email_wrapper input[type=text]:visible'),
  11.           name = $('.card-top-name input').val(),
  12.           data = [],
  13.           c_phones = [], c_emails = [];
  14.         data.name = name;
  15.         for (var i = 0; i < phones.length; i++) {
  16.           if ($(phones[i]).val().length > 0) {
  17.             c_phones[i] = $(phones[i]).val();
  18.           }
  19.         }
  20.         data['phones'] = c_phones;
  21.         for (var i = 0; i < emails.length; i++) {
  22.           if ($(emails[i]).val().length > 0) {
  23.             c_emails[i] = $(emails[i]).val();
  24.           }
  25.         }
  26.         data['emails'] = c_emails;
  27.         console.log(data)
  28.         return data;
  29.       }
  30.       else {
  31.         return false;
  32.       }
  33.     };
  34.  
  35.     this.sendInfo = function (person_name, settings) { // Sending collected information
  36.       self.crm_post(
  37.         'http://example.com/index.php',
  38.         {
  39.           // Sending POST data
  40.           name: person_name['name'],
  41.           phones: person_name['phones'],
  42.           emails: person_name['emails']
  43.         },
  44.         function (msg) {
  45.         },
  46.         'json'
  47.       );
  48.     };
  49.     this.callbacks = {
  50.       settings: function () {
  51.       },
  52.       dpSettings: function () {
  53.       },
  54.       init: function () {
  55.         if (self.system().area == 'ccard') {
  56.           self.contacts = self.get_ccard_info();
  57.         }
  58.         return true;
  59.       },
  60.       bind_actions: function () {
  61.         if (self.system().area == 'ccard' || 'clist') {
  62.           $('.ac-form-button').on('click', function () {
  63.             self.sendInfo(self.contacts);
  64.           });
  65.         }
  66.         return true;
  67.       },
  68.       render: function () {
  69.         var lang = self.i18n('userLang');
  70.         w_code = self.get_settings().widget_code; // in this case w_code='new-widget'
  71.         if (typeof(AMOCRM.data.current_card) != 'undefined') {
  72.           if (AMOCRM.data.current_card.id == 0) {
  73.             return false;
  74.           } // do not render contacts/add || leads/add
  75.         }
  76.         self.render_template({
  77.           caption: {
  78.             class_name: 'js-ac-caption',
  79.             html: ''
  80.           },
  81.           body: '',
  82.           render: '\
  83.                  <div class="ac-form">\
  84.              <div id="js-ac-sub-lists-container">\
  85.              </div>\
  86.                  <div id="js-ac-sub-subs-container">\
  87.                  </div>\
  88.                  <div class="ac-form-button ac_sub">SEND</div>\
  89.                  </div>\
  90.              <div class="ac-already-subs"></div>\
  91.                  // for public widgets
  92.                  '<link type="text/css" rel="stylesheet" href="/widgets/' + w_code + '/style.css" >'
  93.                  // for private widgets
  94.                  '<link type="text/css" rel="stylesheet" href="/upl/widget/' + w_code + '/style.css" >'
  95.        });
  96.        return true;
  97.      },
  98.      contacts: {
  99.        selected: function () {    // Here is the behavior for multi-select contacts and click on the name of the widget
  100.          var c_data = self.list_selected().selected;
  101.  
  102.          $('#js-sub-lists-container').children().remove(); // The container is cleaned then the elements are collected in the container,
  103. selected in
  104. list.container - div block of widget, displayed in the right column.
  105.          var names = [], // Array of names
  106.            length = c_data.length; // Number of selected id (counting starts from 0)
  107.          for (var i = 0; i < length; i++) {
  108.            names[i] = {
  109.              emails: c_data[i].emails,
  110.              phones: c_data[i].phones
  111.            };
  112.          }
  113.          console.log(names);
  114.          for (var i = 0; i < length; i++) {
  115.            $('#js-ac-sub-lists-container').append('<p>Email:' + names[i].emails + ' Phone:' + names[i].phones + '</p>');
  116.          }
  117.          $(self.contacts).remove(); // clear the variable
  118.          self.contacts = names;
  119.        }
  120.      },
  121.      leads: {
  122.        selected: function () {
  123.  
  124.        }
  125.      },
  126.      onSave: function () {
  127.  
  128.        return true;
  129.      }
  130.    };
  131.    return this;
  132.  };
  133.  return CustomWidget;
  134. });

The contents of the file new_widget.css, which can be in the folder with the widget

  1. .card-widgets__widget-new_widget .card-widgets__widget__body {
  2.     padding: 0 10px 0px;
  3.     padding-bottom: 5px;
  4.     background-color: grey;
  5. }
  6. .ac-form {
  7.     padding: 5px 15px 15px;
  8.     margin-bottom: 10px;
  9.     background: #fff;
  10. }
  11. .js-ac-caption  {
  12.     display: block;
  13.     margin: auto;
  14.     background-color : grey;
  15. }
  16.  
  17. .lists_amo_ac ul li span {
  18.     color: #81868f;
  19. }
  20. .ac-form-button{
  21.     padding: 5px 0;
  22.     background: #fafafb;
  23.     text-align: center;
  24.     font-weight: bold;
  25.     text-transform: uppercase;
  26.     border: 1px solid rgba(0, 0, 0, 0.09);
  27.     -webkit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.15);
  28.     box-shadow: 0 1px 0 0 rgba(0,0,0,0.15);
  29.     -webkit-border-radius: 2px;
  30.     border-radius: 2px;
  31.     font-size: 13px;
  32.     cursor: pointer;
  33. }
  34. .ac-form-button:active{
  35.      background: grey;        
  36. }
  37. .ac-already-subs {
  38.     position: absolute;
  39.     width: 245px;
  40.     bottom: 10px;
  41.     right: 15px;
  42.     cursor: pointer;
  43.     color: #f37575;
  44.     background: #fff;
  45. }
  46. #js-ac-sub-lists-container, #js-ac-sub-subs-container {
  47.     min-height: 38px;
  48. }

Widget object methods

The render () method

The render () method is used to work with template temigers twig.js, which is easy to use, you can see the documentation here.

The method is wrapper for twig.js and takes as parameters parameters for the template (data) and data for rendering this template (params). render (data, params). The method returns a rendered template. result = twig (data) .render (params).

Let's take a simple example
  1. var params = [  
  2.           {name:'name1',
  3.            id: 'id1'},
  4.           {name:'name2',
  5.            id: 'id2'},
  6.           {name:'name3',
  7.            id: 'id3'}
  8.           ]; // array of data sent for the template
  9.          
  10. var template = '<div><ul>'+
  11.                     '{% for person in names %}'+
  12.                     '<li>Name : {{ person.name }}, id: {{ person.id }}</li>'+
  13.                     '{% endfor %}'+
  14.                     '</ul></div>';
  15.  
  16.     console.log(self.render({data : template},// submit template
  17.               {names: params}));

As a result, we get the markup:

  • Name: name1, id: id1

  • Name: name2, id: id2

  • Name: name3, id: id3

You can pass a function to one of the templates of our system, for this, in the transmitted data object you need to specify a reference to the template: ref: '/tmpl/controls/#TEMPLATE_NAME#.twig. For example, to create a drop-down list, use the existing template:

  1. m_data = [  
  2.           {option:'option1',
  3.            id: 'id1'},
  4.           {option:'option2',
  5.            id: 'id2'},
  6.           {option:'option3',
  7.            id: 'id3'}
  8.           ]; // array of data sent for the template
  9.              
  10.           var data = self.render(
  11.           {ref: '/tmpl/controls/select.twig'},// the data object in this case contains only a reference to the template
  12.            {
  13.             items: m_data,      // Data
  14.             class_name:'subs_w',  // class specification
  15.             id: w_code +'_list'   // id specification
  16.             });

To look at the markup data, you need to add data to the DOM. The markup of the drop-down list is made in the style of our system.

To get acquainted with the full list of templates, you can follow the link. To use other system templates, you need to change the ref parameter, the general view: ref: '/tmpl/controls/#TEMPLATE_NAME#.twig'

  • textarea

  • suggest

  • select

  • radio

  • multiselect

  • date_field

  • checkbox

  • checkboxes_dropdown

  • file

  • button

  • cancel_button

  • delete_button

  • input

The render () method can transfer not only the system's existing template references, but also links to its own templates. To do this, you pass a data object with a number of parameters. It is necessary to create a folder templates in the folder of our widget and put template template.twig in it. Consider an example

  1. var params = {}; // empty data
  2. var callback = function (template){ // callback function, called if the template is loaded, it is passed an object
  3. template.
  4. var markup = template.render(params); //
  5.  /*
  6.  * then the code to add markup to the DOM
  7.  */
  8. };
  9.     var s = self.render({
  10.                 href:'templates/template.twig', // way to template
  11.                 base_path: self.params.path; // The base way to the directory with the widget
  12.                 load: callback // callback function will only occur if the template exists and is loaded
  13.             }, params); // parameters for the template

If the template exists at the link address, then the passed callback function is called, and the template object that contains the render method is passed to it, render parameters are rendered for rendering. In this example, the callback function call will occur if the template exists in the folder.

Example function for loading templates from the folder templates

For ease of reference, create a function. In it we will pass the parameters: template - the name of the template that lies in the folder with the widget in the template folder, params is the parameter object for the template, callbacks is the callback function that will be called after the template is loaded, in this case we will add the template to the modal window. For a modal window object, you can read in the JS section methods and objects for working with amoCRM.

  1. self.getTemplate = function (template, params, callback) {
  2.             params = (typeof params == 'object')?params:{};
  3.             template = template || '';
  4.  
  5.             return self.render({
  6.                 href:'/templates/' + template + '.twig',
  7.                 base_path:self.params.path, // the widget will return to the object /widgets/#WIDGET_NAME#
  8.                 load: callback // call a callback function
  9.             }, params); // parameters for the template
  10.         }
  11.  
  12. settings: function(){
  13.           self.getTemplate(  // call the function
  14.               'login_block', // specify the name of the template that we have in the folder with the widget in the folder templates
  15.               {}, /* empty data for the template, because we first ask for a template, if it exists, then the callback function
  16. call already
  17. function to add data to the template, see below */
  18.               function(template) {
  19.                       template.render({
  20.                           widget_code:self.params.widget_code,// parameters for the template.
  21.                           lang:self.i18n('settings')}));
  22.               }};
The render_template () method

The render_template () method wraps the markup or template passed to it into a standard widget (markup) and places the resulting markup in the right column of widgets

You can pass this markup to a given html function or a template with the data to render, as well as in the case of the render () method.

The function supplements the markup of its widget stored in the template_element variable passed to it.

  1. /*
  2.    * html_data stores the markup that needs to be placed in the right column of widgets.
  3.  */  
  4.  var html_data ='<div class="nw_form">'+
  5.        '<div id="w_logo">'+
  6.        '<img src="/widgets/new_widget/images/logo.png" id="firstwidget_image"></img>'+
  7.        '</div>'+
  8.        '<div id="js-sub-lists-container">'+
  9.        '</div>'+
  10.            '<div id="js-sub-subs-container">'+
  11.            '</div>'+
  12.            '<div class="nw-form-button">BUTTON</div></div>'+
  13.        '<div class="already-subs"></div>';
  14. self.render_template(
  15.       {
  16.           caption:{
  17.                   class_name:'new_widget', // class name for the markup wrapper
  18.                   },
  19.           body: html_data,// markup
  20.           render : '' // template is not sent
  21.        }
  22.       );

The simplest example was shown without using a template, but the render_template () method can also take a template and data for a template as parameters. You can also pass a link to a template, similar to the render () method.

  1. /*
  2. * Here, the template and data for the template are passed as parameters.
  3. */
  4.    var render_data ='<div class="nw_form">'+
  5.   '<div id="w_logo">'+
  6.   '<img src="/widgets/{{w_code}}/images/logo.png" id="firstwidget_image"></img>'+
  7.   '</div>'+
  8.   '<div id="js-sub-lists-container">'+
  9.   '</div>'+
  10.       '<div id="js-sub-subs-container">'+
  11.       '</div>'+
  12.       '<div class="nw-form-button">{{b_name}}</div></div>'+
  13.   '<div class="already-subs"></div>';
  14.  
  15. self.render_template(
  16.      {
  17.        caption:{
  18.                  class_name:'new_widget'
  19.                  },
  20.        body:'',
  21.       render : render_data
  22.      },
  23.      {
  24.      name:"widget_name",
  25.      w_code:self.get_settings().widget_code,
  26.      b_name:"BUTTON" // in this case it's better to pass a reference to lang via self.i18n ()
  27.      }
  28.     );

Get in the right column of the widget, created from the template.

The widget object has a number of useful functions that can be called to solve different tasks.

Description and examples are given below.

Function set_lang ()

The set_lang () function allows you to change the default settings for files from the i18n folder

The current lang object is stored in the langs variable of the widget object

  1. langs = self.langs; // Calling the current object
  2. langs.settings.apiurl = 'apiurl_new' // change the name of the field
  3. self.set_lang(langs);       // Change the current object to an object with a changed field
  4. console.log(self.langs); // Output to the console to verify that the name has changed
Function set_settings ()

The set_settings () function allows you to add properties to the widget.

  1. self.set_settings({par1:"text"}); //Setting is created with the name par1 and value text
  2. self.get_settings();// In response you will get an array with an already created property
The function list_selected ()

The function list_selected () returns the contacts / leads highlighted by the tick from the contact / lead table as an array of objects: count_selected and selected. One of the selected objects contains an array of ticked objects with the properties emails, id, phones, type.

  1. console.log(self.list_selected().selected); // Returns two objects, choose the object selected
  2.     // Result:
  3.      /*0: Object
  4.        emails: Array[1]
  5.        id: #id#
  6.        phones: Array[1]
  7.        type: "contact" */
WidgetsOverlay () function

The widgetsOverlay () (true / false) function enables or disables the overlay that appears when the widget is called from the contact list or leads.

  1. // Example:
  2.     self.widgetsOverlay(true);
The function add_action ()

When the user works in the contacts and companies list area, you can provide a call to a function by clicking on the phone number or e-mail address of the contact.

The functions add_action () are passed parameters (type, action), where type is "e-mail" or "phone", action is a function that will be called when you click on the phone number or e-mail address.

  1. self.add_action("phone",function(){
  2.     /*
  3.     * code of interaction with the telephony widget
  4.     */
  5. });
The function add_source ()

Allows you to specify a new source that will be displayed in the control at the bottom of the lead card feed, buyer, contact or company.

At the moment, you can specify only one type of source - SMS

The parameters add_source () are passed parameters (source_type, handler), where source_type is "sms", handler is a function that will be called when you click the "send" button.

The "handler" function must always return a Promise object

  1. // example
  2.  
  3. self.add_source("sms", function(params) {
  4.   /*
  5.    params - this is the object in which there will be the necessary parameters for sending SMS
  6.  
  7.    {
  8.      "phone": 75555555555,   // recipient's phone number
  9.      "message": "sms text",  // message to send
  10.      "contact_id": 12345     // contact ID to which the phone number is attached
  11.    }
  12.   */
  13.  
  14.    return new Promise(function (resolve, reject) {
  15.      // Here will describe the logic for sending SMS
  16.  
  17.      $.ajax({
  18.        url: '/widgets/' + self.system().subdomain + '/loader/' + self.get_settings().widget_code +'/send_sms',
  19.        method: 'POST',
  20.        data: params,
  21.        success: function () {
  22.          // if successful, a note like 'sms' will be created
  23.          resolve();
  24.        },
  25.        error: function () {
  26.          reject();
  27.        }
  28.      });
  29.    });
  30. });
The function get_pipeline_id ()

This function allows you to find out which pipeline the widget is connected to as a source. Available if there is a lead_sources area in manifest.json

  1. self.get_pipeline_id()
  2.                 .then(function (pipeline_id) {
  3.                     // From here you can initiate a request using the pipeline ID
  4.                 })
  5.                 .catch(function () {
  6.                     // handling of the case when the widget is not attached to the pipeline
  7.                 });
Function set_status ()

A widget can have one of three statuses. The status of the widget is displayed in the settings area, on the widget's icon. In the event that the widget uses the data entered by the user for the third-party service API, and this data is entered incorrectly, then this function can be used to display the error status.

Statuses are available for install (widget not active) and installed (widget is active), error (widget in error state).

  1. // Example:
  2. self.set_status('error');
The method crm_post (url, data, callback, type, error)

The method is used to send a request to your remote server through the amoCRM proxy server. Its use is necessary, because when working with amoCRM, the user works on a secure SSL protocol and the browser can block cross-domain requests. The best solution is to have a signed SSL certificate on the side of the internal system and work on HTTPS. The function is similar to jQuery post (), but the possibility of catching errors is added (the fifth argument is error), see the documentation (http://docs.jquery.com/Post)

Description of the method

ParameterTypeDescription
url String Link to the script processing data
data
optional
Javascript object Pairs "key-value" that will be sent to the server
callback
optional
Function A function that is called after each successful execution (in the case of type = text or html, it is always executed).
type
optional
String The type of data that the function returns: "xml", "html", "script", "json", "jsonp", or "text".
error
optional
Function A function that is called after each not successful execution (does not extend to type = text or html).
Example of request
  1. self.crm_post (
  2.  
  3.           'http://www.test.com/file.php',
  4.         {
  5.           // We pass the POST data using the Javascript object model
  6.           name: 'myname',
  7.           login:'mylogin',
  8.           password: 'mypassword'
  9.         },
  10.         function(msg)
  11.         {
  12.           alert('It's all OK');
  13.        },
  14.        'text',
  15.        function()
  16.        {
  17.          alert ('Error');
  18.        }
  19.      )
Method self.get_settings

This method is necessary, in order to obtain the data that the user entered when the widget is connected. Data is returned as a javascript object

Example of the answer
  1. {
  2.   login: "ZABRTEST",
  3.   password: "test",
  4.   maybe: "Y"
  5. }
The method self.system ()

This method is necessary in order to obtain system data. Data is returned as a javascript object

ParameterDescription
area The area on which the widget is currently playing
amouser_id User ID
amouser Mail of a user
amohash The key for API authorization
Example of the answer
  1. {
  2.    area: "ccard",
  3.    amouser_id: "103586",
  4.    amouser: "testuser@amocrm.com",
  5.    amohash: "d053abd66063225fa8b763afz6496da8"
  6. }
The method self.i18n (objname)

This method allows you to get an object from the language files in which there will be messages on the language locales used by the user In objname, the name of the object to be extracted is passed

For example, call the function self.i18n ('userLang')

Example of the answer
  1. {
  2.   firstWidgetText: "Click the button to send the data to a third-party server:",
  3.   textIntoTheButton: "Send data",
  4.   responseMessage: "Server Response :",
  5.   responseError: "Error"
  6. }

Thus, having a simple tool for interacting with DOM and performing cross-domain queries, you can, besides creating simple text widgets, change the design of page elements, create your own information blocks based on external data, or vice versa, send data to external services, all of this works immediately for all users of your account.