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