]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * Ext.Widget is a light-weight Component that consists of nothing more than a template\r | |
3 | * Element that can be cloned to quickly and efficiently replicate many instances.\r | |
4 | * Ext.Widget is typically not instantiated directly, because the default template is\r | |
5 | * just a single element with no listeners. Instead Ext.Widget should be extended to\r | |
6 | * create Widgets that have a useful markup structure and event listeners.\r | |
7 | *\r | |
8 | * For example:\r | |
9 | *\r | |
10 | * Ext.define('MyWidget', {\r | |
11 | * extend: 'Ext.Widget',\r | |
12 | *\r | |
13 | * // The element template passed to Ext.Element.create()\r | |
14 | * element: {\r | |
15 | * reference: 'element',\r | |
16 | * listeners: {\r | |
17 | * click: 'onClick'\r | |
18 | * },\r | |
19 | * children: [{\r | |
20 | * reference: 'innerElement',\r | |
21 | * listeners: {\r | |
22 | * click: 'onInnerClick'\r | |
23 | * }\r | |
24 | * }]\r | |
25 | * },\r | |
26 | *\r | |
27 | * constructor: function(config) {\r | |
28 | * // It is important to remember to call the Widget superclass constructor\r | |
29 | * // when overriding the constructor in a derived class. This ensures that\r | |
30 | * // the element is initialized from the template, and that initConfig() is\r | |
31 | * // is called.\r | |
32 | * this.callParent([config]);\r | |
33 | *\r | |
34 | * // After calling the superclass constructor, the Element is available and\r | |
35 | * // can safely be manipulated. Reference Elements are instances of\r | |
36 | * // Ext.Element, and are cached on each Widget instance by reference name.\r | |
37 | * Ext.getBody().appendChild(this.element);\r | |
38 | * },\r | |
39 | *\r | |
40 | * onClick: function() {\r | |
41 | * // listeners use this Widget instance as their scope\r | |
42 | * console.log('element clicked', this);\r | |
43 | * },\r | |
44 | *\r | |
45 | * onInnerClick: function() {\r | |
46 | * // access the innerElement reference by name\r | |
47 | * console.log('inner element clicked', this.innerElement);\r | |
48 | * }\r | |
49 | * });\r | |
50 | *\r | |
51 | * @since 5.0.0\r | |
52 | */\r | |
53 | Ext.define('Ext.Widget', {\r | |
54 | extend: 'Ext.Evented',\r | |
55 | xtype: 'widget',\r | |
56 | \r | |
57 | requires: [\r | |
58 | 'Ext.dom.Element'\r | |
59 | ],\r | |
60 | \r | |
61 | mixins: [\r | |
62 | 'Ext.mixin.Inheritable',\r | |
63 | 'Ext.mixin.Bindable',\r | |
64 | 'Ext.mixin.ComponentDelegation'\r | |
65 | ],\r | |
66 | \r | |
67 | isWidget: true,\r | |
68 | \r | |
69 | /**\r | |
70 | * @property {Object} element\r | |
71 | * A configuration object for Ext.Element.create() that is used to create the Element\r | |
72 | * template. Supports all the standard options of a Ext.Element.create() config and\r | |
73 | * adds 2 additional options:\r | |
74 | *\r | |
75 | * 1. `reference` - this option specifies a name for Element references. These\r | |
76 | * references names become properties of the Widget instance and refer to Ext.Element\r | |
77 | * instances that were created using the template:\r | |
78 | *\r | |
79 | * element: {\r | |
80 | * reference: 'element',\r | |
81 | * children: [{\r | |
82 | * reference: 'innerElement'\r | |
83 | * }]\r | |
84 | * }\r | |
85 | *\r | |
86 | * After construction of a widget the reference elements are accessible as follows:\r | |
87 | *\r | |
88 | * var foo = new FooWidget(),\r | |
89 | * innerEl = foo.innerEl; // an Ext.Element that wraps the innerElement\r | |
90 | *\r | |
91 | * The reference attribute is optional, but all Widgets must have a `'element'`\r | |
92 | * reference on some element within the template (usually the outermost one).\r | |
93 | *\r | |
94 | * 2. `listeners` - a standard listeners object as specified by {@link\r | |
95 | * Ext.mixin.Observable}.\r | |
96 | *\r | |
97 | * element: {\r | |
98 | * reference: 'element',\r | |
99 | * listeners: {\r | |
100 | * click: 'onClick'\r | |
101 | * },\r | |
102 | * children: [{\r | |
103 | * reference: 'innerElement',\r | |
104 | * listeners: {\r | |
105 | * click: 'onInnerClick'\r | |
106 | * }\r | |
107 | * }]\r | |
108 | * }\r | |
109 | *\r | |
110 | * Since listeners cannot be attached without an Ext.Element reference the `reference`\r | |
111 | * property MUST be specified in order to use `listeners`.\r | |
112 | *\r | |
113 | * The Widget instance is used as the scope for all listeners specified in this way,\r | |
114 | * so it is invalid to use the `scope` option in the `listeners` config since it will\r | |
115 | * always be overwritten using `this`.\r | |
116 | * @protected\r | |
117 | */\r | |
118 | element: {\r | |
119 | reference: 'element'\r | |
120 | },\r | |
121 | \r | |
122 | observableType: 'component',\r | |
123 | \r | |
124 | cachedConfig: {\r | |
125 | /**\r | |
126 | * @cfg {String/Object} style\r | |
127 | * Additional CSS styles that will be rendered into an inline style attribute when\r | |
128 | * the widget is rendered.\r | |
129 | *\r | |
130 | * You can pass either a string syntax:\r | |
131 | *\r | |
132 | * style: 'background:red'\r | |
133 | *\r | |
134 | * Or by using an object:\r | |
135 | *\r | |
136 | * style: {\r | |
137 | * background: 'red'\r | |
138 | * }\r | |
139 | *\r | |
140 | * When using the object syntax, you can define CSS Properties by using a string:\r | |
141 | *\r | |
142 | * style: {\r | |
143 | * 'border-left': '1px solid red'\r | |
144 | * }\r | |
145 | *\r | |
146 | * Although the object syntax is much easier to read, we suggest you to use the\r | |
147 | * string syntax for better performance.\r | |
148 | * @accessor\r | |
149 | */\r | |
150 | style: null\r | |
151 | },\r | |
152 | \r | |
153 | config: {\r | |
154 | /**\r | |
155 | * @cfg {String/String[]} userCls\r | |
156 | * One or more CSS classes to add to the component's primary element. This config\r | |
157 | * is intended solely for use by the component instantiator (the "user"), not by\r | |
158 | * derived classes.\r | |
159 | *\r | |
160 | * For example:\r | |
161 | *\r | |
162 | * items: [{\r | |
163 | * xtype: 'button',\r | |
164 | * userCls: 'my-button'\r | |
165 | * ...\r | |
166 | * }]\r | |
167 | */\r | |
168 | userCls: null\r | |
169 | },\r | |
170 | \r | |
171 | eventedConfig: {\r | |
172 | /**\r | |
173 | * @cfg {Number/String} width\r | |
174 | * The width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.\r | |
175 | * By default, if this is not explicitly set, this Component's element will simply have its own natural size.\r | |
176 | * If set to `auto`, it will set the width to `null` meaning it will have its own natural size.\r | |
177 | * @accessor\r | |
178 | * @evented\r | |
179 | */\r | |
180 | width: null,\r | |
181 | \r | |
182 | /**\r | |
183 | * @cfg {Number/String} height\r | |
184 | * The height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.\r | |
185 | * By default, if this is not explicitly set, this Component's element will simply have its own natural size.\r | |
186 | * If set to `auto`, it will set the width to `null` meaning it will have its own natural size.\r | |
187 | * @accessor\r | |
188 | * @evented\r | |
189 | */\r | |
190 | height: null\r | |
191 | },\r | |
192 | \r | |
193 | /**\r | |
194 | * @property {Array} template\r | |
195 | * An array of child elements to use as the children of the main element in the {@link\r | |
196 | * #element} template. Only used if "children" are not specified explicitly in the\r | |
197 | * {@link #element} template.\r | |
198 | * @protected\r | |
199 | */\r | |
200 | template: [],\r | |
201 | \r | |
202 | constructor: function(config) {\r | |
203 | var me = this,\r | |
204 | controller;\r | |
205 | \r | |
206 | me.initId(config);\r | |
207 | me.initElement();\r | |
208 | me.mixins.observable.constructor.call(me, config);\r | |
209 | Ext.ComponentManager.register(me);\r | |
210 | \r | |
211 | controller = me.getController();\r | |
212 | if (controller) {\r | |
213 | controller.init(me);\r | |
214 | }\r | |
215 | },\r | |
216 | \r | |
217 | afterCachedConfig: function() {\r | |
218 | // This method runs once for the first instance of this Widget type that is\r | |
219 | // created. It runs after the element config has been processed for the first\r | |
220 | // instance, and after all the cachedConfigs (whose appliers/updaters may modify\r | |
221 | // the element) have been initialized. At this point we are ready to take the\r | |
222 | // DOM that was generated for the first Element instance, clone it, and cache it\r | |
223 | // on the prototype, so that it can be cloned by future instance to create their\r | |
224 | // elements (see initElement).\r | |
225 | var me = this,\r | |
226 | prototype = me.self.prototype,\r | |
227 | referenceList = me.referenceList,\r | |
228 | renderElement = me.renderElement,\r | |
229 | renderTemplate, element, i, ln, reference, elements;\r | |
230 | \r | |
231 | // This is where we take the first instance's DOM and clone it as the template\r | |
232 | // for future instances\r | |
233 | prototype.renderTemplate = renderTemplate = document.createDocumentFragment();\r | |
234 | renderTemplate.appendChild(renderElement.clone(true, true));\r | |
235 | \r | |
236 | elements = renderTemplate.querySelectorAll('[id]');\r | |
237 | \r | |
238 | for (i = 0,ln = elements.length; i < ln; i++) {\r | |
239 | element = elements[i];\r | |
240 | element.removeAttribute('id');\r | |
241 | }\r | |
242 | \r | |
243 | // initElement skips removal of reference attributes for the first instance so that\r | |
244 | // the reference attributes will be present in the cached element when it is cloned.\r | |
245 | // Now that we're done cloning and caching the template element, it is safe to\r | |
246 | // remove the reference attributes from this instance's elements\r | |
247 | for (i = 0,ln = referenceList.length; i < ln; i++) {\r | |
248 | reference = referenceList[i];\r | |
249 | me[reference].dom.removeAttribute('reference');\r | |
250 | }\r | |
251 | },\r | |
252 | \r | |
253 | addCls: function(cls) {\r | |
254 | this.el.addCls(cls);\r | |
255 | },\r | |
256 | \r | |
257 | applyWidth: function(width) {\r | |
258 | return this.filterLengthValue(width);\r | |
259 | },\r | |
260 | \r | |
261 | applyHeight: function(height) {\r | |
262 | return this.filterLengthValue(height);\r | |
263 | },\r | |
264 | \r | |
265 | clearListeners: function() {\r | |
266 | var me = this;\r | |
267 | me.mixins.observable.clearListeners.call(me);\r | |
268 | me.mixins.componentDelegation.clearDelegatedListeners.call(me);\r | |
269 | },\r | |
270 | \r | |
271 | destroy: function() {\r | |
272 | var me = this,\r | |
273 | referenceList = me.referenceList,\r | |
274 | i, ln, reference;\r | |
275 | \r | |
276 | // Destroy all element references\r | |
277 | for (i = 0, ln = referenceList.length; i < ln; i++) {\r | |
278 | reference = referenceList[i];\r | |
279 | if (me.hasOwnProperty(reference)) {\r | |
280 | me[reference].destroy();\r | |
281 | me[reference] = null;\r | |
282 | }\r | |
283 | }\r | |
284 | \r | |
285 | me.destroyBindable();\r | |
286 | \r | |
287 | me.callParent();\r | |
288 | \r | |
289 | Ext.ComponentManager.unregister(me);\r | |
290 | },\r | |
291 | \r | |
292 | doFireEvent: function(eventName, args, bubbles) {\r | |
293 | var me = this,\r | |
294 | ret = me.mixins.observable.doFireEvent.call(me, eventName, args, bubbles);\r | |
295 | \r | |
296 | if (ret !== false) {\r | |
297 | ret = me.mixins.componentDelegation.doFireDelegatedEvent.call(me, eventName, args);\r | |
298 | }\r | |
299 | \r | |
300 | return ret;\r | |
301 | },\r | |
302 | \r | |
303 | /**\r | |
304 | * A template method for modifying the {@link #element} config before it is processed.\r | |
305 | * By default adds the result of `this.getTemplate()` as the `children` array of \r | |
306 | * {@link #element} if `children` were not specified in the original \r | |
307 | * {@link #element} config. Typically this method should not need to be implemented \r | |
308 | * in subclasses. Instead the {@link #element} property should be use to configure \r | |
309 | * the element template for a given Widget subclass.\r | |
310 | *\r | |
311 | * This method is called once when the first instance of each Widget subclass is\r | |
312 | * created. The element config object that is returned is cached and used as the template\r | |
313 | * for all successive instances. The scope object for this method is the class prototype,\r | |
314 | * not the instance.\r | |
315 | *\r | |
316 | * @return {Object} the element config object\r | |
317 | * @protected\r | |
318 | */\r | |
319 | getElementConfig: function() {\r | |
320 | var me = this,\r | |
321 | el = me.element;\r | |
322 | \r | |
323 | if (!('children' in el)) {\r | |
324 | el = Ext.apply({\r | |
325 | children: me.getTemplate()\r | |
326 | }, el);\r | |
327 | }\r | |
328 | \r | |
329 | return el;\r | |
330 | },\r | |
331 | \r | |
332 | /**\r | |
333 | * Returns the height and width of the Component.\r | |
334 | * @return {Object} The current `height` and `width` of the Component.\r | |
335 | * @return {Number} return.width\r | |
336 | * @return {Number} return.height\r | |
337 | */\r | |
338 | getSize: function() {\r | |
339 | return {\r | |
340 | width: this.getWidth(),\r | |
341 | height: this.getHeight()\r | |
342 | };\r | |
343 | },\r | |
344 | \r | |
345 | getTemplate: function() {\r | |
346 | return this.template;\r | |
347 | },\r | |
348 | \r | |
349 | /**\r | |
350 | * Initializes the Element for this Widget instance. If this is the first time a\r | |
351 | * Widget of this type has been instantiated the {@link #element} config will be\r | |
352 | * processed to create an Element. This Element is then cached on the prototype (see\r | |
353 | * afterCachedConfig) so that future instances can obtain their element by simply\r | |
354 | * cloning the Element that was cached by the first instance.\r | |
355 | * @protected\r | |
356 | */\r | |
357 | initElement: function() {\r | |
358 | var me = this,\r | |
359 | prototype = me.self.prototype,\r | |
360 | id = me.getId(),\r | |
361 | // The double assignment is intentional to workaround a JIT issue that prevents\r | |
362 | // me.referenceList from being assigned in random scenarios. The issue occurs on 4th gen \r | |
363 | // iPads and lower, possibly other older iOS devices. See EXTJS-16494.\r | |
364 | referenceList = me.referenceList = me.referenceList = [],\r | |
365 | cleanAttributes = true,\r | |
366 | renderTemplate, renderElement, element, referenceNodes, i, ln, referenceNode,\r | |
367 | reference;\r | |
368 | \r | |
369 | if (prototype.hasOwnProperty('renderTemplate')) {\r | |
370 | // we have already created an instance of this Widget type, so the element\r | |
371 | // config has already been processed, and the resulting DOM has been cached on\r | |
372 | // the prototype (see afterCachedConfig). This means we can obtain our element\r | |
373 | // by simply cloning the cached element.\r | |
374 | renderTemplate = me.renderTemplate.cloneNode(true);\r | |
375 | renderElement = renderTemplate.firstChild;\r | |
376 | } else {\r | |
377 | // this is the first instantiation of this widget type. Process the element\r | |
378 | // config from scratch to create our Element.\r | |
379 | cleanAttributes = false;\r | |
380 | renderTemplate = document.createDocumentFragment();\r | |
381 | renderElement = Ext.Element.create(me.processElementConfig.call(prototype), true);\r | |
382 | renderTemplate.appendChild(renderElement);\r | |
383 | }\r | |
384 | \r | |
385 | referenceNodes = renderTemplate.querySelectorAll('[reference]');\r | |
386 | \r | |
387 | for (i = 0,ln = referenceNodes.length; i < ln; i++) {\r | |
388 | referenceNode = referenceNodes[i];\r | |
389 | reference = referenceNode.getAttribute('reference');\r | |
390 | \r | |
391 | if (cleanAttributes) {\r | |
392 | // on first instantiation we do not clean the reference attributes here.\r | |
393 | // This is because this instance's element will be used as the template\r | |
394 | // for future instances, and we need the reference attributes to be\r | |
395 | // present in the template so that future instances can resolve their\r | |
396 | // references. afterCachedConfig is responsible for removing the\r | |
397 | // reference attributes from the DOM for the first instance after the\r | |
398 | // Element has been cloned and cached as the template.\r | |
399 | referenceNode.removeAttribute('reference');\r | |
400 | }\r | |
401 | \r | |
402 | if (reference === 'element') {\r | |
403 | //<debug>\r | |
404 | if (element) {\r | |
405 | // already resolved a reference named element - can't have two\r | |
406 | Ext.raise("Duplicate 'element' reference detected in '" +\r | |
407 | me.$className + "' template.");\r | |
408 | }\r | |
409 | //</debug>\r | |
410 | referenceNode.id = id;\r | |
411 | // element reference needs to be established ASAP, so add the reference\r | |
412 | // immediately, not "on-demand"\r | |
413 | element = me.el = me.addElementReference(reference, referenceNode);\r | |
414 | \r | |
415 | // Poke our id in our magic attribute to enable Component#fromElement\r | |
416 | element.dom.setAttribute('data-componentid', id);\r | |
417 | } else {\r | |
418 | me.addElementReferenceOnDemand(reference, referenceNode);\r | |
419 | }\r | |
420 | \r | |
421 | referenceList.push(reference);\r | |
422 | }\r | |
423 | \r | |
424 | //<debug>\r | |
425 | if (!element) {\r | |
426 | Ext.raise("No 'element' reference found in '" + me.$className +\r | |
427 | "' template.");\r | |
428 | }\r | |
429 | //</debug>\r | |
430 | \r | |
431 | if (renderElement === element.dom) {\r | |
432 | me.renderElement = element;\r | |
433 | }\r | |
434 | else {\r | |
435 | me.addElementReferenceOnDemand('renderElement', renderElement);\r | |
436 | }\r | |
437 | },\r | |
438 | \r | |
439 | /**\r | |
440 | * Tests whether this Widget matches a {@link Ext.ComponentQuery ComponentQuery}\r | |
441 | * selector string.\r | |
442 | * @param {String} selector The selector string to test against.\r | |
443 | * @return {Boolean} `true` if this Widget matches the selector.\r | |
444 | */\r | |
445 | is: function(selector) {\r | |
446 | return Ext.ComponentQuery.is(this, selector);\r | |
447 | },\r | |
448 | \r | |
449 | /**\r | |
450 | * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended\r | |
451 | * from the xtype (default) or whether it is directly of the xtype specified (`shallow = true`).\r | |
452 | * **If using your own subclasses, be aware that a Component must register its own xtype\r | |
453 | * to participate in determination of inherited xtypes.__\r | |
454 | *\r | |
455 | * For a list of all available xtypes, see the {@link Ext.Component} header.\r | |
456 | *\r | |
457 | * Example usage:\r | |
458 | *\r | |
459 | * var t = new Ext.field.Text();\r | |
460 | * var isText = t.isXType('textfield'); // true\r | |
461 | * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.field.Field\r | |
462 | * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.field.Field instance\r | |
463 | *\r | |
464 | * @param {String} xtype The xtype to check for this Component.\r | |
465 | * @param {Boolean} shallow (optional) `false` to check whether this Component is descended from the xtype (this is\r | |
466 | * the default), or `true` to check whether this Component is directly of the specified xtype.\r | |
467 | * @return {Boolean} `true` if this component descends from the specified xtype, `false` otherwise.\r | |
468 | */\r | |
469 | isXType: function(xtype, shallow) {\r | |
470 | return shallow ? (Ext.Array.indexOf(this.xtypes, xtype) !== -1) :\r | |
471 | !!this.xtypesMap[xtype];\r | |
472 | },\r | |
473 | \r | |
474 | removeCls: function(cls) {\r | |
475 | this.el.removeCls(cls);\r | |
476 | },\r | |
477 | \r | |
478 | /**\r | |
479 | * Toggles the specified CSS class on this element (removes it if it already exists,\r | |
480 | * otherwise adds it).\r | |
481 | * @param {String} className The CSS class to toggle.\r | |
482 | * @param {Boolean} [state] If specified as `true`, causes the class to be added. If\r | |
483 | * specified as `false`, causes the class to be removed.\r | |
484 | */\r | |
485 | toggleCls: function (cls, state) {\r | |
486 | this.element.toggleCls(cls,state);\r | |
487 | },\r | |
488 | \r | |
489 | resolveListenerScope: function(defaultScope, skipThis) {\r | |
490 | // break the tie between Observable and Inheritable resolveListenerScope\r | |
491 | return this.mixins.inheritable.resolveListenerScope.call(this, defaultScope, skipThis);\r | |
492 | },\r | |
493 | \r | |
494 | /**\r | |
495 | * Sets the size of the Component.\r | |
496 | * @param {Number} width The new width for the Component.\r | |
497 | * @param {Number} height The new height for the Component.\r | |
498 | */\r | |
499 | setSize: function(width, height) {\r | |
500 | if (width !== undefined) {\r | |
501 | this.setWidth(width);\r | |
502 | }\r | |
503 | if (height !== undefined) {\r | |
504 | this.setHeight(height);\r | |
505 | }\r | |
506 | },\r | |
507 | \r | |
508 | /**\r | |
509 | * @protected\r | |
510 | */\r | |
511 | applyStyle: function(style, oldStyle) {\r | |
512 | // If we're doing something with data binding, say:\r | |
513 | // style: {\r | |
514 | // backgroundColor: 'rgba({r}, {g}, {b}, 1)'\r | |
515 | // }\r | |
516 | // The inner values will change, but the object won't, so force\r | |
517 | // a copy to be created here if necessary\r | |
518 | if (oldStyle && style === oldStyle && Ext.isObject(oldStyle)) {\r | |
519 | style = Ext.apply({}, style);\r | |
520 | }\r | |
521 | return style;\r | |
522 | },\r | |
523 | \r | |
524 | /**\r | |
525 | * @protected\r | |
526 | */\r | |
527 | updateStyle: function(style) {\r | |
528 | this.element.applyStyles(style);\r | |
529 | },\r | |
530 | \r | |
531 | /**\r | |
532 | * @param width\r | |
533 | * @protected\r | |
534 | */\r | |
535 | updateWidth: function(width) {\r | |
536 | this.element.setWidth(width);\r | |
537 | },\r | |
538 | \r | |
539 | /**\r | |
540 | * @param height\r | |
541 | * @protected\r | |
542 | */\r | |
543 | updateHeight: function(height) {\r | |
544 | this.element.setHeight(height);\r | |
545 | },\r | |
546 | \r | |
547 | // Temporary workarounds to keep Ext.ComponentManager from throwing errors when dealing\r | |
548 | // Widgets. TODO: remove these emptyFns when proper focus handling is implmented\r | |
549 | onFocusEnter: Ext.emptyFn,\r | |
550 | onFocusLeave: Ext.emptyFn,\r | |
551 | isAncestor: function () { return false; },\r | |
552 | \r | |
553 | //-------------------------------------------------------------------------\r | |
554 | \r | |
555 | privates: {\r | |
556 | /**\r | |
557 | * Reduces instantiation time for a Widget by lazily instantiating Ext.Element\r | |
558 | * references the first time they are used. This optimization only works for elements\r | |
559 | * with no listeners specified.\r | |
560 | *\r | |
561 | * @param {String} name The name of the reference\r | |
562 | * @param {HTMLElement} domNode\r | |
563 | * @private\r | |
564 | */\r | |
565 | addElementReferenceOnDemand: function(name, domNode) {\r | |
566 | if (this._elementListeners[name]) {\r | |
567 | // if the element was configured with listeners then we cannot add the\r | |
568 | // reference on demand because we need to make sure the element responds\r | |
569 | // immediately to any events, even if its reference is never accessed\r | |
570 | this.addElementReference(name, domNode);\r | |
571 | } else {\r | |
572 | // no listeners - element reference can be resolved on demand.\r | |
573 | // TODO: measure if this has any significant performance impact.\r | |
574 | Ext.Object.defineProperty(this, name, {\r | |
575 | get: function() {\r | |
576 | // remove the property that was defined using defineProperty because\r | |
577 | // addElementReference will set the property on the instance, - the\r | |
578 | // getter is not needed after the first access.\r | |
579 | delete this[name];\r | |
580 | return this.addElementReference(name, domNode);\r | |
581 | },\r | |
582 | configurable: true\r | |
583 | });\r | |
584 | }\r | |
585 | },\r | |
586 | \r | |
587 | /**\r | |
588 | * Adds an element reference to this Widget instance.\r | |
589 | * @param {String} name The name of the reference\r | |
590 | * @param {HTMLElement} domNode\r | |
591 | * @return {Ext.dom.Element}\r | |
592 | * @private\r | |
593 | */\r | |
594 | addElementReference: function (name, domNode) {\r | |
595 | var me = this,\r | |
596 | referenceEl = me[name] = Ext.get(domNode),\r | |
597 | listeners = me._elementListeners[name],\r | |
598 | eventName, listener;\r | |
599 | \r | |
600 | referenceEl.skipGarbageCollection = true;\r | |
601 | referenceEl.component = me;\r | |
602 | \r | |
603 | if (listeners) {\r | |
604 | // TODO: These references will be needed when we use delegation to listen\r | |
605 | // for element events, but for now, we'll just attach the listeners directly\r | |
606 | // referenceEl.reference = name;\r | |
607 | // referenceEl.component = me;\r | |
608 | // referenceEl.listeners = listeners;\r | |
609 | \r | |
610 | // At this point "listeners" exists on the class prototype. We need to clone\r | |
611 | // it before poking the scope reference onto it, because it is used as the\r | |
612 | // options object by Observable and so can't be safely shared.\r | |
613 | //\r | |
614 | listeners = Ext.clone(listeners);\r | |
615 | \r | |
616 | // If the listener is specified as an object it needs to have the scope\r | |
617 | // option added to that object, for example:\r | |
618 | //\r | |
619 | // {\r | |
620 | // click: {\r | |
621 | // fn: 'onClick',\r | |
622 | // scope: this\r | |
623 | // }\r | |
624 | // }\r | |
625 | //\r | |
626 | for (eventName in listeners) {\r | |
627 | listener = listeners[eventName];\r | |
628 | if (typeof listener === 'object') {\r | |
629 | listener.scope = me;\r | |
630 | }\r | |
631 | }\r | |
632 | \r | |
633 | // The outermost listeners object always needs the scope option. This covers\r | |
634 | // a listeners object with the following shape:\r | |
635 | //\r | |
636 | // {\r | |
637 | // click: 'onClick'\r | |
638 | // scope: this\r | |
639 | // }\r | |
640 | //\r | |
641 | listeners.scope = me; // do this *after* the above loop over listeners\r | |
642 | \r | |
643 | // Hopefully in the future we can stop calling on() here, and just use\r | |
644 | // event delegation to dispatch events to Widgets that have declared their\r | |
645 | // listeners in their template.\r | |
646 | //\r | |
647 | referenceEl.on(listeners);\r | |
648 | }\r | |
649 | \r | |
650 | return referenceEl;\r | |
651 | },\r | |
652 | \r | |
653 | detachFromBody: function() {\r | |
654 | // See reattachToBody\r | |
655 | Ext.getDetachedBody().appendChild(this.element);\r | |
656 | this.isDetached = true;\r | |
657 | },\r | |
658 | \r | |
659 | /**\r | |
660 | * @private\r | |
661 | */\r | |
662 | doAddListener: function(name, fn, scope, options, order, caller, manager) {\r | |
663 | var me = this,\r | |
664 | delegate;\r | |
665 | \r | |
666 | if (options && 'element' in options) {\r | |
667 | //<debug>\r | |
668 | if (me.referenceList.indexOf(options.element) === -1) {\r | |
669 | Ext.Logger.error("Adding event listener with an invalid element reference of '" + options.element +\r | |
670 | "' for this component. Available values are: '" + me.referenceList.join("', '") + "'", me);\r | |
671 | }\r | |
672 | //</debug>\r | |
673 | \r | |
674 | // The default scope is this component\r | |
675 | me[options.element].doAddListener(name, fn, scope || me, options, order);\r | |
676 | }\r | |
677 | \r | |
678 | if (options) {\r | |
679 | delegate = options.delegate;\r | |
680 | if (delegate) {\r | |
681 | me.mixins.componentDelegation.addDelegatedListener.call(me, name, fn, scope, options, order, caller, manager);\r | |
682 | return;\r | |
683 | }\r | |
684 | }\r | |
685 | \r | |
686 | me.callParent([name, fn, scope, options, order, caller, manager]);\r | |
687 | },\r | |
688 | \r | |
689 | doRemoveListener: function(eventName, fn, scope) {\r | |
690 | var me = this;\r | |
691 | me.mixins.observable.doRemoveListener.call(me, eventName, fn, scope);\r | |
692 | me.mixins.componentDelegation.removeDelegatedListener.call(me, eventName, fn, scope);\r | |
693 | },\r | |
694 | \r | |
695 | filterLengthValue: function(value) {\r | |
696 | if (value === 'auto' || (!value && value !== 0)) {\r | |
697 | return null;\r | |
698 | }\r | |
699 | \r | |
700 | return value;\r | |
701 | },\r | |
702 | \r | |
703 | getFocusEl: function () {\r | |
704 | return this.element;\r | |
705 | },\r | |
706 | \r | |
707 | /**\r | |
708 | * Called for the first instance of this Widget to create an object that contains the\r | |
709 | * listener configs for all of the element references keyed by reference name. The\r | |
710 | * object is cached on the prototype and has the following shape:\r | |
711 | *\r | |
712 | * _elementListeners: {\r | |
713 | * element: {\r | |
714 | * click: 'onClick',\r | |
715 | * scope: this\r | |
716 | * },\r | |
717 | * fooReference: {\r | |
718 | * tap: {\r | |
719 | * fn: someFunction,\r | |
720 | * delay: 100\r | |
721 | * }\r | |
722 | * }\r | |
723 | * }\r | |
724 | *\r | |
725 | * The returned object is prototype chained to the _elementListeners object of its\r | |
726 | * superclass, and each key in the object is prototype chained to object with the\r | |
727 | * corresponding key in the superclass _elementListeners. This allows element\r | |
728 | * listeners to be inherited and overridden when subclassing widgets.\r | |
729 | *\r | |
730 | * This method is invoked with the prototype object as the scope\r | |
731 | *\r | |
732 | * @private\r | |
733 | */\r | |
734 | initElementListeners: function(elementConfig) {\r | |
735 | var prototype = this,\r | |
736 | superPrototype = prototype.self.superclass,\r | |
737 | superElementListeners = superPrototype._elementListeners,\r | |
738 | reference = elementConfig.reference,\r | |
739 | children = elementConfig.children,\r | |
740 | elementListeners, listeners, superListeners, ln, i;\r | |
741 | \r | |
742 | if (prototype.hasOwnProperty('_elementListeners')) {\r | |
743 | elementListeners = prototype._elementListeners;\r | |
744 | } else {\r | |
745 | elementListeners = prototype._elementListeners =\r | |
746 | (superElementListeners ? Ext.Object.chain(superElementListeners) : {});\r | |
747 | }\r | |
748 | \r | |
749 | if (reference) {\r | |
750 | listeners = elementConfig.listeners;\r | |
751 | if (listeners) {\r | |
752 | if (superElementListeners) {\r | |
753 | superListeners = superElementListeners[reference];\r | |
754 | if (superListeners) {\r | |
755 | listeners = Ext.Object.chain(superListeners);\r | |
756 | Ext.apply(listeners, elementConfig.listeners);\r | |
757 | }\r | |
758 | }\r | |
759 | \r | |
760 | elementListeners[reference] = listeners;\r | |
761 | // null out the listeners on the elementConfig, since we are going to pass\r | |
762 | // it to Element.create(), and don't want "listeners" to be treated as an\r | |
763 | // attribute\r | |
764 | elementConfig.listeners = null;\r | |
765 | }\r | |
766 | }\r | |
767 | \r | |
768 | if (children) {\r | |
769 | for (i = 0, ln = children.length; i < ln; i++) {\r | |
770 | prototype.initElementListeners(children[i]);\r | |
771 | }\r | |
772 | }\r | |
773 | },\r | |
774 | \r | |
775 | initId: function(config) {\r | |
776 | var me = this,\r | |
777 | defaultConfig = me.config,\r | |
778 | id = (config && config.id) || (defaultConfig && defaultConfig.id);\r | |
779 | \r | |
780 | if (id) {\r | |
781 | // setId() will normally be inherited from Identifiable, unless "id" is a\r | |
782 | // proper config, in which case it will be generated by the config system.\r | |
783 | me.setId(id);\r | |
784 | me.id = id;\r | |
785 | } else {\r | |
786 | // if no id configured, generate one (Identifiable)\r | |
787 | me.getId();\r | |
788 | }\r | |
789 | },\r | |
790 | \r | |
791 | /**\r | |
792 | * Recursively processes the element templates for this class and its superclasses,\r | |
793 | * ascending the hierarchy until it reaches a superclass whose element template\r | |
794 | * has already been processed. This method is invoked using the prototype as the scope.\r | |
795 | *\r | |
796 | * @private\r | |
797 | * @return {Object}\r | |
798 | */\r | |
799 | processElementConfig: function() {\r | |
800 | var prototype = this,\r | |
801 | superPrototype = prototype.self.superclass,\r | |
802 | elementConfig;\r | |
803 | \r | |
804 | if (prototype.hasOwnProperty('_elementConfig')) {\r | |
805 | elementConfig = prototype._elementConfig;\r | |
806 | } else {\r | |
807 | // cache the elementConfig on the prototype, since we may end up here multiple\r | |
808 | // times if there are multiple subclasses\r | |
809 | elementConfig = prototype._elementConfig = prototype.getElementConfig();\r | |
810 | \r | |
811 | if (superPrototype.isWidget) {\r | |
812 | // Before initializing element listeners we must process the element template\r | |
813 | // for our superclass so that we can chain our listeners to the superclass listeners\r | |
814 | prototype.processElementConfig.call(superPrototype);\r | |
815 | }\r | |
816 | \r | |
817 | // initElementListeners needs to be called BEFORE passing the element config\r | |
818 | // along to Ext.Element.create(). This ensures that the listener meta data is\r | |
819 | // saved, and then the listeners objects are removed from the element config\r | |
820 | // so that they do not get added as attributes by create()\r | |
821 | prototype.initElementListeners(elementConfig);\r | |
822 | }\r | |
823 | \r | |
824 | return elementConfig;\r | |
825 | },\r | |
826 | \r | |
827 | reattachToBody: function() {\r | |
828 | // See detachFromBody\r | |
829 | this.isDetached = false;\r | |
830 | },\r | |
831 | \r | |
832 | updateUserCls: function (newCls, oldCls) {\r | |
833 | this.element.replaceCls(oldCls, newCls);\r | |
834 | }\r | |
835 | }\r | |
836 | }, function(Widget) {\r | |
837 | var prototype = Widget.prototype;\r | |
838 | \r | |
839 | // event options for listeners that use the "element" event options must also include\r | |
840 | // event options from Ext.Element\r | |
841 | (prototype.$elementEventOptions =\r | |
842 | Ext.Object.chain(Ext.Element.prototype.$eventOptions)).element = 1;\r | |
843 | \r | |
844 | (prototype.$eventOptions = Ext.Object.chain(prototype.$eventOptions)).delegate = 1;\r | |
845 | });\r |