|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('widget-uievents', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 * Support for Widget UI Events (Custom Events fired by the widget, which wrap the underlying DOM events - e.g. widget:click, widget:mousedown) |
|
12 * |
|
13 * @module widget |
|
14 * @submodule widget-uievents |
|
15 */ |
|
16 |
|
17 var BOUNDING_BOX = "boundingBox", |
|
18 Widget = Y.Widget, |
|
19 RENDER = "render", |
|
20 L = Y.Lang, |
|
21 EVENT_PREFIX_DELIMITER = ":", |
|
22 |
|
23 // Map of Node instances serving as a delegation containers for a specific |
|
24 // event type to Widget instances using that delegation container. |
|
25 _uievts = Y.Widget._uievts = Y.Widget._uievts || {}; |
|
26 |
|
27 Y.mix(Widget.prototype, { |
|
28 |
|
29 /** |
|
30 * Destructor logic for UI event infrastructure, |
|
31 * invoked during Widget destruction. |
|
32 * |
|
33 * @method _destroyUIEvents |
|
34 * @for Widget |
|
35 * @private |
|
36 */ |
|
37 _destroyUIEvents: function() { |
|
38 |
|
39 var widgetGuid = Y.stamp(this, true); |
|
40 |
|
41 Y.each(_uievts, function (info, key) { |
|
42 if (info.instances[widgetGuid]) { |
|
43 // Unregister this Widget instance as needing this delegated |
|
44 // event listener. |
|
45 delete info.instances[widgetGuid]; |
|
46 |
|
47 // There are no more Widget instances using this delegated |
|
48 // event listener, so detach it. |
|
49 |
|
50 if (Y.Object.isEmpty(info.instances)) { |
|
51 info.handle.detach(); |
|
52 |
|
53 if (_uievts[key]) { |
|
54 delete _uievts[key]; |
|
55 } |
|
56 } |
|
57 } |
|
58 }); |
|
59 }, |
|
60 |
|
61 /** |
|
62 * Map of DOM events that should be fired as Custom Events by the |
|
63 * Widget instance. |
|
64 * |
|
65 * @property UI_EVENTS |
|
66 * @for Widget |
|
67 * @type Object |
|
68 */ |
|
69 UI_EVENTS: Y.Node.DOM_EVENTS, |
|
70 |
|
71 /** |
|
72 * Returns the node on which to bind delegate listeners. |
|
73 * |
|
74 * @method _getUIEventNode |
|
75 * @for Widget |
|
76 * @protected |
|
77 */ |
|
78 _getUIEventNode: function () { |
|
79 return this.get(BOUNDING_BOX); |
|
80 }, |
|
81 |
|
82 /** |
|
83 * Binds a delegated DOM event listener of the specified type to the |
|
84 * Widget's outtermost DOM element to facilitate the firing of a Custom |
|
85 * Event of the same type for the Widget instance. |
|
86 * |
|
87 * @method _createUIEvent |
|
88 * @for Widget |
|
89 * @param type {String} String representing the name of the event |
|
90 * @private |
|
91 */ |
|
92 _createUIEvent: function (type) { |
|
93 |
|
94 var uiEvtNode = this._getUIEventNode(), |
|
95 key = (Y.stamp(uiEvtNode) + type), |
|
96 info = _uievts[key], |
|
97 handle; |
|
98 |
|
99 // For each Node instance: Ensure that there is only one delegated |
|
100 // event listener used to fire Widget UI events. |
|
101 |
|
102 if (!info) { |
|
103 |
|
104 handle = uiEvtNode.delegate(type, function (evt) { |
|
105 |
|
106 var widget = Widget.getByNode(this); |
|
107 |
|
108 // Widget could be null if node instance belongs to |
|
109 // another Y instance. |
|
110 |
|
111 if (widget) { |
|
112 if (widget._filterUIEvent(evt)) { |
|
113 widget.fire(evt.type, { domEvent: evt }); |
|
114 } |
|
115 } |
|
116 |
|
117 }, "." + Y.Widget.getClassName()); |
|
118 |
|
119 _uievts[key] = info = { instances: {}, handle: handle }; |
|
120 } |
|
121 |
|
122 // Register this Widget as using this Node as a delegation container. |
|
123 info.instances[Y.stamp(this)] = 1; |
|
124 }, |
|
125 |
|
126 /** |
|
127 * This method is used to determine if we should fire |
|
128 * the UI Event or not. The default implementation makes sure |
|
129 * that for nested delegates (nested unrelated widgets), we don't |
|
130 * fire the UI event listener more than once at each level. |
|
131 * |
|
132 * <p>For example, without the additional filter, if you have nested |
|
133 * widgets, each widget will have a delegate listener. If you |
|
134 * click on the inner widget, the inner delegate listener's |
|
135 * filter will match once, but the outer will match twice |
|
136 * (based on delegate's design) - once for the inner widget, |
|
137 * and once for the outer.</p> |
|
138 * |
|
139 * @method _filterUIEvent |
|
140 * @for Widget |
|
141 * @param {DOMEventFacade} evt |
|
142 * @return {boolean} true if it's OK to fire the custom UI event, false if not. |
|
143 * @private |
|
144 * |
|
145 */ |
|
146 _filterUIEvent: function(evt) { |
|
147 // Either it's hitting this widget's delegate container (and not some other widget's), |
|
148 // or the container it's hitting is handling this widget's ui events. |
|
149 return (evt.currentTarget.compareTo(evt.container) || evt.container.compareTo(this._getUIEventNode())); |
|
150 }, |
|
151 |
|
152 /** |
|
153 * Determines if the specified event is a UI event. |
|
154 * |
|
155 * @private |
|
156 * @method _isUIEvent |
|
157 * @for Widget |
|
158 * @param type {String} String representing the name of the event |
|
159 * @return {String} Event Returns the name of the UI Event, otherwise |
|
160 * undefined. |
|
161 */ |
|
162 _getUIEvent: function (type) { |
|
163 |
|
164 if (L.isString(type)) { |
|
165 var sType = this.parseType(type)[1], |
|
166 iDelim, |
|
167 returnVal; |
|
168 |
|
169 if (sType) { |
|
170 // TODO: Get delimiter from ET, or have ET support this. |
|
171 iDelim = sType.indexOf(EVENT_PREFIX_DELIMITER); |
|
172 if (iDelim > -1) { |
|
173 sType = sType.substring(iDelim + EVENT_PREFIX_DELIMITER.length); |
|
174 } |
|
175 |
|
176 if (this.UI_EVENTS[sType]) { |
|
177 returnVal = sType; |
|
178 } |
|
179 } |
|
180 |
|
181 return returnVal; |
|
182 } |
|
183 }, |
|
184 |
|
185 /** |
|
186 * Sets up infrastructure required to fire a UI event. |
|
187 * |
|
188 * @private |
|
189 * @method _initUIEvent |
|
190 * @for Widget |
|
191 * @param type {String} String representing the name of the event |
|
192 * @return {String} |
|
193 */ |
|
194 _initUIEvent: function (type) { |
|
195 var sType = this._getUIEvent(type), |
|
196 queue = this._uiEvtsInitQueue || {}; |
|
197 |
|
198 if (sType && !queue[sType]) { |
|
199 |
|
200 this._uiEvtsInitQueue = queue[sType] = 1; |
|
201 |
|
202 this.after(RENDER, function() { |
|
203 this._createUIEvent(sType); |
|
204 delete this._uiEvtsInitQueue[sType]; |
|
205 }); |
|
206 } |
|
207 }, |
|
208 |
|
209 // Override of "on" from Base to facilitate the firing of Widget events |
|
210 // based on DOM events of the same name/type (e.g. "click", "mouseover"). |
|
211 // Temporary solution until we have the ability to listen to when |
|
212 // someone adds an event listener (bug 2528230) |
|
213 on: function (type) { |
|
214 this._initUIEvent(type); |
|
215 return Widget.superclass.on.apply(this, arguments); |
|
216 }, |
|
217 |
|
218 // Override of "publish" from Base to facilitate the firing of Widget events |
|
219 // based on DOM events of the same name/type (e.g. "click", "mouseover"). |
|
220 // Temporary solution until we have the ability to listen to when |
|
221 // someone publishes an event (bug 2528230) |
|
222 publish: function (type, config) { |
|
223 var sType = this._getUIEvent(type); |
|
224 if (sType && config && config.defaultFn) { |
|
225 this._initUIEvent(sType); |
|
226 } |
|
227 return Widget.superclass.publish.apply(this, arguments); |
|
228 } |
|
229 |
|
230 }, true); // overwrite existing EventTarget methods |
|
231 |
|
232 |
|
233 }, '3.10.3', {"requires": ["node-event-delegate", "widget-base"]}); |