Предистория
У меня 5 лет инженерного опыта, включающего в себя .NET (+forms, +WPF, +.NET MVC), Java (+Swing, +Tapestry5, +Groovy), JavaScript (+CoffeeScript, +Node).
Последний год я активно пишу собственное одностраничное веб-приложение работающее без перезагрузки, (о котором речь будет в следующих выпусках). Вся динамическая часть пользовательского интерфейса создается на клиенте, с сервера приходят только данные. Как часто бывает в UI, я имею дело с деревом компонентов. Разумеется, для организации взаимодействия дерева мне нужен механизм отправки и обработки событий. Я решил написать свой, и не использовать Backbone, или что-то из Google Closure. В любом случае у меня был опыт реализации этюда Слушатель (Listener pattern).
Класс ИспускательСобытий первой версии если ты хотел слушать его — просто записывал тебя в массив слушателей. Когда возникало событие «Ч» он обходил массив, и искал слушателей с методом «наЧ», и вызывал его. Типа как в Swing / .NET.
Все отлично работало для небольшого числа Испускателей. С ростом системы пришла проблема перекрытия имен событий между разными Испускателями. Подписчик выполнял один и тот же метод «наЧ», даже если «Ч» исходило от разных Испускателей. Затем был более привычный для JS, EventEmitter, как в jQuery / NodeJS. Не буду особо затягивать…
SuperEmitter
Теперь я работаю с табличным представлением дерева «испускатель -> события -> реакции». Пример:
class Brain extends SuperEmitter event_table: [ # источник событие реакции [ 'ear' , [ [ 'snake_heard' , [ 'emit_adrenaline' 'look_around' ] ] ] ] [ 'eye' , [ [ 'food_spotted' , [ 'emit_noradrenaline' 'hunt' 'emit_endorphins' ] ] [ 'predator_spotted', [ 'emit_cortisol' 'emit_adrenaline' 'run' ] ] ] ] [ 'nose', [ [ 'food_smelled' , [ 'look_around' ] ] [ 'blood_smelled' , [ 'emit_adrenaline' 'look_around' ] ] ] ] ] # constructor and instance members constructor = -> @ear = new Ear() @eye = new Eye() @nose = new Nose() # methods: emit_adrenaline: -> emit_cortisol: -> emit_endorphins: -> hunt: -> look_around: -> new Brain().bind_events()
Вместо:
class Brain bind_events: -> @ear.on 'snake_heard', (args...) => # условные аргументы @emit_adrenaline(args...) # условная передача аргументов @look_around(args...) @eye.on 'food_spotted', (args...) => @emit_noradrenaline(args...) @hunt(args...) @emit_endorphins(args...) @eye.on 'predator_spotted', (args...) => @emit_cortisol(args...) @emit_adrenaline(args...) @run(args...) @nose.on 'food_smelled', (args...) => @look_around(args...) @nose.on 'blood_smelled', (args...) => @emit_adrenaline(args...) @look_around(args...) # и т.д.
Это моя библиотека ognivo/super-emitter. Она позволяет убрать весь соединительный код, и сосредоточиться на источниках, событиях и реакциях. Кода меньше, он чище, более гранулирован. Формат позволяет видеть, что и откуда происходит и последовательность ответов.
Более того, таблица — это данные, и с ней можно делать все, что можно делать с данными. Сравнивать, склеивать, клонировать и проч.
Демо
Демо онлайн. Это из папки demo в репо. Можете клонировать репо, можете выполнить
npm install super-emitter
. Я хочу сделать более масштабное демо, но пока туго со временем.
Выпуск и прием событий
SuperEmitter реализован как класс. В нем есть метод emit, через который выпускается событие и передаются его аргументы. Аргументы должны быть упакованы в массив при вызове, что выделяет их визуально. Функция подписанная на событие принимает аргументы через обычный списк параметров.
brain = new Brain() brain.bind_events() brain.emit('adrenaline') brain.emit('adrenaline', [dosage_ml = 300, noradrenaline = true]) brain.on 'adrenaline', (dosage_ml, noradrenaline) -> console.log "I'm running from something" # OR methods class Brain extends SuperEmitter emit_adrenaline: (dosage_ml = 10, noradrenaline = true) -> @emit('adrenaline, [dosage_ml, noradrenaline]) receive_adrenaline: (dosage, noradrenaline) -> if noradrenaline console.log "I will fight to death" else console.log "Let's get outta here"
Требования
Экземпляр на который подписываешься должен иметь метод «on» с сигнатурой (есть ли русское слово?) event_name, callback. Подходят jQuery обертки, KineticJS фигуры (shapes), экземпляры класса SuperEmitter.
Стандартный для DOM-компонентов метод addEventListener имеет такую же сигнатуру, так что с ним тоже ничего сложного. Однажды мне нужно было подписаться на popstate от окна, соответственно все решилось window.on = window.addEventListener.
Чего нет
Нету всплытия и перехвата событий. Причин делать пока не вижу, мне нравится что для каждого уровня иерархии компонентов я вижу все события которые через него проходят.
Буду рад критике, похвале и дополнениям.
З.Ы.: я намерено выбрал слово испускатель, а не источник.
ссылка на оригинал статьи http://habrahabr.ru/post/212883/
Добавить комментарий