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
130
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Events
How to build a custom event system
ningzbruc
March 19, 2014
More Decks by ningzbruc
See All by ningzbruc
如何写出一个优秀的开源库
ningzbruc
0
66
去啊无线前端的一天
ningzbruc
1
190
React & Component
ningzbruc
0
45
阿里旅行去啊H5首页总结&Promise
ningzbruc
0
270
KISSY.Base - all about that Base
ningzbruc
0
140
Hammer.js
ningzbruc
1
340
淘宝旅行门票iPad版开发
ningzbruc
0
140
Travel on KISSY mini
ningzbruc
0
210
Why YUI3
ningzbruc
0
190
Other Decks in Programming
See All in Programming
AI時代のPHPer生存戦略 ~「言語、もうなんでもよくない?」に本気で向き合う~
vivion
0
180
継続モナドとリアクティブプログラミング
yukikurage
3
650
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
190
変わらないものが、変わるものを決める — 意図駆動開発 × イベントソーシング × イミュータブル | What Doesn't Change Decides What Can — IDD × Event Sourcing × Immutability
tomohisa
0
550
【やさしく解説 設計編 #1】「ドメイン駆動」と「実装駆動」ってなに? 〜設計の考え方を、たとえ話で学ぼう〜
panda728
PRO
1
140
人間の目はかわらない、だからJPEGは30年もつ
yuzneri
12
16k
作るコストが小さくなった時代 幸せに働くために改めて考えたいこと 〜エンジニアとして価値を出し続けるために注視している二分野〜
yuppeeng
0
130
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
0
510
AIエージェントで 変わるAndroid開発環境
takahirom
2
730
【やさしく解説 設計編・中級 #6】良いアーキテクチャとは ~ 一本の登り道の、行き先 ~
panda728
PRO
0
180
今さら聞けない .NET CLI
htkym
0
140
生成AI導入の「期待外れ」を乗り越える ー 開発フロー改革が目指す、真の組織変革
starfish719
0
2.5k
Featured
See All Featured
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
41k
Scaling GitHub
holman
464
140k
Practical Orchestrator
shlominoach
191
11k
The Cost Of JavaScript in 2023
addyosmani
55
10k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
230
30 Presentation Tips
portentint
PRO
1
350
The SEO Collaboration Effect
kristinabergwall1
1
510
Information Architects: The Missing Link in Design Systems
soysaucechin
0
1k
Raft: Consensus for Rubyists
vanstee
141
7.6k
Navigating Team Friction
lara
192
16k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
920
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