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

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

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

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

<!-- My/Control.wml -->
<div>
    <button on:click="clickHandler()">Нажми на меня</button>
</div>
// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
     protected _template: TemplateFunction = template;
     clickHandler(): void {
          alert('В элементе button произошло событие click.')
     }
}

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

<!-- My/Control.wml -->
<My.Button on:myEvent="myHandler()" />
// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
     protected _template: TemplateFunction = template;
     myHandler(): void {
         alert('В контроле произошло событие myEvent.')
     }
}

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

<!-- My/Control.wml -->
<div>
    <ws:partial template="wml!My/Template" on:someEvent="simpleHandler()" />
</div>
// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    simpleHandler(): void {
        alert('В шаблоне произошло событие someEvent.')
    }
}

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

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

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

  • проще отслеживать созданные обработчики;
  • эффективнее тестировать функционал;
  • выполняется автоматическая отписка от события.
    Ниже показан примере того, как делать запрещено:
    // My/Control.ts
    import { Control, TemplateFunction } from 'UI/Base';
    import * as template from 'wml!My/Control';
    export default class extends Control {
        protected _template: TemplateFunction = template;
        protected _afterMount(): void {
            this._children.myDiv.addEventListener('click', function() {
                // some work
            });
        }
    }
    Не стоит подписываться нативно, т.к. при синхронизации верстки элемент может быть пересоздан и подписка пропадет, оставив за собой утечку.

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

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

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

<!-- Demo/Template.wml -->
<div>
    <button on:click="clickHandler('Сейчас', getCurrentYear(), myUnit)">Нажми на меня</button>
</div>
// Demo/Module.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!Demo/Template';

export default class extends Control {
    protected _template: TemplateFunction = template;
    protected myUnit: "год";

    clickHandler(e, msg, date, unit): void {
        alert(msg + " " + date + " " + unit);
    }

    getCurrentYear(): Date {
        return new Date().getFullYear();
    }
}

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

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

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

// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    count: 0;
    clickHandler(): void {
        this.count++;
    }
}
<!-- My/Control.wml -->
<div>
    Counter: <button on:click="clickHandler()">{{ count }}</button>
</div>

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

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

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

<!-- My/Control.wml -->
<div>
    <My.Other.Control name="otherControl">
    <button on:click="otherControl.clickHandler()">Нажми на меня</button>
</div>
// My/Other/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Other/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    clickHandler(): void {
        alert('В элементе button произошло событие click.')
    }
}

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

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

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

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

<!-- My/Control.wml -->
<div>
    <button on:click="clickHandler()">Нажми на меня</button>
    <My.TextBox on:valueChanged="myHandler()" />
</div>
// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;

    clickHandler(e): void {
        console.log(e.nativeEvent.clientX)
    }

    myHandler(e): void {
        console.log(e.target)
    }
}

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

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

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

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

// My/Button.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    startValue = 10;
    endValue = 20;

    protected _afterMount(): void {
        this._notify("myEvent", [this.startValue, this.endValue]);
    }
}
<!-- My/Button.wml -->
<My.Button on:myEvent="myHandler(newNumber)" />
// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    newNumber = 30;

    myHandler(e, arg1, arg2, arg3): void {
        console.log(arg1) // 30
        console.log(arg2) // 10
        console.log(arg3) // 20
    }
}

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

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

// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;

    protected _afterMount(): void {
        this._notify("myEvent");
    }
}

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

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

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

// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    startValue: 10;
    endValue: 20;

    protected _afterMount(): void {
        this._notify("myEvent", [this.startValue, this.endValue]);
    }
}

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

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

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

// My/Control.ts
import { Control, TemplateFunction } from 'UI/Base';
import * as template from 'wml!My/Control';

export default class extends Control {
    protected _template: TemplateFunction = template;
    startValue: 10;
    endValue: 20;

    protected _afterMount(): void {
        this._notify("myEvent", [], {bubbling: true});
    }
}

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

Особенности работы системы событий Wasaby

  1. Подписка на события происходит на фазе захвата. Система событий сама обеспечивает всплытие события с учетом controlNode. Таким образом, любые нативные обработчики (добавленные через addEventListener) будут конфликтовать с системой событий.
  2. События сохраняются на DOM-элементах в eventProperties, это значит, что подписки, добавленные на контрол (или хок) будут смержены с событиями на корневом элементе. Сначала добавляются события подписанные на корневом элементе, потом события с хока, и в конце события с контрола.
    <!-- Шаблон родителя -->
    <div>
       <HOC on:click="clickHandler()">
          <C2 />
       </HOC>
       <C2 on:click="clickHandler()"/>
    </div>
    <!-- Шаблон ребенка -->
    <div on:click="clickHandler()"> content </div>
    <!-- Шаблон хока -->
    <ws:partial template="{{ _options.content }}" on:click="clickHandler()" />

    В данном случае мы получим следующую верстку:
    <div>
       <div> content </div>
       <div> content </div>
    </div>

    События в данном случае будут такие:
    <!-- Первый div content -->
    {
       on:click:  [
          {value: "clickHandler", viewController: C2}
          {value: "clickHandler", viewController: HOC}
          {value: "clickHandler", viewController: C1}
       ]
    }
    <!-- Второй div content -->
    {
       on:click:  [
          {value: "clickHandler", viewController: C2}
          {value: "clickHandler", viewController: C1}
       ]
    }
  3. Вызов обработчиков основывается на порядке добавления обработчика в eventProperties, это значит, что вызов stopPropagation в обработчике приведет к остановке всплытия события, т.е. событие перестанет всплывать как по controlNode, так и нативно.

Если необходимо остановить только синтетическое событие Wasaby, то следует использовать event.stopSyntheticEvent(), где event наследник SyntheticEvent. В таком случае будет остановлено только всплытие по controlNode.

// TypeScript
{
    on:click:  [
        {value: "clickHandler2", viewController: C2}
        {value: "clickHandler1", viewController: C1}
    ]
}
protected clickHandler2(event): void {
    event.stopPropagation();
}
protected clickHandler1(event): void {
    // some action
}

В данном случае вызов обработчика clickHandler1() по клику не произойдет.