]>
Commit | Line | Data |
---|---|---|
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 | |
6 | Ext.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 | }); |