]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/Widget.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / Widget.js
CommitLineData
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
53Ext.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