script.js

The entire widget is represented as an object. When the system loads widgets, it extends the existing system Widget object with the functionality described in script.js. Thus, the CustomWidget object inherits properties and methods that will be useful to work with and will be discussed further.

In this article we will see

The JS part of the widget consists of main required parts and additional functions. The object has callback functions that are called under certain conditions. Let’s analyze the initial frame of this file.

General view of script.js

define(['jquery'], function($){
  var CustomWidget  function () {
        var self = this, // to access an object from methods
        system = self.system(), // This method returns an object with system variables.
        langs = self.langs;  // Localization object with data from the localization file (i18n folder)
       
        this.callbacks = {
              settings: function(){
              },
              init: function(){      
                    return true;
              },
              bind_actions: function(){        
                    return true;
              },
              render: function(){      
                    return true;
              },            
              dpSettings: function(){              
              },
              advancedSettings: function() {
              },
              destroy: function(){              
              },    
              contacts: { selected: function() {                  
                    }
              },
              leads: { selected: function() {                  
                    }
              },
              onSave: function(){        
              }
        };
        return this;
    };
  return CustomWidget;
});

Callback functions, callbacks object

Function Description
render: When installing the widget, callbacks.render is first called. This method usually describes the actions for displaying the widget. The widget will be displayed by default only in the settings menu. For displaying the widget in other areas, for example, in the widgets panel on the right, you need to use special methods in this function, for example, the object methods render() and/or render_template(), which are parsed further.
It is necessary that callbacks.render returns true. This is important because, without this, the methods callbacks.init and callbacks.bind_actions will not start.
init: Runs immediately after callbacks.render at the same time as callbacks.bind_actions. The init() method is typically used to collect the necessary information and other actions, such as communicating 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 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 attach events to actions taken by the user, such as when the user clicks on a button. callbacks.bind_actions must return true.
settings: The callbacks.settings method is called when the widget’s icon in the settings area is clicked. It can be used to add a modal window to the page.
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 this callback to function, it is necessary to specify the widget connection area advanced_settings.
onSave: callbacks.onSave is called when the user clicks on the “Set/Save” button in the widget settings. It can be used to submit form data and change widget status. This method also fires when the widget is disabled. OnSave fires first, then destroy.
leads:selected This function is called when selecting items from the leads list, using the checkbox, and then click on the widget name in the additional menu that appears when choosing items from a list. Used when you need to take any action with the selected objects. Examples are discussed below.
contacts:selected This function is called when selecting items from the contacts list, using the checkbox, and then clicking on the widget name in the additional menu that appears when choosing items from a list. Used when you need to take some action with the selected objects. Examples are discussed below.
todo:selected This function is called when selecting items in the tasks list, using the checkbox, and then clicking on the widget name in the additional menu that appears when choosing items from a list. Used when you need to take some 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. Also, this method is triggered when switching between widget display areas.
onSource: This function defines the logic of the source and is called if some source has been used. For example, if the user sent SMS.
onSalesbotDesignerSave: This function defines the logic of the widget action in Salesbot and is called if the widget was added in the Salesbot constructor when saving.
onAddAsSource: This function is called when adding a widget as a source in the digital pipeline settings.

Example of the widget’s JS-code

The following example demonstrates how to use the callback object with additional functions, as well as the use of some functions of the widget object. All of these functions are covered in the examples below. We recommend that you simply review the code and refer to the description of the functions of the widget object for details.

This widget will select marked contacts from the list of contacts and transfer phone numbers 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 callbacks object.

