JS-Widget

7. Разработка script.js

Разберем общую структуру script.js:

Данная часть виджета состоит из основных обязательных частей, которые мы рассмотрим. Так же script.js может содержать дополнительные функции. Разберем начальный каркас данного файла.

Весь виджет представляется в виде объекта. Когда система загружает виджеты, она расширяет существующий системный объект Widget функционалом описанным в script.js. Таким образом объект CustomWidget наследует свойства и методы, которые будут полезны для работы и разобраны далее. Объект имеет функции обратного вызова, которые вызываются при определенных условиях. Данные функции перечислены в таблице после примера кода script.js.

Общий вид script.js

  1. define(['jquery'], function($){
  2.   var CustomWidget = function () {
  3.         var self = this, // для доступа к объекту из методов
  4.         system = self.system(), //Данный метод возвращает объект с переменными системы.
  5.         langs = self.langs;  //Объект локализации с данными из файла локализации (папки i18n)
  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.               destroy: function(){              
  22.               },    
  23.               contacts: { selected: function() {                  
  24.                     }
  25.               },
  26.               leads: { selected: function() {                  
  27.                     }
  28.               },
  29.               onSave: function(){        
  30.               }
  31.         };
  32.         return this;
  33.     };
  34.   return CustomWidget;
  35. });

Функции обратного вызова, объект callbacks

FUNCTION NAMEDESCRIPTION
render: При сборке виджета первым вызывается callbacks.render.В этом методе обычно описываются действия для отображения виджета.Виджет будет отображаться самостоятельно только в меню настроек (settings), для отображения виджета в других областях, например в правой колонке, необходимо использовать специальные методы в этой функции, например методы объекта render() и/или render_template(), которые разобраны далее. Необходимо чтобы callbacks.render вернул true. Это важно, т.к. без этого, не запустятся методы callbacks.init и callbacks.bind_actions.
init: Запускается сразу после callbacks.render одновременно с callbacks.bind_actions.Метод init() обычно используется для сбора необходимой информации и других действий, например связи с сторонним сервером и авторизации по API, если виджет используется для передачи или запроса информации стороннему серверу.В самом простом случае он может ,к примеру,определять текущую локацию, где находится пользователь.callbacks.init должен возвращать true для дальнейшей работы.
bind_actions: Метод callbacks.bind_actions используется для навешивания событий на действия предпринимаемые пользователем, например нажатие пользователя на кнопку. callbacks.bind_actions должен возвращать true.
settings: Метод callbacks.settings вызывается при щелчке на иконку виджета в области настроек.Может использоваться для добавления на страницу модального окна, подробнее это рассмотрено далее.
Публичные виджет не должны никак скрывать/влиять на рейтинг и отзывы виджета.
dpSettings: Метод callbacks.dpSettings аналогичен callbacks.settings, но вызывается в области видимости digital_pipeline (подробнее Digital pipeline)
onSave: callbacks.onSave вызывается при щелчке пользователя на кнопке "Сохранить" в настройках виджета. Можно использовать для отправки введенных в форму данных и смены статуса виджета.
leads:selected Данная функция вызывается в случае выбора элементов списка сделок, с использованием checkbox, и последующем нажатии на имя виджета в добавочном меню. Используется, когда нужно предпринять какие-либо действия с выделенными объектами. Примеры рассмотрены далее.
contacts:selected Данная функция вызывается в случае выбора элементов списка контактов, с использованием checkbox, и последующем нажатии на имя виджета в добавочном меню. Используется, когда нужно предпринять какие-либо действия с выделенными объектами. Примеры рассмотрены далее.
destroy: Данная функция вызывается при отключении виджета через меню его настроек. Например нужно удалить из DOM все элементы виджета, если он был отключен, или предпринять еще какие-либо действия.

Пример JS-кода виджета:

Приведенный ниже пример демонстрирует варианты использования объекта функций обратного вызова с дополнительными функциями, а так же применение некоторых функций объекта виджет. Все эти функции рассмотрены в примерах далее.Советуем просто просмотреть данный код, а за подробностями обратиться к описанию функций объекта widget.

Данный виджет будет выбирать из листа контактов отмеченные контакты и передавать телефоны и e-mail адреса на сторонний сервер.

