31 мая 2014 г.

Создание расширений (custom bindings) для Knockout. Простой пример

При работе с KnockoutJS, рано или поздно приходится создавать свои собственные расширения. Не знаю, как у других, а для меня эта часть всегда была самой проблемной в изучении очередного фреймоврка. Изначально кажется, что такая возможность врятле пригодится в работе, и нужна лишь для любителей экспериментов. На самом же деле, написание своих binding-ов может здорово помочь в повседневной разработке, а также сделает код более структурированным и однотипным. Давайте разберем простой пример...


Очень часто, при написании knockout-приложения, у нас есть необходимость подключать к проекту дополнительные JQuery-плагины. Практика показывает, что в большинстве случаем лучше делать это с помощью расширения функционала knockout. Я хочу рассмотреть именно такую ситуацию.

Предположим, что нам нужно интегрировать в проект плагин JQuery Limit. Он позволяет указывать лимиты символов для тестовых полей.
Вот, как бы выглядело представление в "классическом" варианте применения плагина:

<textarea data-bind="value: message" cols="30" rows="6"></textarea>
<br>
<span id="charsLeft"></span> chars left

Теперь скрипт:

   1:  function viewModel() {
   2:      var self = this;
   3:      
   4:      self.message = ko.observable('');
   5:      
   6:      $('textarea').limit('140','#charsLeft');
   7:  }
   8:   
   9:  ko.applyBindings(new viewModel());


Как видите, все очень просто. На странице у нас есть поле для ввода текста, и span, в котором будет отображаться оставшееся кол-во символов. Последним действием модели мы инициализируем плагин limit.

Этот пример вполне рабочий. Кода мало, функционал есть, в чем же проблема?
На данном этапе проблемы нет. Но она обязательно появится, когда нам нужно будет применять этот плагин повсеместно по всему проекту, а не только для одного текстового поля.
Представьте, что у вас есть форма, содержащая несколько полей для воода, каждое из которых нужно лимитировать. В таком случае наш скрипт выглядел бы так:

   1:  function viewModel() {
   2:      var self = this;
   3:      
   4:      self.message = ko.observable('');
   5:      
   6:      ....    
   7:      
   8:      $('#textarea1').limit('140','#charsLeft1');
   9:      $('#textarea2').limit('150','#charsLeft2');
  10:      $('#input1').limit('70','#charsLeft3');
  11:      $('#input2').limit('60','#charsLeft4');
  12:      $('#textarea3').limit('160','#charsLeft5');
  13:  }


Согласитесь, смотрится "не очень" ))

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

Начнем, пожалуй, с самого расширения. Вот как будет выглядеть обновленный скрипт:

   1:  function viewModel() {
   2:      var self = this;
   3:      
   4:      self.message = ko.observable('');
   5:  }
   6:   
   7:  ko.bindingHandlers['limitator'] = {
   8:      update: function(element, valueAccessor) {
   9:          var options = ko.utils.unwrapObservable(valueAccessor());
  10:          $(element).limit(options.limit, options.targetSelector);
  11:      }
  12:  };
  13:   
  14:  ko.applyBindings(new viewModel());


Мы создали простейшее расширение "limitator", которое будет инициализировать плагин с параметрами, полученными из байндинга. В нашем случае параметров должно быть 2: limit - кол-во символов, targetSelector - элемент, который будет содержать кол-во оставшихся символов.

Теперь, вместо инициализации через модель, нужно лишь добавить к требуемому текстовому полю новый байндинг:

<textarea data-bind="value: message, limitator: {limit: 140, targetSelector: '#charsLeft'}"></textarea>
<br>
<span id="charsLeft"></span> chars left


Тоесть, при наличии нескольких полей для лимитации, представление выглядело примерно так:

   1:  <textarea data-bind="limitator: {limit: 140, targetSelector: '#charsLeft1'}"></textarea>
   2:  <textarea data-bind="limitator: {limit: 150, targetSelector: '#charsLeft2'}"></textarea>
   3:  <textarea data-bind="limitator: {limit: 160, targetSelector: '#charsLeft3'}"></textarea>
   4:  <input type="text" data-bind="limitator: {limit: 70, targetSelector: '#charsLeft4'}" />
   5:  <input type="text" data-bind="limitator: {limit: 60, targetSelector: '#charsLeft5'}" />


Думаю, что разница, как и польза, очевидны.
Мы вынесли логику инициализации в отдельный модуль, и применяем этот модель к элементам не из скрипта, а прямо на странице, используя механизм привязок knockout js.

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


______
Исходники