define(['jquery'], function ($) {
  var CustomWidget = function () {
    var self = this,
        system = self.system;

    this.get_ccard_info = function () // Collecting information from a contact card
    {
      if (self.system().area == 'ccard') { // In the contact card area
        var phones = $('.card-cf-table-main-entity .phone_wrapper input[type=text]:visible'), // Identify the phone numbers
          emails = $('.card-cf-table-main-entity .email_wrapper input[type=text]:visible'), // Identify the email addresses
          name = $('.card-top-name input').val(), 
          data = [],
          c_phones = [], c_emails = [];
        data.name = name;
        for (var i = 0; i < phones.length; i++) { if ($(phones[i]).val().length > 0) {
            c_phones[i] = $(phones[i]).val();
          }
        }
        data['phones'] = c_phones;
        for (var i = 0; i < emails.length; i++) { if ($(emails[i]).val().length > 0) {
            c_emails[i] = $(emails[i]).val();
          }
        }
        data['emails'] = c_emails;
        console.log(data)
        return data;
      }
      else {
        return false;
      }
    };

    this.sendInfo = function (person_name, settings) // Sending the collected information
     {
      self.crm_post(
        'http://example.com/index.php',
        {
          // Sending POST data
          name: person_name['name'],
          phones: person_name['phones'],
          emails: person_name['emails']
        },
        function (msg) {
        },
        'json'
      );
    };
    this.callbacks = {
      settings: function () {
      },
      dpSettings: function () {
      },
      init: function () {
        if (self.system().area == 'ccard') {
          self.contacts = self.get_ccard_info();
        }
        return true;
      },
      bind_actions: function () {
        if (self.system().area == 'ccard' || 'clist') {
          $('.ac-form-button').on('click', function () {
            self.sendInfo(self.contacts);
          });
        }
        return true;
      },
      render: function () {
        var lang = self.i18n('userLang');
        w_code = self.get_settings().widget_code;
        if (typeof(APP.data.current_card) != 'undefined') {
          if (APP.data.current_card.id == 0) {
            return false;
          } // do not render contacts/add || leads/add
        }
        self.render_template({
          caption: {
            class_name: 'js-ac-caption',
            html: ''
          },
          body: '',
          render: '\<div class="ac-form">\
                <div id="js-ac-sub-lists-container">\ </div>\
                <div id="js-ac-sub-subs-container">\ </div>\
                <div class="ac-form-button ac_sub">SEND</div>\
                </div>\
                <div class="ac-already-subs"></div>\
                <link type="text/css" rel="stylesheet" href="/widgets/' + w_code + '/style.css" >'
          });
          return true;
      },
      contacts: {
        selected: function () {    // Here is the behavior for multi-select contacts and click on the name of the widget
          var c_data = self.list_selected().selected;
          $('#js-sub-lists-container').children().remove(); // The container is cleaned then the elements are collected in the container, selected in list.container - div block of widget, displayed in the right column.
            var names = [], // Array of names
            length = c_data.length; // Number of selected id (counting starts from 0)
          for (var i = 0; i &lt length; i++) {
            names[i] = {
              emails: c_data[i].emails,
              phones: c_data[i].phones
            };
          }
          console.log(names);
          for (var i = 0; i &lt length; i++) {
            $('#js-ac-sub-lists-container').append('<p>Email:' + names[i].emails + ' Phone:' + names[i].phones + '</p>');
          }
          $(self.contacts).remove(); // clear the variable
          self.contacts = names;
        }
      },
      leads: {
        selected: function () {
               //This describes the behavior when multi-selecting contacts and clicking on the widget name
              var c_data = self.list_selected().selected;
              $('#js-sub-lists-container').children().remove(); //The container is cleaned, then the items selected in the list are collected into the container. The container is the widget's div block, displayed in the right column.
              var names = [], // Array of names
              length = c_data.length; //Number of selected IDs (counting starts from 0)
              for (var i = 0; i < length; i++) {
                        names[i] = {
                            emails: c_data[i].emails,
                            phones: c_data[i].phones
                        };
              }
              console.log(names);
              for (var i = 0; i < length; i++) {
                      $('#js-ac-sub-lists-container').append('<p>Email:' + names[i].emails + ' Phone:' + names[i].phones + '</p>');
              }
              $(self.contacts).remove();
              self.contacts = names;
        }
      },
      onSave: function () {

        return true;
      }
    };
    return this;
  };
  return CustomWidget;
});

Widget object methods

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

Method render()

This method is used to work with the template engine twig.js. You can read about the template engine.

The method is wrapping for twig.js and accepts template information (data) and data for rendering this template (params) as parameters. render(data, params). The method returns the rendered template

result = twig(data).render(params).

Let’s consider a simple example of the template

var params = [
  {name:'name1',
    id: 'id1'},
  {name:'name2',
    id: 'id2'},
  {name:'name3',
    id: 'id3'}
]; // array of data sent for the template

var template = '<div><ul>' +
  '{% for person in names %}' +
  '<li>Name : {{ person.name }}, id: {{ person.id }}</li>' +
  '{% endfor %}' + '</ul></div>';
console.log(self.render({data: template},{names: params}));

As a result, we get the markup:

  • Name: name1, id: id1
  • Name: name2, id: id2
  • Name: name3, id: id3

You can pass one of the templates of our system to the function, for this you need to specify a link to the template in the passed data object: ref: ‘/tmpl/controls/#TEMPLATE_NAME#.twig’, where #TEMPLATE_NAME# is one of the system templates:

  • textarea
  • suggest
  • select
  • radio
  • multiselect
  • date_field
  • checkbox
  • checkboxes_dropdown
  • file
  • button
  • cancel_button
  • delete_button
  • input

