Работа с событиями

Подписка на событие

Для подписки на событие используют директиву on. Можно подписаться на события элементов шаблона:

В следующем примере показана подписка на нативное событие DOM-элемента.

<!-- WML -->
<div>
    <button on:click="clickHandler()">Нажми на меня</button>
</div>
// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      clickHandler: function() {
         alert('В элементе button произошло событие click.').
      }
   });
   return ModuleClass;
});

В следующем примере показана подписка на событие контрола.

<!-- WML -->
<div>
    <My.Button on:myEvent="myHandler()" />
</div>
// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      myHandler: function() {
         alert('В контроле произошло событие myEvent.').
      }
   });
   return ModuleClass;
});

В следующем примере показана подписка на событие шаблона.

<!-- WML -->
<div>
    <ws:partial template="wml!My/Template" on:someEvent="simpleHandler()" />
</div>
// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      simpleHandler: function() {
         alert('В шаблоне произошло событие someEvent.').
      }
   });
   return ModuleClass;
});

Директива on имеет следующую форму записи:

on:<событие>="<обработчик>(<параметры>)"

В Wasaby отказались от динамической подписки на события. Применяется только декларативная форма, благодаря которой:

  • проще отслеживать созданные обработчики;
  • эффективнее тестировать функционал;
  • выполняется автоматическая отписка от события.
    Пример:
    define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
       var ModuleClass = Base.Control.extend({
          _template: template,
          _afterMount: function() {
             this._children.myDiv.addEventListener('click', function() {
                // some work
             });
          }
       });
       return ModuleClass;
    });
    Не стоит подписываться нативно, т.к. при синхронизации верстки элемент может быть пересоздан и подписка пропадет, оставив за собой утечку.

В шаблоне можно подписаться только на события, которые происходят в элементах шаблона. При публикации события подписаться на него можно только в родительском контроле.

Передача параметров события из шаблона

В шаблоне параметр обрабатывается как синтаксическая конструкция. Результат её вычисления передаётся в аргумент обработчика.

<!-- Demo/Template.wml -->
<div>
    <button on:click="clickHandler('Сейчас', getCurrentYear(), myUnit)">Нажми на меня</button>
</div>
// Module.js
define("Demo/Module", ["UI/Base", "wml!Demo/Template"], function(Base,template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      myUnit: "год",
      clickHandler: function(e, msg, date, unit) {
         alert(msg + " " + date + " " + unit);
      },
      getCurrentYear: function() {
         return new Date().getFullYear();
      }
   });
   return ModuleClass;
});

См. демо-пример на CodeSandbox

Обработчик события

Обработчик события описывается как метод в рамках класса контрола (см. Создание класса контрола). В теле обработчика переменная this содержит экземпляр класса контрола.

// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      count: 0,
      clickHandler: function() {
         this.count++;
      }
   });
   return ModuleClass;
});
<!-- WML -->
<div>
    Counter: <button on:click="clickHandler()">{{ count }}</button>
</div>

См. демо-пример на CodeSandbox

При разработке контрола на TypeScript рекомендуется придерживаться следующих правил при создании обработчика.

В качестве обработчика события можно указать метод из дочернего контрола. Для этого дочерний контрол добавляют в шаблон и задают ему имя в атрибуте name. При указании имени обработчика используют такую форму записи <значение атрибута name>.<имя обработчика>(<параметры>)

<!-- WML -->
<div>
    <My.Other.Control name="otherControl">
    <button on:click="otherControl.clickHandler()">Нажми на меня</button>
</div>
// JavaScript
define("My/Other/Control", ["UI/Base",... ], function(Base, ...) {
   var ModuleClass = Base.Control.extend({
      ...
      clickHandler: function() {
         alert('В элементе button произошло событие click.').
      }
   });
   return ModuleClass;
});

Дескриптор события

В обработчик первым аргументом всегда приходит дескриптор события (экземпляр класса Vdom/Vdom:SyntheticEvent). Он содержит детальную информацию о событии, для доступа к которой можно обратиться к следующим свойствам:

  • nativeEvent — содержимое этого свойства зависит от типа события. Для нативного события содержит поля интерфейса Event. Принимает null, если событие публикуется контролом.
  • result — значение, возвращенное из предыдущего обработчика, который выполнен на то же событие.
  • stopped — признак, что не происходит дальнейшее всплытие события по цепочке.
  • target — DOM-элемент, в котором произошло событие.
  • type — имя события.

В примере ниже показана работа с обоими типами событий:

<!-- WML -->
<div>
    <button on:click="clickHandler()">Нажми на меня</button>
    <My.TextBox on:valueChanged="myHandler()" />
</div>
// JavaScript
define("My/Module", ["UI/Base", "wml!My/Template"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      clickHandler: function(e) {
         console.log(e.nativeEvent.clientX)
      },
      myHandler: function(e) {
         console.log(e.target)
      }
   });
   return ModuleClass;
});

Начиная со второго аргумента доступны параметры, переданные из шаблона или из модуля.

Приоритет обработки параметров события

В аргументах обработчика события сначала доступны параметры из шаблона, а далее — параметры из модуля, переданные в момент публикации.

В следующем примере создана подписка на событие myEvent.

// JavaScript
define("My/Button", ["UI/Base", "wml!My/Button"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      startValue: 10,
      endValue: 20,
      _afterMount: function() {
         this._notify("myEvent", [this.startValue, this.endValue]);
      }
   });
   return ModuleClass;
});
<!-- WML -->
<div>
   <My.Button on:myEvent="myHandler(newNumber)" />
</div>
// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      newNumber: 30,
      myHandler: function(e, arg1, arg2, arg3){
         console.log(arg1) // 30
         console.log(arg2) // 10
         console.log(arg3) // 20
      }
   });
   return ModuleClass;
});

Публикация пользовательского события

Для публикации события в контроле вызывают метод _notify(). Первый аргумент метода — обязательный, он принимает имя события.

// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      _afterMount: function() {
         this._notify("myEvent");
      }
   });
   return ModuleClass;
});

События не публикуют в хуке _beforeMount(), т.к. в этой точке жизненного цикла контрол не инициализирован.

Передача параметров события из модуля

Во втором аргументе метода _notify() в массиве можно передать параметры. Они будут доступны в аргументах обработчика события.

// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      startValue: 10,
      endValue: 20,
      _afterMount: function() {
         this._notify("myEvent", [this.startValue, this.endValue]);
      }
   });
   return ModuleClass;
});

Всплываемость

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

Для этого следует в методе _notify() передать третьим аргументом значение {bubbling: true}.

// JavaScript
define("My/Control", ["UI/Base", "wml!My/Control"], function(Base, template) {
   var ModuleClass = Base.Control.extend({
      _template: template,
      startValue: 10,
      endValue: 20,
      _afterMount: function() {
         this._notify("myEvent", [], {bubbling: true});
      }
   });
   return ModuleClass;
});

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