Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Events
Search
ningzbruc
March 19, 2014
Programming
2
100
Events
How to build a custom event system
ningzbruc
March 19, 2014
Tweet
Share
More Decks by ningzbruc
See All by ningzbruc
如何写出一个优秀的开源库
ningzbruc
0
25
去啊无线前端的一天
ningzbruc
1
130
React & Component
ningzbruc
0
14
阿里旅行去啊H5首页总结&Promise
ningzbruc
0
230
KISSY.Base - all about that Base
ningzbruc
0
87
Hammer.js
ningzbruc
1
310
淘宝旅行门票iPad版开发
ningzbruc
0
94
Travel on KISSY mini
ningzbruc
0
160
Why YUI3
ningzbruc
0
170
Other Decks in Programming
See All in Programming
Hotwire or React? ~アフタートーク・本編に含めなかった話~ / Hotwire or React? after talk
harunatsujita
1
120
Make Impossible States Impossibleを 意識してReactのPropsを設計しよう
ikumatadokoro
0
280
CSC509 Lecture 12
javiergs
PRO
0
160
AWS Lambdaから始まった Serverlessの「熱」とキャリアパス / It started with AWS Lambda Serverless “fever” and career path
seike460
PRO
1
260
タクシーアプリ『GO』のリアルタイムデータ分析基盤における機械学習サービスの活用
mot_techtalk
6
1.5k
React への依存を最小にするフロントエンド設計
takonda
12
3.3k
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
6
2k
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.3k
Jakarta EE meets AI
ivargrimstad
0
730
初めてDefinitelyTypedにPRを出した話
syumai
0
420
as(型アサーション)を書く前にできること
marokanatani
10
2.7k
Amazon Bedrock Agentsを用いてアプリ開発してみた!
har1101
0
340
Featured
See All Featured
A better future with KSS
kneath
238
17k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
27
840
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
How to Think Like a Performance Engineer
csswizardry
20
1.1k
Optimising Largest Contentful Paint
csswizardry
33
2.9k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
Product Roadmaps are Hard
iamctodd
PRO
49
11k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Designing on Purpose - Digital PM Summit 2013
jponch
115
7k
Raft: Consensus for Rubyists
vanstee
136
6.6k
The Cost Of JavaScript in 2023
addyosmani
45
6.8k
Fireside Chat
paigeccino
34
3k
Transcript
Events • DOM Events • Custom Events
# DOM Events
add button.addEventListener('click', doSomething, false);
remove button.removeEventListener('click', doSomething, false);
context button.addEventListener('click', obj.fn, false); button.addEventListener('click', obj.fn.bind(obj), false);
e • type • target/currentTarget • preventDefault • stopPropagation/stopImmediatePropagation •
cancelable/bubbles • …
Event Phases
target • target • currentTarget <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li>
<li>five</li> <li>six</li> ul.on('click', doSomething);
delegate <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li> <li>five</li> <li>six</li> ul.delegate('click', 'li',
doSomething);
# Simulate Events
Simulate Events ! // 创建事件 var event = button.createEvent('Event');
! // 初始化事件 event.initEvent('click', true, true); ! // 触发事件 button.dispatchEvent(event);
None
Special Events • tripleclick • tap • valuechange • …
Special Events button.on('touchstart', function() { setTimeout(function()
{ var event = button.createEvent('Event'); event.initEvent('tap', true, true); button.dispatchEvent(event); }, 100); }); ! button.on('tap', doSomething); 触发tap事件
# Custom Events
None
Why • 低耦合 • 易通信
DOM Event Features • 绑定/移除 • 捕获/冒泡 • 阻⽌止默认⾏行为 •
阻⽌止传播 • …
How ! // 创建事件 var event = button.createEvent('Event');
! // 初始化事件 event.initEvent('whatever', true, true); ! // 触发事件 button.dispatchEvent(event);
None
flight.js ! var component = Component.attachTo('#container', cfg); ! component.on('select',
doSomething); ! component.trigger('select', { item: 'item' }); 模拟DOM事件
flight.js
None
What if not DOM-base Component?
None
Pub/Sub Pattern
Pub/Sub Pattern button.addEventListener('click', doSomething, false); publisher topic subscriber
Custom System • EventTarget - publisher • CustomEvent - topic
• Subscriber
Custom System EventTarget CustomEvent CustomEvent CustomEvent Subscriber Subscriber Subscriber
function EventTarget() { this._customEvents = {};
} ! // 绑定事件 EventTarget.prototype.on = function(type, fn, context) { this.getCustomEvent(type).add(fn, context); }; ! // 解除事件 EventTarget.prototype.detach = function(type, fn, context) { this.getCustomEvent(type).remove(fn, context); }; ! // 触发事件 EventTarget.prototype.fire = function(type, payload) { this.getCustomEvent(type).fire(payload); };
! function CustomEvent(host, type) { this._type
= type; this._subscribers = []; } ! // 绑定事件 CustomEvent.prototype.add = function(type, fn, context) { this._subscribers.push(new Subscriber(fn, context)); }; ! // 解除事件 CustomEvent.prototype.remove = function(type, fn, context) { this._subscribers.splice(this.indexOfSub(fn, context), 1); }; ! // 触发事件 CustomEvent.prototype.fire = function(type, payload) { this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, payload); }); };
function Subscriber(fn, context) { this.fn =
fn; this.context = context; //... more property }
! var component = new EventTarget(cfg); ! component.on('select', doSomething);
! component.fire('select', { item: 'item' }); ! component.detach('select', doSomething); 触发select事件
default action function CustomEvent(cfg) { this._type
= cfg.type; this._defaultFn = cfg.defaultFn; this._subscribers = []; } ! // 触发事件 CustomEvent.prototype.fire = function(type, payload) { this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, payload); }); this._defaultFn && this._defaultFn.call(this, payload); };
default action // 发布事件 EventTarget.prototype.publish = function(type, config) {
this.getCustomEvent(type, config); };
default action ! var component = new EventTarget(cfg); !
component.publish('show', { defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', doSomething); ! component.fire('show');
we love default action //选中⼀一个⽇日期 this.publish('select', {
defaultFn: this._defSelectFn }); ! //选中⼀一个⽇日期期间 this.publish('selectRange', { defaultFn: this._defSelectRangeFn }); ! //取消选中⼀一个⽇日期期间 this.publish('unselectRange', { defaultFn: this._defUnSelectRangeFn }); ! //选中⼀一个⽇日期期间开始 this.publish('selectRangeStart', { defaultFn: this._defSelectRangeStartFn }); ! //选中⼀一个⽇日期期间结束 this.publish('selectRangeEnd', { defaultFn: this._defSelectRangeEndFn }); ! //错误事件 this.publish('error', { defaultFn: this._defErrorFn });
preventDefault function EventFacade(payload) { this.prevented =
false; merge(this, payload); } ! EventFacade.prototype.preventDefault = function() { this.prevented = true; };
preventDefault function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._subscribers = []; } ! CustomEvent.prototype.fire = function(type, payload) { var e = new EventFacade(payload); this._subscribers.forEach(function(subscriber) { subscriber.fn.call(context, e); }); if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); } };
preventDefault var component = new EventTarget(cfg); ! component.publish('show', {
cancelable: true, defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', function(e) { e.preventDefault(); }); ! component.fire('show', {}); 不会被执⾏行
preventDefault component.fire(“show"); execute subscribers defaultFn prevented? end yes no
bubbles? <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li> <li>five</li> <li>six</li> We don’t
have a DOM-like structure
bubbles ManagerClass instance1 instance2 instance3 instance4 instance5 instance6 manager.on('select', doSomething);
bubbles function EventTarget() { this._customEvents =
{}; this._targets = []; } ! EventTarget.prototype.addTarget = function(target) { this._targets.push(target); }; ! EventTarget.prototype.removeTarget = function(target) { this._targets.splice(this._targets.indexOf(target), 1); };
function CustomEvent(cfg) { this._type = cfg.type;
this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._subscribers = []; } ! CustomEvent.prototype.fire = function(type, payload) { var e = new EventFacade(payload); e.currentTarget = e.target = this; this.fireEvents(e); this.fireDefautFn(e); }; bubbles
bubbles CustomEvent.prototype.fireEvent = function(e) { this._subscribers.forEach(function(subscriber)
{ subscriber.fn.call(context, e); }); if (this._bubbles) { this._targets.forEach(function(target) { e.currentTarget = target; target.fireEvent(e); }); } }; ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
FilterPanel:select Radio:select CheckBox:select Cascade:select
bubbles radio.addTarget(filterPanel); checkBox.addTarget(filterPanel); cascade.addTarget(filterPanel); ! filterPanel.on('select', doSomething);
! radio.fire('select'); checkBox.fire('select'); cascade.fire('select'); 均冒泡⾄至filterPanel
delegate? ManagerClass instance1 instance2 instance3 instance4 instance5 instance6 manager.delegate('select', 'instance',
doSomething);
delegate function EventTarget(prefix) { this._customEvents =
{}; this._targets = []; this._prefix = prefix; }
delegate radio.addTarget(filterPanel); checkBox.addTarget(filterPanel); cascade.addTarget(filterPanel); ! filterPanel.delegate(‘select', 'radio',
doSomething); ! radio.fire('select'); checkBox.fire('select'); cascade.fire('select'); 只有radio冒泡⾄至filterPanel
stop function EventFacade(payload) { this.prevented =
false; this.stopped = 0; merge(this, payload); } ! EventFacade.prototype.stopPropagation = function() { this.stopped = this.stopped == 0 ? 1 : this.stopped; }; ! EventFacade.prototype.stopImmediatePropagation = function() { this.stopped = 2; };
CustomEvent.prototype.fireEvent = function(e) { this._subscribers.forEach(function(subscriber) {
(e.stopped != 2) && subscriber.fn.call(context, e); }); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { if (e.stopped != 2) { e.currentTarget = target; target.fireEvent(e); } }); } }; ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
stop radio.addTarget(filterPanel); ! filterPanel.on('select', doSomething); radio.on('select', doOtherthing);
radio.on('select', function(e) { //e.stopPropagation(); e.stopImmediatePropagation(); }); ! radio.fire('select'); ⽴立刻停⽌止 停⽌止⽗父组件
defaultTargetOnly function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._defaultTargetOnly = cfg.defaultTargetOnly; this._subscribers = []; } ! CustomEvent.prototype.fireDefautFn = function(e) { if (!e.prevented || !this._cancelable) { this._defaultFn && this._defaultFn.call(this, e); if (this._bubbles && !e.stopped && !this._defaultTargetOnly) { this._targets.forEach(function(target) { target.fireDefautFn(e); }); } } };
defaultTargetOnly radio.publish({ bubbles: true,
defaultTargetOnly: true, defaultFn: radio._render }); ! filterPanel.publish({ defaultFn: filterPanel._render }); ! radio.render = function() { this.fire('render'); }; ! radio.addTarget(filterPanel); filterPanel.on('render', doSomething); ! radio.render(); 触发filterPanel的render事件 不会触发
# More Than DOM
after moment? ! var component = new EventTarget(cfg); !
component.publish('show', { defaultFn: function() { component.container.css('display', 'block'); } }); ! component.on('show', doSomething); ! component.fire('show'); 如果想在show完之后处理其他逻辑怎么办? component.after('show', doSomething); we need this!!!
after function CustomEvent(cfg) { this._type =
cfg.type; this._defaultFn = cfg.defaultFn; this._cancelable = cfg.cancelable; this._bubbles = cfg.bubbles; this._defaultTargetOnly = cfg.defaultTargetOnly; this._subscribers = []; this._afterSubscribers = []; }
CustomEvent.prototype.fire = function(type, payload) { var
e = new EventFacade(payload); e.currentTarget = e.target = this; this.fireEvents(e); this.fireDefautFn(e); this.fireEvents(e, 'after'); }; ! CustomEvent.prototype.fireEvent = function(e, when) { var subs = when == 'after' ? this._afterSubscribers : this._subscribers; if (when == 'after' && e.prevented) { return; } this._subscribers.forEach(function(subscriber) { (e.stopped != 2) && subscriber.fn.call(context, e); }); if (this._bubbles && !e.stopped) { this._targets.forEach(function(target) { if (e.stopped != 2) { e.currentTarget = target; target.fireEvent(e, when); } }); } };
instance event _defEventFn instance.on(event,handler) instance.after(event,handler) after
stateChange this.after('activeItemChange', this._afterActiveItemChange); this.after('resultsChange', this._afterResultsChange); this.after('visibleChange', this._afterVisibleChange); autocomplete/list.js
instance event _defEventFn instance.on(event,handler) instance.after(event,handler) stateChanged beforeStateChange change state
Features ✓ add/remove/fire ✓ default/bubbles ✓ stop/prevent ✓ delegate ✓
after
component component component component Events your app manager
Rethink how you use events
Rethink how you build your apps
None
# Q&A
further reading • http://www.nczonline.net/blog/2010/03/09/custom-events-in-javascript/ • https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent • http://yuilibrary.com/yui/docs/event-custom/ • http://msdn.microsoft.com/en-us/magazine/hh201955.aspx
• http://otaqui.com/blog/1374/event-emitter-pub-sub-or-deferred-promises-which- should-you-choose/ • http://coding.smashingmagazine.com/2013/11/12/an-introduction-to-dom-events/ • http://flightjs.github.io/ • http://www.youtube.com/watch?v=s_7VjN3qxe8