]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/mixin/ComponentDelegation.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / mixin / ComponentDelegation.js
CommitLineData
6527f429
DM
1/**\r
2 * A mixin that gives Ext.Component and Ext.Widget the ability to process the "delegate"\r
3 * event option.\r
4 * @private\r
5 */\r
6Ext.define('Ext.mixin.ComponentDelegation', {\r
7 extend: 'Ext.Mixin',\r
8 mixinConfig: {\r
9 id: 'componentDelegation'\r
10 },\r
11\r
12 privates: {\r
13 /**\r
14 * @private\r
15 * Adds a listeners with the "delegate" event option. Users should not invoke this\r
16 * method directly. Use the "delegate" event option of \r
17 * {@link Ext.util.Observable#addListener addListener} instead.\r
18 */\r
19 addDelegatedListener: function(eventName, fn, scope, options, order, caller, manager) {\r
20 var me = this,\r
21 delegatedEvents, event, priority;\r
22\r
23 // The following processing of the "order" option is typically done by the\r
24 // doAddListener method of Ext.mixin.Observable, but that method does not\r
25 // get called when adding a delegated listener, so we must do the conversion\r
26 // of order to priority here.\r
27 order = order || options.order;\r
28\r
29 if (order) {\r
30 priority = (options && options.priority);\r
31\r
32 if (!priority) { // priority option takes precedence over order\r
33 // do not mutate the user's options\r
34 options = options ? Ext.Object.chain(options) : {};\r
35 options.priority = me.$orderToPriority[order];\r
36 }\r
37 }\r
38\r
39 //<debug>\r
40 if (options.target) {\r
41 Ext.raise("Cannot add '" + eventName + "' listener to component: '"\r
42 + me.id + "' - 'delegate' and 'target' event options are incompatible.");\r
43 }\r
44 //</debug>\r
45\r
46 // Delegated events are tracked in a map keyed by event name, where the values\r
47 // are instances of Ext.util.Event that track all of the delegate listeners\r
48 // for the given event name.\r
49 delegatedEvents = me.$delegatedEvents || (me.$delegatedEvents = {});\r
50 event = delegatedEvents[eventName] ||\r
51 (delegatedEvents[eventName] = new Ext.util.Event(me, eventName));\r
52\r
53 if (event.addListener(fn, scope, options, caller, manager)) {\r
54 me.$hasDelegatedListeners._incr_(eventName);\r
55 }\r
56 },\r
57\r
58 /**\r
59 * @private\r
60 * Clears all listeners that were attached using the "delegate" event option.\r
61 * Users should not invoke this method directly. It is called automatically as\r
62 * part of normal {@link Ext.util.Observable#clearListeners clearListeners} \r
63 * processing.\r
64 */\r
65 clearDelegatedListeners: function() {\r
66 var me = this,\r
67 delegatedEvents = me.$delegatedEvents,\r
68 eventName, event, listenerCount;\r
69\r
70 if (delegatedEvents) {\r
71 for (eventName in delegatedEvents) {\r
72 event = delegatedEvents[eventName];\r
73 listenerCount = event.listeners.length;\r
74 event.clearListeners();\r
75 me.$hasDelegatedListeners._decr_(eventName, listenerCount);\r
76 delete delegatedEvents[eventName];\r
77 }\r
78 }\r
79 },\r
80\r
81 /**\r
82 * @private\r
83 * Fires a delegated event. Users should not invoke this method directly. It\r
84 * is called automatically by the framework as needed (see the "delegate" event\r
85 * option of {@link Ext.util.Observable#addListener addListener} for more \r
86 * details.\r
87 */\r
88 doFireDelegatedEvent: function(eventName, args) {\r
89 var me = this,\r
90 ret = true,\r
91 owner, delegatedEvents, event;\r
92\r
93 // NOTE: $hasDelegatedListeners exists on the prototype of this mixin\r
94 // which means it is inherited by both Ext.Component and Ext.Widget\r
95 // This means that if any Component in the universe is listening for\r
96 // the given eventName in a delegated manner, we need to traverse up the\r
97 // hierarchy to see if that Component is in fact our ancestor, and if so\r
98 // we need to fire the event on the ancestor.\r
99 if (me.$hasDelegatedListeners[eventName]) {\r
100 owner = me.getRefOwner();\r
101\r
102 while (owner) {\r
103 delegatedEvents = owner.$delegatedEvents;\r
104 if (delegatedEvents ) {\r
105 event = delegatedEvents[eventName];\r
106\r
107 if (event) {\r
108 ret = event.fireDelegated(me, args);\r
109\r
110 if (ret === false) {\r
111 break;\r
112 }\r
113 }\r
114 }\r
115\r
116 owner = owner.getRefOwner();\r
117 }\r
118 }\r
119\r
120 return ret;\r
121 },\r
122\r
123 /**\r
124 * @private\r
125 * Removes delegated listeners for a given eventName, function, and scope.\r
126 * Users should not invoke this method directly. It is called automatically by\r
127 * the framework as part of {@link #removeListener} processing.\r
128 */\r
129 removeDelegatedListener: function(eventName, fn, scope) {\r
130 var me = this,\r
131 delegatedEvents = me.$delegatedEvents,\r
132 event;\r
133\r
134 if (delegatedEvents ) {\r
135 event = delegatedEvents[eventName];\r
136 if (event && event.removeListener(fn, scope)) {\r
137 me.$hasDelegatedListeners._decr_(eventName);\r
138\r
139 if (event.listeners.length === 0) {\r
140 delete delegatedEvents[eventName];\r
141 }\r
142 }\r
143 }\r
144 }\r
145 },\r
146\r
147 onClassMixedIn: function(T) {\r
148 // When a Component listener is attached with the "delegate" option, it means\r
149 // All components anywhere in the hierarchy MUST now fire the event just in case\r
150 // the Component with the delegate listener is an ancestor of the component that\r
151 // fired the event (otherwise the ancestor will not have a chance to intercept\r
152 // and process the event - see doFireDelegatedEvent). To ensure that this happens\r
153 // we chain the class-level hasListeners object of Ext.Component and Ext.Widget\r
154 // to the single $hasDelegatedListeners object (see class-creation callback\r
155 // of this class for more info)\r
156 function HasListeners() {}\r
157 T.prototype.HasListeners = T.HasListeners = HasListeners;\r
158 HasListeners.prototype = T.hasListeners = new Ext.mixin.ComponentDelegation.HasDelegatedListeners();\r
159 }\r
160}, function(ComponentDelegation) {\r
161 // Here We set up a HasListeners instance ($hasDelegatedListeners) that will be incremented\r
162 // and decremented any time a Component or Widget adds or removes a listener using the\r
163 // "delegate" event option. This HasListeners instance is stored on the prototype\r
164 // of the ComponentDelegation mixin, and therefore will be applied to the prototype\r
165 // of both Ext.Component and Ext.Widget. This means that Ext.Widget and Ext.Component\r
166 // (intentionally) share the same $hasDelegatedListeners instance. To understand the\r
167 // reason for this common instance one must first understand how delegated events are\r
168 // fired:\r
169 //\r
170 // When any component or widget fires an event of any kind, it must call doFireDelegatedEvent\r
171 // to process possible delegated listeners. The implementation of doFireDelegatedEvent\r
172 // traverses up the component hierarchy searching for any ancestors that may be listening\r
173 // for the event in a delegated manner; however, this traversal of the hierarchy can\r
174 // be skipped if there are no known Components with delegated listeners for the given event.\r
175 // The $hasDelegatedListeners instance is used to track whether or not there are any\r
176 // delegated listeners for the given event name for this purpose. Since Ext.Widgets\r
177 // and Ext.Components can be part of the same hierarchy they must share the same\r
178 // $hasDelegatesListeners instance.\r
179 function HasDelegatedListeners() {}\r
180\r
181 ComponentDelegation.HasDelegatedListeners = HasDelegatedListeners;\r
182\r
183 HasDelegatedListeners.prototype = ComponentDelegation.prototype.$hasDelegatedListeners =\r
184 new Ext.mixin.Observable.HasListeners();\r
185});