Функции, используемые в данном примере разобраны более подробно далее. В первую очередь стоит обратить внимание на объект callbacks.

  1. define(['jquery'], function ($) {
  2.   var CustomWidget = function () {
  3.     var self = this,
  4.       system = self.system;
  5.  
  6.     this.get_ccard_info = function () //Сбор информации из карточки контакта
  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) { // Отправка собранной информации
  36.       self.crm_post(
  37.         'http://example.com/index.php',
  38.         {
  39.           // Передаем POST данные
  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; //в данном случае w_code='new-widget'
  71.         if (typeof(AMOCRM.data.current_card) != 'undefined') {
  72.           if (AMOCRM.data.current_card.id == 0) {
  73.             return false;
  74.           } // не рендерить на 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.                   <link type="text/css" rel="stylesheet" href="/widgets/' + w_code + '/style.css" >'
  92.         });
  93.         return true;
  94.       },
  95.       contacts: {
  96.         selected: function () {    //Здесь описано поведение при мультивыборе контактов и клике на название виджета
  97.           var c_data = self.list_selected().selected;
  98.  
  99.           $('#js-sub-lists-container').children().remove(); //Контейнер очищается затем в контейнер собираются элементы, выделенные в списке.контейнер - div блок виджета, отображается в правой колонке.
  100.           var names = [], // Массив имен
  101.             length = c_data.length; // Количество выбранных id (отсчет начинается с 0)
  102.           for (var i = 0; i < length; i++) {
  103.             names[i] = {
  104.               emails: c_data[i].emails,
  105.               phones: c_data[i].phones
  106.             };
  107.           }
  108.           console.log(names);
  109.           for (var i = 0; i < length; i++) {
  110.             $('#js-ac-sub-lists-container').append('<p>Email:' + names[i].emails + ' Phone:' + names[i].phones + '</p>');
  111.           }
  112.           $(self.contacts).remove(); //очищаем переменную
  113.           self.contacts = names;
  114.         }
  115.       },
  116.       leads: {
  117.         selected: function () {
  118.  
  119.         }
  120.       },
  121.       onSave: function () {
  122.  
  123.         return true;
  124.       }
  125.     };
  126.     return this;
  127.   };
  128.   return CustomWidget;
  129. });

Содержание файла new_widget.css, который может находиться в папке с виджетом

  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. }
  49.  

Методы объекта widget

Метод render()

Метод render() используется, для работы с шаблонами шаблонизатора twig.js, который удобен в использовании, ознакомиться с документацией можно по ссылке.

Метод является оборачивающим для twig.js и принимает в качестве параметров информацию по шаблону(data) и данные для рендеринга данного шаблона (params). render(data, params). Метод возвращает отрендеренный шаблон. result = twig(data).render(params).

Разберем простой Пример:

  1. var params = [  
  2.           {name:'name1',
  3.            id: 'id1'},
  4.           {name:'name2',
  5.            id: 'id2'},
  6.           {name:'name3',
  7.            id: 'id3'}
  8.           ]; //массив данных, передаваемых для шаблона
  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},// передаем шаблон
  17.               {names: params}));   

В результате мы получим разметку :

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

Можно передать функции один из шаблонов нашей системы, для этого в передаваемом объекте data нужно указать ссылку на шаблон: ref: '/tmpl/controls/#TEMPLATE_NAME#.twig. Например для создания раскрывающегося списка используем существующий шаблон:

  1.  
  2. m_data = [  
  3.           {option:'option1',
  4.            id: 'id1'},
  5.           {option:'option2',
  6.            id: 'id2'},
  7.           {option:'option3',
  8.            id: 'id3'}
  9.           ]; //массив данных, передаваемых для шаблона
  10.              
  11.           var data = self.render(
  12.           {ref: '/tmpl/controls/select.twig'},// объект data в данном случае содержит только ссылку на шаблон
  13.            {
  14.             items: m_data,      //данные
  15.             class_name:'subs_w',  //указание класса
  16.             id: w_code +'_list'   //указание id
  17.             });

Чтобы посмотреть на разметку data, надо добавить data в DOM. Разметка раскрывающегося списка выполнена в стиле нашей системы.

Полный список шаблонов приведен ниже. Для использования других системных шаблонов нужно поменять параметр ref, общий вид: ref: '/tmpl/controls/#TEMPLATE_NAME#.twig'

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

