Сегодня я начинаю цикл статей посвященных фреймворку ExtJs. Это не будут туториалы или мануалы для пошагового изучения библиотеки. Я просто хочу поделиться своим опытом, поэтому начну с чего попало, а именно – с событий.

С изобретением GUI и Мыши, DOM-объекты, подобные кнопкам и текстовым полям стали обладать некими событиями. Например, click, mouseover, keydown и другими. По сути событие – это сообщение, вызов функции, генерируемое одной частью приложения (источником), которое извещает другую часть приложения (приемник или подписчик) о том, что что-то случилось.

   1: <div id="theDiv" onclick="alert('You clicked me')">Click me!</div>

В ExtJs DOM-элементы оборачиваются в Ext.Element, поэтому подписка на тоже событие будет выглядеть так:

   1: Ext.get('theDiv').on('click', function() {alert('You clicked me');});

При разработке сложных пользовательских интерфейсов, отдельные элементы DOMа получают особое назначение. К примеру, банальный div может быть заголовком колонки в таблице (я имею ввиду ExtJs Grid). Следовательно, когда пользователь захочет изменить порядок колонок в гриде и начнет перетягивать колонку за заголовок – в заголовке произойдут некие события (mouseover, mousedown, click, …). Каждый раз отслеживать порядок и детали “низкоуровневых” событий в своем коде рутинно. Поэтому ExtJs поддерживает собственную модель событий. К примеру, GridPanel поддерживает такие события как columnmove, cellclick, headerclick и другие.

Вся магия событий в ExtJs реализована в классе Ext.util.Observable. Чтобы наделить ваш компонент свойствами источника события, вам необходимо “унаследоваться” от Ext.util.Observable.  Если же вы наследуете свой компонент от других компонентов, таких как Panel, Grid, Form, Tree и другие, то дополнительно наследоваться от Ext.util.Observable не обязательно. О принципе паттерна Observable и об особенностях его реализации на javascript можно прочесть на CodeProject.

После того как вы так или иначе унаследовались от Ext.util.Observable, вы можете добавить ваши собственные события примерно таким образом

   1: this.addEvents( 'IAmHappy', 'IAmNotHappy' );

Где, this – это и есть ваш компонент.

Инициировать ваше событие также просто:

   1: this.fireEvent('IAmHappy', this, firstArg, secondArg);

Когда мы вызываем функцию fireEvent, то Observable смотрит, подписывался ли на это событие (в данном случае – ‘IAmHappy’) кто-либо или нет. Если подписывался, то вызывает функцию-обработчик и устанавливает значение переменной this равное текущему объекту (этого мы добились указав this вторым параметром после названия события).

Также в эту функцию будут переданы параметры event и target. Где, event будет объект типа Ext.EventObject, а target – ссылка на HTMLElement. Следом за этими параметрами пойдут наши, собственные параметры firstArg и secondArg, коих может быть любое количество.

   1: function IAmHappyListener(event, target, fristArg, secondArg){
   2: ...
   3: }

Если же Observable не нашел ни одного подписчика, то он, соответственно, ничего и не вызывает.

Простейший способ подписаться на события ExtJs я показал выше, но существуют также некоторые вариации:

Обработчик событий с задержкой (обработчик запуститься через 250 миллисекунд после события)

   1: el.on('click', this.onClick, this, {delay: 250});

Обработчик событий с буферизацией (это означает, обработчик запуститься не чаще, чем раз в 100 миллисекунд и при условии, что событие произошло)

   1: el.on('click', this.onClick, this, {buffer: 100});

Обработчик событий с условием распространения события

   1: // prevent default
   2: el.on('click', this.onClick, this, {preventDefault: true});
   3:  
   4: // only stop propagation
   5: el.on('click', this.onClick, this, {stopPropagation: true});
   6:  
   7: // prevent default and stop propagation
   8: el.on('click', this.onClick, this, {stopEvent: true});

preventDefault и stopPropagation означают тоже самое, что и соответствующие методы родных, джаваскриптовых событий.

Обработчик событий с другими опциями

   1: el.on('click', this.onClick, this, {
   2:     single: true, // удаляет автоматически, после первого запуска
   3:     delegate: 'li.some-class' // автоматическая делегация событий
   4: });

Добавление сразу нескольких обработчиков может выглядеть вот так:

   1: el.on({
   2:     'click' : {
   3:         fn: this.onClick,
   4:     scope: this,
   5:     delay: 100
   6:     }, 
   7:     'mouseover' : {
   8:         fn: this.onMouseOver,
   9:     scope: this
  10:     },
  11:     'mouseout' : {
  12:         fn: this.onMouseOut,
  13:         scope: this
  14:     }
  15: });

Еще стоит упомянуть о такой полезной функции как relayEvents. Она позволяет делегировать события одного компонента – другому компоненту. К примеру, у вас есть панель A, содержащая другую панель B, а та в свою очередь содержит кнопку Save. По нажатию на кнопку Save произойдет ваше собственное событие onSomeSave. К сожалению, ни панель А, ни панель B, обработать это событие не может. Поэтому панели А необходимо генерировать это событие как свое собственное, в надежде, что кто-то сможет его обработать.

clip_image001

“Передать” события панели B в панель A и позволяет relayEvents:

   1: panelA.relayEvents(panelB, ['onSomeSave']);

Фух, что-то длинный пост получился. Устал. А я ведь еще не рассказал об Ext.EventManager, о такой полезной функции как within и о многом другом. Ну чтож, это в другой раз.

Полезные материалы.

Полезные ссылки

Document Object Model Events

Introduction to Events

Tutorial:Events Explained

Tutorial:RelayEvents