]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/_WidgetBase.js
2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
8 if(!dojo
._hasResource
["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo
._hasResource
["dijit._WidgetBase"] = true;
10 dojo
.provide("dijit._WidgetBase");
11 dojo
.require("dijit._base.manager");
12 dojo
.require("dojo.Stateful");
17 dojo
.declare("dijit._WidgetBase", dojo
.Stateful
, {
19 // Future base class for all Dijit widgets.
20 // _Widget extends this class adding support for various features needed by desktop.
23 // A unique, opaque ID string that can be assigned by users or by the
24 // system. If the developer passes an ID which is known not to be
25 // unique, the specified ID is ignored and the system-generated ID is
29 // lang: [const] String
30 // Rarely used. Overrides the default Dojo locale used to render this widget,
31 // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
32 // Value must be among the list of locales specified during by the Dojo bootstrap,
33 // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
36 // dir: [const] String
37 // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
38 // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's
43 // HTML class attribute
46 // style: String||Object
47 // HTML style attributes as cssText string or name/value hash
51 // HTML title attribute.
53 // For form widgets this specifies a tooltip to display when hovering over
54 // the widget (just like the native HTML title attribute).
56 // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
57 // etc., it's used to specify the tab label, accordion pane title, etc.
61 // When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
62 // this specifies the tooltip to appear when the mouse is hovered over that text.
65 // baseClass: [protected] String
66 // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
70 // srcNodeRef: [readonly] DomNode
71 // pointer to original DOM node
74 // domNode: [readonly] DomNode
75 // This is our visible representation of the widget! Other DOM
76 // Nodes may by assigned to other properties, usually through the
77 // template system's dojoAttachPoint syntax, but the domNode
78 // property is the canonical "top level" node in widget UI.
81 // containerNode: [readonly] DomNode
82 // Designates where children of the source DOM node will be placed.
83 // "Children" in this case refers to both DOM nodes and widgets.
84 // For example, for myWidget:
86 // | <div dojoType=myWidget>
87 // | <b> here's a plain DOM node
88 // | <span dojoType=subWidget>and a widget</span>
89 // | <i> and another plain DOM node </i>
92 // containerNode would point to:
94 // | <b> here's a plain DOM node
95 // | <span dojoType=subWidget>and a widget</span>
96 // | <i> and another plain DOM node </i>
98 // In templated widgets, "containerNode" is set via a
99 // dojoAttachPoint assignment.
101 // containerNode must be defined for any widget that accepts innerHTML
102 // (like ContentPane or BorderContainer or even Button), and conversely
103 // is null for widgets that don't, like TextBox.
108 // startup() has completed.
112 // attributeMap: [protected] Object
113 // attributeMap sets up a "binding" between attributes (aka properties)
114 // of the widget and the widget's DOM.
115 // Changes to widget attributes listed in attributeMap will be
116 // reflected into the DOM.
118 // For example, calling set('title', 'hello')
119 // on a TitlePane will automatically cause the TitlePane's DOM to update
120 // with the new title.
122 // attributeMap is a hash where the key is an attribute of the widget,
123 // and the value reflects a binding to a:
125 // - DOM node attribute
126 // | focus: {node: "focusNode", type: "attribute"}
127 // Maps this.focus to this.focusNode.focus
129 // - DOM node innerHTML
130 // | title: { node: "titleNode", type: "innerHTML" }
131 // Maps this.title to this.titleNode.innerHTML
133 // - DOM node innerText
134 // | title: { node: "titleNode", type: "innerText" }
135 // Maps this.title to this.titleNode.innerText
137 // - DOM node CSS class
138 // | myClass: { node: "domNode", type: "class" }
139 // Maps this.myClass to this.domNode.className
141 // If the value is an array, then each element in the array matches one of the
142 // formats of the above list.
144 // There are also some shorthands for backwards compatibility:
145 // - string --> { node: string, type: "attribute" }, for example:
146 // | "focusNode" ---> { node: "focusNode", type: "attribute" }
147 // - "" --> { node: "domNode", type: "attribute" }
148 attributeMap
: {id
:"", dir
:"", lang
:"", "class":"", style
:"", title
:""},
150 // _blankGif: [protected] String
151 // Path to a blank 1x1 image.
152 // Used by <img> nodes in templates that really get their image via CSS background-image.
153 _blankGif
: (dojo
.config
.blankGif
|| dojo
.moduleUrl("dojo", "resources/blank.gif")).toString(),
155 //////////// INITIALIZATION METHODS ///////////////////////////////////////
157 postscript: function(/*Object?*/params
, /*DomNode|String*/srcNodeRef
){
159 // Kicks off widget instantiation. See create() for details.
162 this.create(params
, srcNodeRef
);
165 create: function(/*Object?*/params
, /*DomNode|String?*/srcNodeRef
){
167 // Kick off the life-cycle of a widget
169 // Hash of initialization parameters for widget, including
170 // scalar values (like title, duration etc.) and functions,
171 // typically callbacks like onClick.
173 // If a srcNodeRef (DOM node) is specified:
174 // - use srcNodeRef.innerHTML as my contents
175 // - if this is a behavioral widget then apply behavior
176 // to that srcNodeRef
177 // - otherwise, replace srcNodeRef with my generated DOM
180 // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
181 // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
182 // for a discussion of the widget creation lifecycle.
184 // Of course, adventurous developers could override create entirely, but this should
185 // only be done as a last resort.
189 // store pointer to original DOM tree
190 this.srcNodeRef
= dojo
.byId(srcNodeRef
);
192 // For garbage collection. An array of handles returned by Widget.connect()
193 // Each handle returned from Widget.connect() is an array of handles from dojo.connect()
196 // For garbage collection. An array of handles returned by Widget.subscribe()
197 // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
198 this._subscribes
= [];
200 // mix in our passed parameters
201 if(this.srcNodeRef
&& (typeof this.srcNodeRef
.id
== "string")){ this.id
= this.srcNodeRef
.id
; }
203 this.params
= params
;
204 dojo
._mixin(this, params
);
206 this.postMixInProperties();
208 // generate an id for the widget if one wasn't specified
209 // (be sure to do this before buildRendering() because that function might
210 // expect the id to be there.)
212 this.id
= dijit
.getUniqueId(this.declaredClass
.replace(/\./g,"_"));
214 dijit
.registry
.add(this);
216 this.buildRendering();
219 // Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
220 // Also calls custom setters for all attributes with custom setters.
221 this._applyAttributes();
223 // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
224 // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
225 // widget being attached to the DOM since it isn't when a widget is created programmatically like
226 // new MyWidget({}). See #11635.
227 var source
= this.srcNodeRef
;
228 if(source
&& source
.parentNode
&& this.domNode
!== source
){
229 source
.parentNode
.replaceChild(this.domNode
, source
);
234 // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
235 // assuming that dojo._scopeName even exists in 2.0
236 this.domNode
.setAttribute("widgetId", this.id
);
240 // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
241 if(this.srcNodeRef
&& !this.srcNodeRef
.parentNode
){
242 delete this.srcNodeRef
;
245 this._created
= true;
248 _applyAttributes: function(){
250 // Step during widget creation to copy all widget attributes to the
251 // DOM as per attributeMap and _setXXXAttr functions.
253 // Skips over blank/false attribute values, unless they were explicitly specified
254 // as parameters to the widget, since those are the default anyway,
255 // and setting tabIndex="" is different than not setting tabIndex at all.
257 // It processes the attributes in the attribute map first, and then
258 // it goes through and processes the attributes for the _setXXXAttr
259 // functions that have been specified
262 var condAttrApply = function(attr
, scope
){
263 if((scope
.params
&& attr
in scope
.params
) || scope
[attr
]){
264 scope
.set(attr
, scope
[attr
]);
268 // Do the attributes in attributeMap
269 for(var attr
in this.attributeMap
){
270 condAttrApply(attr
, this);
273 // And also any attributes with custom setters
274 dojo
.forEach(this._getSetterAttributes(), function(a
){
275 if(!(a
in this.attributeMap
)){
276 condAttrApply(a
, this);
281 _getSetterAttributes: function(){
283 // Returns list of attributes with custom setters for this widget
284 var ctor
= this.constructor;
285 if(!ctor
._setterAttrs
){
286 var r
= (ctor
._setterAttrs
= []),
288 proto
= ctor
.prototype;
289 for(var fxName
in proto
){
290 if(dojo
.isFunction(proto
[fxName
]) && (attrs
= fxName
.match(/^_set([a-zA-Z]*)Attr$/)) && attrs
[1]){
291 r
.push(attrs
[1].charAt(0).toLowerCase() + attrs
[1].substr(1));
295 return ctor
._setterAttrs
; // String[]
298 postMixInProperties: function(){
300 // Called after the parameters to the widget have been read-in,
301 // but before the widget template is instantiated. Especially
302 // useful to set properties that are referenced in the widget
308 buildRendering: function(){
310 // Construct the UI for this widget, setting this.domNode
312 // Most widgets will mixin `dijit._Templated`, which implements this
318 // Create root node if it wasn't created by _Templated
319 this.domNode
= this.srcNodeRef
|| dojo
.create('div');
322 // baseClass is a single class name or occasionally a space-separated list of names.
323 // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
324 // TODO: make baseClass custom setter
326 var classes
= this.baseClass
.split(" ");
327 if(!this.isLeftToRight()){
328 classes
= classes
.concat( dojo
.map(classes
, function(name
){ return name
+"Rtl"; }));
330 dojo
.addClass(this.domNode
, classes
);
334 postCreate: function(){
336 // Processing after the DOM fragment is created
338 // Called after the DOM fragment has been created, but not necessarily
339 // added to the document. Do not include any operations which rely on
340 // node dimensions or placement.
347 // Processing after the DOM fragment is added to the document
349 // Called after a widget and its children have been created and added to the page,
350 // and all related widgets have finished their create() cycle, up through postCreate().
351 // This is useful for composite widgets that need to control or layout sub-widgets.
352 // Many layout widgets can use this as a wiring phase.
353 this._started
= true;
356 //////////// DESTROY FUNCTIONS ////////////////////////////////
358 destroyRecursive: function(/*Boolean?*/ preserveDom
){
360 // Destroy this widget and its descendants
362 // This is the generic "destructor" function that all widget users
363 // should call to cleanly discard with a widget. Once a widget is
364 // destroyed, it is removed from the manager object.
366 // If true, this method will leave the original DOM structure
367 // alone of descendant Widgets. Note: This will NOT work with
368 // dijit._Templated widgets.
370 this._beingDestroyed
= true;
371 this.destroyDescendants(preserveDom
);
372 this.destroy(preserveDom
);
375 destroy: function(/*Boolean*/ preserveDom
){
377 // Destroy this widget, but not its descendants.
378 // This method will, however, destroy internal widgets such as those used within a template.
379 // preserveDom: Boolean
380 // If true, this method will leave the original DOM structure alone.
381 // Note: This will not yet work with _Templated widgets
383 this._beingDestroyed
= true;
388 dfe(this._connects
, function(array
){
389 dfe(array
, d
.disconnect
);
391 dfe(this._subscribes
, function(handle
){
395 // destroy widgets created as part of template, etc.
396 dfe(this._supportingWidgets
|| [], function(w
){
397 if(w
.destroyRecursive
){
398 w
.destroyRecursive();
404 this.destroyRendering(preserveDom
);
405 dijit
.registry
.remove(this.id
);
406 this._destroyed
= true;
409 destroyRendering: function(/*Boolean?*/ preserveDom
){
411 // Destroys the DOM nodes associated with this widget
413 // If true, this method will leave the original DOM structure alone
414 // during tear-down. Note: this will not work with _Templated
420 this.bgIframe
.destroy(preserveDom
);
421 delete this.bgIframe
;
426 dojo
.removeAttr(this.domNode
, "widgetId");
428 dojo
.destroy(this.domNode
);
435 dojo
.destroy(this.srcNodeRef
);
437 delete this.srcNodeRef
;
441 destroyDescendants: function(/*Boolean?*/ preserveDom
){
443 // Recursively destroy the children of this widget and their
446 // If true, the preserveDom attribute is passed to all descendant
447 // widget's .destroy() method. Not for use with _Templated
450 // get all direct descendants and destroy them recursively
451 dojo
.forEach(this.getChildren(), function(widget
){
452 if(widget
.destroyRecursive
){
453 widget
.destroyRecursive(preserveDom
);
458 uninitialize: function(){
460 // Stub function. Override to implement custom widget tear-down
467 ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
469 _setClassAttr: function(/*String*/ value
){
471 // Custom setter for the CSS "class" attribute
474 var mapNode
= this[this.attributeMap
["class"] || 'domNode'];
475 dojo
.replaceClass(mapNode
, value
, this["class"]);
476 this._set("class", value
);
479 _setStyleAttr: function(/*String||Object*/ value
){
481 // Sets the style attribute of the widget according to value,
482 // which is either a hash like {height: "5px", width: "3px"}
485 // Determines which node to set the style on based on style setting
490 var mapNode
= this[this.attributeMap
.style
|| 'domNode'];
492 // Note: technically we should revert any style setting made in a previous call
493 // to his method, but that's difficult to keep track of.
495 if(dojo
.isObject(value
)){
496 dojo
.style(mapNode
, value
);
498 if(mapNode
.style
.cssText
){
499 mapNode
.style
.cssText
+= "; " + value
;
501 mapNode
.style
.cssText
= value
;
505 this._set("style", value
);
508 _attrToDom: function(/*String*/ attr
, /*String*/ value
){
510 // Reflect a widget attribute (title, tabIndex, duration etc.) to
511 // the widget DOM, as specified in attributeMap.
512 // Note some attributes like "type"
513 // cannot be processed this way as they are not mutable.
518 var commands
= this.attributeMap
[attr
];
519 dojo
.forEach(dojo
.isArray(commands
) ? commands
: [commands
], function(command
){
521 // Get target node and what we are doing to that node
522 var mapNode
= this[command
.node
|| command
|| "domNode"]; // DOM node
523 var type
= command
.type
|| "attribute"; // class, innerHTML, innerText, or attribute
527 if(dojo
.isFunction(value
)){ // functions execute in the context of the widget
528 value
= dojo
.hitch(this, value
);
531 // Get the name of the DOM node attribute; usually it's the same
532 // as the name of the attribute in the widget (attr), but can be overridden.
533 // Also maps handler names to lowercase, like onSubmit --> onsubmit
534 var attrName
= command
.attribute
? command
.attribute
:
535 (/^on[A-Z][a-zA-Z]*$/.test(attr
) ? attr
.toLowerCase() : attr
);
537 dojo
.attr(mapNode
, attrName
, value
);
540 mapNode
.innerHTML
= "";
541 mapNode
.appendChild(dojo
.doc
.createTextNode(value
));
544 mapNode
.innerHTML
= value
;
547 dojo
.replaceClass(mapNode
, value
, this[attr
]);
555 // Get a property from a widget.
557 // The property to get.
559 // Get a named property from a widget. The property may
560 // potentially be retrieved via a getter method. If no getter is defined, this
561 // just retrieves the object's property.
562 // For example, if the widget has a properties "foo"
563 // and "bar" and a method named "_getFooAttr", calling:
564 // | myWidget.get("foo");
565 // would be equivalent to writing:
566 // | widget._getFooAttr();
568 // | myWidget.get("bar");
569 // would be equivalent to writing:
571 var names
= this._getAttrNames(name
);
572 return this[names
.g
] ? this[names
.g
]() : this[name
];
575 set: function(name
, value
){
577 // Set a property on a widget
579 // The property to set.
581 // The value to set in the property.
583 // Sets named properties on a widget which may potentially be handled by a
584 // setter in the widget.
585 // For example, if the widget has a properties "foo"
586 // and "bar" and a method named "_setFooAttr", calling:
587 // | myWidget.set("foo", "Howdy!");
588 // would be equivalent to writing:
589 // | widget._setFooAttr("Howdy!");
591 // | myWidget.set("bar", 3);
592 // would be equivalent to writing:
595 // set() may also be called with a hash of name/value pairs, ex:
600 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
602 if(typeof name
=== "object"){
604 this.set(x
, name
[x
]);
608 var names
= this._getAttrNames(name
);
610 // use the explicit setter
611 var result
= this[names
.s
].apply(this, Array
.prototype.slice
.call(arguments
, 1));
613 // if param is specified as DOM node attribute, copy it
614 if(name
in this.attributeMap
){
615 this._attrToDom(name
, value
);
617 this._set(name
, value
);
619 return result
|| this;
622 _attrPairNames
: {}, // shared between all widgets
623 _getAttrNames: function(name
){
625 // Helper function for get() and set().
626 // Caches attribute name values so we don't do the string ops every time.
630 var apn
= this._attrPairNames
;
631 if(apn
[name
]){ return apn
[name
]; }
632 var uc
= name
.charAt(0).toUpperCase() + name
.substr(1);
633 return (apn
[name
] = {
640 _set: function(/*String*/ name
, /*anything*/ value
){
642 // Helper function to set new value for specified attribute, and call handlers
643 // registered with watch() if the value has changed.
644 var oldValue
= this[name
];
646 if(this._watchCallbacks
&& this._created
&& value
!== oldValue
){
647 this._watchCallbacks(name
, oldValue
, value
);
651 toString: function(){
653 // Returns a string that represents the widget
655 // When a widget is cast to a string, this method will be used to generate the
656 // output. Currently, it does not implement any sort of reversible
658 return '[Widget ' + this.declaredClass
+ ', ' + (this.id
|| 'NO ID') + ']'; // String
661 getDescendants: function(){
663 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
664 // This method should generally be avoided as it returns widgets declared in templates, which are
665 // supposed to be internal/hidden, but it's left here for back-compat reasons.
667 return this.containerNode
? dojo
.query('[widgetId]', this.containerNode
).map(dijit
.byNode
) : []; // dijit._Widget[]
670 getChildren: function(){
672 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
673 // Does not return nested widgets, nor widgets that are part of this widget's template.
674 return this.containerNode
? dijit
.findWidgets(this.containerNode
) : []; // dijit._Widget[]
679 /*String|Function*/ event
,
680 /*String|Function*/ method
){
682 // Connects specified obj/event to specified method of this object
683 // and registers for disconnect() on widget destroy.
685 // Provide widget-specific analog to dojo.connect, except with the
686 // implicit use of this widget as the target object.
687 // Events connected with `this.connect` are disconnected upon
690 // A handle that can be passed to `disconnect` in order to disconnect before
691 // the widget is destroyed.
693 // | var btn = new dijit.form.Button();
694 // | // when foo.bar() is called, call the listener we're going to
695 // | // provide in the scope of btn
696 // | btn.connect(foo, "bar", function(){
697 // | console.debug(this.toString());
702 var handles
= [dojo
._connect(obj
, event
, this, method
)];
703 this._connects
.push(handles
);
704 return handles
; // _Widget.Handle
707 disconnect: function(/* _Widget.Handle */ handles
){
709 // Disconnects handle created by `connect`.
710 // Also removes handle from this widget's list of connects.
713 for(var i
=0; i
<this._connects
.length
; i
++){
714 if(this._connects
[i
] == handles
){
715 dojo
.forEach(handles
, dojo
.disconnect
);
716 this._connects
.splice(i
, 1);
724 /*String|Function*/ method
){
726 // Subscribes to the specified topic and calls the specified method
727 // of this object and registers for unsubscribe() on widget destroy.
729 // Provide widget-specific analog to dojo.subscribe, except with the
730 // implicit use of this widget as the target object.
732 // | var btn = new dijit.form.Button();
733 // | // when /my/topic is published, this button changes its label to
734 // | // be the parameter of the topic.
735 // | btn.subscribe("/my/topic", function(v){
736 // | this.set("label", v);
738 var handle
= dojo
.subscribe(topic
, this, method
);
740 // return handles for Any widget that may need them
741 this._subscribes
.push(handle
);
745 unsubscribe: function(/*Object*/ handle
){
747 // Unsubscribes handle created by this.subscribe.
748 // Also removes handle from this widget's list of subscriptions
749 for(var i
=0; i
<this._subscribes
.length
; i
++){
750 if(this._subscribes
[i
] == handle
){
751 dojo
.unsubscribe(handle
);
752 this._subscribes
.splice(i
, 1);
758 isLeftToRight: function(){
760 // Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
763 return this.dir
? (this.dir
== "ltr") : dojo
._isBodyLtr(); //Boolean
766 placeAt: function(/* String|DomNode|_Widget */reference
, /* String?|Int? */position
){
768 // Place this widget's domNode reference somewhere in the DOM based
769 // on standard dojo.place conventions, or passing a Widget reference that
770 // contains and addChild member.
773 // A convenience function provided in all _Widgets, providing a simple
774 // shorthand mechanism to put an existing (or newly created) Widget
775 // somewhere in the dom, and allow chaining.
778 // The String id of a domNode, a domNode reference, or a reference to a Widget posessing
779 // an addChild method.
782 // If passed a string or domNode reference, the position argument
783 // accepts a string just as dojo.place does, one of: "first", "last",
784 // "before", or "after".
786 // If passed a _Widget reference, and that widget reference has an ".addChild" method,
787 // it will be called passing this widget instance into that method, supplying the optional
788 // position index passed.
792 // Provides a useful return of the newly created dijit._Widget instance so you
793 // can "chain" this function by instantiating, placing, then saving the return value
797 // | // create a Button with no srcNodeRef, and place it in the body:
798 // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
799 // | // now, 'button' is still the widget reference to the newly created button
800 // | dojo.connect(button, "onClick", function(e){ console.log('click'); });
803 // | // create a button out of a node with id="src" and append it to id="wrapper":
804 // | var button = new dijit.form.Button({},"src").placeAt("wrapper");
807 // | // place a new button as the first element of some div
808 // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
811 // | // create a contentpane and add it to a TabContainer
812 // | var tc = dijit.byId("myTabs");
813 // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
815 if(reference
.declaredClass
&& reference
.addChild
){
816 reference
.addChild(this, position
);
818 dojo
.place(this.domNode
, reference
, position
);