Методу render() можно передавать не только сслыки системные существующие шаблоны, но и ссылки на собственные шаблоны.Для этого надо передавать объект data с рядом параметров. Необходимо создать папку templates в папке нашего виджета и положить в нее шаблон template.twig. Рассмотрим пример:

  1. var params = {}; //пустые данные
  2. var callback = function (template){ //функция обратного вызова,вызывается если шаблон загружен, ей передается объект шаблон.
  3. var markup = template.render(params); //
  4.  /*
  5.  *далее код для добавления разметки в DOM
  6.  */
  7. };
  8.     var s = self.render({
  9.                 href:'templates/template.twig', //путь до шаблона
  10.                 base_path: self.params.path; //базовый путь до директории с виджетом
  11.                 load: callback //вызов функции обратного вызова произойдет только если шаблон существует и загружен
  12.             }, params); //параметры для шаблона
  13.  

Если шаблон существует по адресу ссылки, то вызывается переданная функция callback, и ей передается объект шаблон, который содержит метод render, передаем render параметры для рендеринга. В данном примере вызов функции обратного вызова произойдет, если шаблон существует в папке.

Пример функции для загрузки шаблонов по из папки templates

Для удобства обращения создадим функцию. В нее будем передавать параметры: template - имя шаблона который лежит в папке с виджетом в папке template, params - объект параметров для шаблона, callbacks - функция обратного вызова, которая будет вызываться после загрузки шаблона, в данном случае будем добавлять шаблон в модальное окно. Про объект модальное окно можно почитать в разделе JS методы и объекты для работы с 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, //тут обращение к объекту виджет вернет /widgets/#WIDGET_NAME#
  8.                 load: callback //вызов функции обратного вызова
  9.             }, params); //параметры для шаблона
  10.         }
  11.  
  12. settings: function(){
  13.           self.getTemplate(  //вызов функции
  14.               'login_block', //указываем имя шаблона, который лежит у нас в папке с виджетом в папке templates
  15.               {}, /* пустые данные для шаблона, т.к мы сначала запросим шаблон, если он существует, то функция обр.вызова вызовет уже функцию для добавления данных к шаблону, см.ниже */
  16.               function(template) {
  17.                       template.render({
  18.                           widget_code:self.params.widget_code,//параметры для шаблона.
  19.                           lang:self.i18n('settings')}));
  20.               }};

Метод render_template()

Метод render_template() оборачивает переданную ему разметку или шаблон в стандартную для виджетов оболочку (разметку) и помещает полученную разметку в правую колонку виджетов

Можно передавать данной функции html разметку или шаблон с данными для рендеринга, так же как в случае с методом render().

Функция дополняет переданную ей разметку своей, хранящейся в переменной template_element объекта widget.

  1.  /*
  2.    *html_data хранит разметку, которую необходимо поместить в правую колонку виджетов.
  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', //имя класса для обертки разметки
  18.                   },
  19.           body: html_data,//разметка
  20.           render : '' //шаблон не передается
  21.        }
  22.       );

Был показан самый простой пример без использования шаблона, но метод render_template() так же может принимать шаблон и данные для шаблона в качестве параметров. Так же можно передавать сылку на шаблон, аналогично методу render().

  1. /*
  2. *Здесь в качестве параметров передается шаблон и данные для шаблона.
  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" // в данном случае лучше передать ссылку на lang через self.i18n()
  27.      }
  28.     );

Получаем в правой колонке виджет, созданный по шаблону.

Объект widget имеет еще ряд полезных функций, которые можно вызывать для решения разных задач.

Описание и примеры приведены ниже.

Функция set_lang()

Функция set_lang() позволяет изменять параметры, установленные по умолчанию файлами из папки i18n

Текущий объект lang хранится в переменной langs объекта widget

  1.     langs = self.langs; //Вызываем текущий объект
  2.     langs.settings.apiurl = 'apiurl_new' //меняем имя поля
  3.     self.set_lang(langs);       //меняем текущий объект на объект с измененным полем
  4.     console.log(self.langs); //выводим в консоль, чтобы убедиться, что название изменилось

Функция set_settings()

Функция set_settings() позволяет добавлять виджету свойства.

  1. self.set_settings({par1:"text"}); //создается свойство с именем par1 и значением text
  2. self.get_settings();// в ответ придет массив с уже созданным свойством

Функция list_selected()

Функция list_selected() возвращает выделенные галочками контакты/сделки из таблицы контактов/сделок в виде массива объектов: count_selected и selected. Один из объектов selected содержит массив выделенных галочками объектов с свойствами emails, id, phones, type.

  1.     console.log(self.list_selected().selected); //Возвращает два объекта, выбираем объект selected
  2.     //Результат:
  3.      /*0: Object
  4.        emails: Array[1]
  5.        id: #id#
  6.        phones: Array[1]
  7.        type: "contact" */