For example, to create a drop-down list, we use the template select.twig:

m_data = [  
          {option:'option1',
           id: 'id1'},
          {option:'option2',
           id: 'id2'},
          {option:'option3',
           id: 'id3'}
          ]; // array of data sent for the template
             
          var data = self.render(
          {ref: '/tmpl/controls/select.twig'},// the data object in this case contains only a reference to the template
           {
            items: m_data,      // Data
            class_name:'subs_w',  // class specification
            id: w_code +'_list'   // id specification
            });

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

You can pass to the render() method not only references to existing system templates but also references to your own templates. To do this, you need to pass a data object with a number of parameters. It’s necessary to create a templates folder in our widget folder and put the template.twig template in it.

Example on rendering a template:

var params = {}; // empty data
var callback = function (template){ // callback function, called if the template is loaded, it is passed an object template.
var markup = template.render(params); //
 /*
 * then the code to add markup to the DOM
 */
};
var s = self.render({
          href:'templates/template.twig', // way to template
          base_path: self.params.path; // The base way to the directory with the widget
          load: callback // callback function will only occur if the template exists and is loaded
      }, params); // parameters for the template
Example: Rendering an existing template

If the template exists at the link address, then the passed callback function is called, and the template object is passed to it. The template object contains the render method, with the corresponding parameters for rendering. In this example, the callback function will be called if the template exists in the folder.

For more convenient work, we will create a function, called getTemplate, pass parameters to it: parameter template is the name of the template that exists in the template folder in the widget folder, parameter params is the parameters object for the template, and parameter callbacks is the callback function that will be called after loading the template, in this case we will add the template to the modal window. You can read about the modal window object in the section JS methods and objects for working with Kommo.

this.getTemplate = function (template, params, callback) {
      params = (typeof params == 'object') ? params : {};
      template = template || '';

      return this.render({
        href: '/templates/' + template + '.twig',
        base_path: this.params.path, // the widget will return to the object /widgets/#WIDGET_NAME#
        load: callback // call a callback function
      }, params); // parameters for the template
}
settings: function () {
        self.getTemplate( // call the function
        	'login_block',  // specify the name of the template that we have in the folder with the widget in the folder templates
        	{},  /* empty data for the template, because we will first request the template, if it exists, then the function callback function will already call a function to add data to the template, see below */
        	function (template) {
               	template.render({ // parameters for the template.
            			title: self.i18n('settings').title, 
               		widget_code: self.get_settings().widget_code 
            		})
            	}
         );
}

Now in this modal window, you can add a tour by adding the tour property to manifest.json.

"tour": {
    "is_tour": true,
    "tour_images": {
      "en": [
        "/images/tour_en_1.png",
        "/images/tour_en_2.png",
        "/images/tour_en_3.png",
        "/images/tour_en_4.png"
      ],
      "es": [
        "/images/tour_es_1.png",
        "/images/tour_es_2.png",
        "/images/tour_es_3.png",
        "/images/tour_es_4.png"
      ]
    },
    "tour_description": "widget.tour_description"
  },

Method render_template()

This method wraps the markup or template passed to it in a standard widget wrapper (markup) and places the resulting markup in the right column of widgets.

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

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

/*
* html_data stores the markup that needs to be placed in the right column of widgets.
*/
var html_data ='<div id="w_logo" class="nw_form">' + '<div id="js-sub-subs-container">' + '</div>' + '<div class="nw-form-button">BUTTON</div></div>' + '<div class="already-subs"></div>';
self.render_template(
  {
    caption:{
      class_name:'new_widget', // class name for the markup wrapper
    },
    body: html_data,// markup
    render : '' // template is not sent
  }
);

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

/*
* Here, the template and data for the template are passed as parameters.
*/
var render_data ='<div class="nw_form">' +
             '<div id="w_logo">' +
                   '<img src="/widgets/{{w_code}}/images/logo.png" id="firstwidget_image"></img>' +
             '</div>' +
             '<div id="js-sub-lists-container">' + '</div>' +
             '<div id="js-sub-subs-container">' + '</div>' +
             '<div class="nw-form-button">{{b_name}}</div>
      </div>' +
       '<div class="already-subs"></div>';