Функция widgetsOverlay()

Функция widgetsOverlay() (true/false) включает или отключает оверлей, который появляется при вызове виджета из списка контактов или сделок.

  1. //Пример:
  2.     self.widgetsOverlay(true);

Функция add_action()

При работе пользователя в области список контактов и компаний, можно обеспечить вызов какой-либо функции, при щелчке на номер телефона или e-mail адрес контакта.

Функции add_action() передаются параметры (type,action), где type - "e-mail" или "phone", action - функция, которая будет вызываться при щелчке на номере телефона или адресе e-mail.

  1. self.add_action("phone",function(){
  2.     /*
  3.     * код взаимодействия с виджетом телефонии
  4.     */
  5. });

Функция set_status()

Виджет может иметь один из трех статусов. Статус виджета отображен в области settings, на иконке виджета. В случае, если виджет использует данные, введенные пользователем для API стороннего сервиса, и эти данные введены неверно, то можно использовать данную функцию для отображения статуса error.

Доступны статусы install(виджет не активен) и installed (виджет активен), error (виджет в состоянии ошибки).

  1. //Пример:
  2. self.set_status('error');    

Метод crm_post(url, data, callback, type, error)

Метод используется для отправки запроса на ваш удаленный сервер через прокси-сервер amoCRM. Его использование необходимо, т.к. при работе с amoCRM пользователь работает по защищенному SSL протоколу и браузер может блокировать кросс-доменные запросы. Лучшем решением является наличие подписанного SSL-сертификата на стороне внутренней системы и работа по HTTPS. Функция аналогична jQuery post(), но добавлена возможность отлова ошибок (5ый аргумент error) см. документацию (http://docs.jquery.com/Post)

Описание метода

PARAMETERTYPEDESCRIPTION
url Строка Ссылка на скрипт обрабатывающий данные
data optional Javascript объект Пары ключ:значение, которые будут отосланы на сервер
callback optional Функция Функция, вызывающаяся после каждого успешного выполнения (в случае передачи type=text or html, выполняется всегда).
typeoptional Строка Тип данных, который возвращается функции: “xml”, “html”, “script”, “json”, “jsonp”, или “text”.
error optional Функция Функция, вызывающаяся после каждого не удачного выполнения (не распространяется на type=text or html).

Request Example

  1. self.crm_post (
  2.  
  3.           'http://www.test.ru/file.php',
  4.         {
  5.           // Передаем POST данные с помощью объектной модели Javascript
  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.       )

Метод self.get_settings

Данный метод необходим, для того, чтобы получить данные, которые ввел пользователь при подключении виджета. Данные возвращаются в виде объекта javascript

Response Example

  1. {
  2.   login: "ZABRTEST",
  3.   password: "test",
  4.   maybe: "Y"
  5. }

Метод self.system()

Данный метод необходим, для того, чтобы получить системные данные. Данные возвращаются в виде объекта javascript

PARAMETERDESCRIPTION
area Область на которой воспроизводится виджет в данный момент
amouser_id Id пользователя
amouser Почта пользователя
amohash Ключ для авторизации API

Response Example

  1. {
  2. area: "ccard",
  3. amouser_id: "103586",
  4. amouser: "testuser@amocrm.ru",
  5. amohash: "d053abd66063225fa8b763afz6496da8"
  6. }

Метод self.i18n(objname)

Данные метод позволяет, получить объект из языковых файлов, в котором будут сообщения на языковых локалях, используемые пользователем
В objname передается имя объекта, который необходимо вытащить

Например, вызываем функцию self.i18n('userLang')

Response example

  1. {
  2.   firstWidgetText: "Кликни на кнопку, чтобы переслать данные на сторонний сервер:",
  3.   textIntoTheButton: "Отправить данные",
  4.   responseMessage: "Ответ сервера :",
  5.   responseError: "Ошибка"
  6. }

Таким образом, имея простой инструмент для взаимодействия с DOM и выполнения кроссдоменных запросов, вы можете помимо создания простых текстовых виджетов, менять дизайн элементов страницы, создавать собственные информационные блоки на основе внешних данных, или наоборот, пересылать данные во внешние сервисы, причем все это работает сразу для всех пользователей вашего аккаунта.

Next Step: Upload Widget