self.render_template(
  {
    caption:{
      class_name:'new_widget'
    },
    body:'',
    render : render_data
  },
  {
    name:"widget_name",
    w_code:self.get_settings().widget_code,
    b_name:"BUTTON" // in this case it's better to pass a reference to lang via self.i18n ()
  }
);

We get in the widgets panel on the right column a widget created by a template.

Method crm_post (url, data, callback, type, error)

This method is used to send a request to your remote server through the Kommo proxy server. Its use is necessary because when working with Kommo, the user works using 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 over HTTPS. The function is similar to jQuery post() with the possibility of catching an error.

Description of the method

Parameter Type Description
url String Link to script processing data
data
optional
Javascript object key:value pairs to be sent to the server
callback
optional
Function A function that is called after each successful execution (in the case of passing type the value “text” or “html”, it is always executed).
type
optional
String The type of data returned by the function. One of “xml”, “html”, “script”, “json”, “jsonp”, or “text”.
error
optional
Function A function that is called after each unsuccessful execution (does not extend to type the value “text” or “html”).
Example of request
self.crm_post (
        'http://www.test.com/file.php',
        {
		name: 'myname',
        	login:'mylogin',
        	password: 'mypassword'
        } // We pass the POST data using the Javascript object model
        ,
        function(msg)
        {
        	alert('It\'s all OK');
       },
       'text',
       function(){
        	alert ('Error');
       }
     )

Method self.get_settings()

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

Example of the answer
{
  login: "ZABRTEST",
  password: "test",
  maybe: "Y"
}

Method self.system()

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

Parameter Description
area The area on which the widget is currently playing
kommouser_id User ID
kommouser Mail of a user
kommohash The key for API authorization
Example of the answer
{
   area: "ccard",
   kommouser_id: "103586",
   kommouser: "testuser@kommo.com",
   kommohash: "d053abd66063225fa8b763afz6496da8"
}

Method self.i18n (key)

This method allows you to get an object from language files, which will contain messages in the language locales used by the user. The passed parameter key is the name of the object that needs to be extracted.

For example, we call the function

self.i18n('userLang')

Example of the answer

{
  firstWidgetText: "Click the button to send the data to a third-party server:",
  textIntoTheButton: "Send data",
  responseMessage: "Server Response :",
  responseError: "Error"
}

Function set_lang()

This 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

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

Function set_settings()

This function allows you to add properties to the widget.

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

Function list_selected()

This function returns the checked contacts/leads from the contacts/leads table as an array of objects: count_selected and selected. One of the selected objects contains an array of checkboxed objects with emails, id, phones, type properties.

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

Function widgetsOverlay()

This function enables or disables the overlay that appears when the widget is called from the contacts or leads list. As an argument, the function takes the value true or false.

self.widgetsOverlay(true);

Function add_action()

When the user is working in the contacts and companies list area, it is possible to provide a call to any function when clicking on the contact’s phone number or e-mail address.

This function is passed parameters type and action, where type is “e-mail” or “phone“, action is the function that will be called when clicking on the phone number or e-mail address.

self.add_action("phone",function(){
    /*
    * code of interaction with the VoIP widget
    */
});

Function add_source()

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

This function is passed parameters source_type and handler, where source_type is “sms“, handler is the function that will be called when the “send” button is clicked.

At the moment, you can specify only one source type: sms. The handler function must always return a Promise object

self.add_source("sms", function(params) {
  /*
   params - this is the object in which there will be the necessary parameters for sending SMS
 
   {
     "phone": 75555555555,   // recipient's phone number
     "message": "sms text",  // message to send
     "contact_id": 12345     // contact ID to which the phone number is attached
   }
  */
 
   return new Promise(function (resolve, reject) {
     // Here will describe the logic for sending SMS
 
     $.ajax({
       url: '/widgets/' + self.system().subdomain + '/loader/' + self.get_settings().widget_code +'/send_sms',
       method: 'POST',
       data: params,
       success: function () {
         // if successful, a note like 'sms' will be created
         resolve();
       },
       error: function () {
         reject();
       }
     });
   });
});

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.

self.get_pipeline_id()
    .then(function (pipeline_id) {
 // From here you can initiate a request using the pipeline ID
    })
    .catch(function () {
 // handling of the case when the widget is not attached to the pipeline
    });

Function set_status()

A widget can have one of three statuses. The widget status is displayed in the settings area, on the widget icon. If the widget uses the data entered by the user for the API of a third-party service, and this data is entered incorrectly, then you can use this function to display the error status.

Available statuses are install (widget is not active) and installed (widget is active), error (widget is in error state). For example:

self.set_status('error');

It’s time to see a step-by-step example.