]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/_WidgetBase.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / _WidgetBase.js.uncompressed.js
1 define("dijit/_WidgetBase", [
2         "require",                      // require.toUrl
3         "dojo/_base/array", // array.forEach array.map
4         "dojo/aspect",
5         "dojo/_base/config", // config.blankGif
6         "dojo/_base/connect", // connect.connect
7         "dojo/_base/declare", // declare
8         "dojo/dom", // dom.byId
9         "dojo/dom-attr", // domAttr.set domAttr.remove
10         "dojo/dom-class", // domClass.add domClass.replace
11         "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place
12         "dojo/dom-geometry",    // isBodyLtr
13         "dojo/dom-style", // domStyle.set, domStyle.get
14         "dojo/_base/kernel",
15         "dojo/_base/lang", // mixin(), isArray(), etc.
16         "dojo/on",
17         "dojo/ready",
18         "dojo/Stateful", // Stateful
19         "dojo/topic",
20         "dojo/_base/window", // win.doc.createTextNode
21         "./registry"    // registry.getUniqueId(), registry.findWidgets()
22 ], function(require, array, aspect, config, connect, declare,
23                         dom, domAttr, domClass, domConstruct, domGeometry, domStyle, kernel,
24                         lang, on, ready, Stateful, topic, win, registry){
25
26 /*=====
27 var Stateful = dojo.Stateful;
28 =====*/
29
30 // module:
31 //              dijit/_WidgetBase
32 // summary:
33 //              Future base class for all Dijit widgets.
34
35 // For back-compat, remove in 2.0.
36 if(!kernel.isAsync){
37         ready(0, function(){
38                 var requires = ["dijit/_base/manager"];
39                 require(requires);      // use indirection so modules not rolled into a build
40         });
41 }
42
43 // Nested hash listing attributes for each tag, all strings in lowercase.
44 // ex: {"div": {"style": true, "tabindex" true}, "form": { ...
45 var tagAttrs = {};
46 function getAttrs(obj){
47         var ret = {};
48         for(var attr in obj){
49                 ret[attr.toLowerCase()] = true;
50         }
51         return ret;
52 }
53
54 function nonEmptyAttrToDom(attr){
55         // summary:
56         //              Returns a setter function that copies the attribute to this.domNode,
57         //              or removes the attribute from this.domNode, depending on whether the
58         //              value is defined or not.
59         return function(val){
60                 domAttr[val ? "set" : "remove"](this.domNode, attr, val);
61                 this._set(attr, val);
62         };
63 }
64
65 return declare("dijit._WidgetBase", Stateful, {
66         // summary:
67         //              Future base class for all Dijit widgets.
68         // description:
69         //              Future base class for all Dijit widgets.
70         //              _Widget extends this class adding support for various features needed by desktop.
71         //
72         //              Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(),
73         //              postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch().
74         //
75         //              Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value).
76         //              For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr().
77         //
78         //              _setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes:
79         //
80         //              - DOM node attribute
81         // |            _setFocusAttr: {node: "focusNode", type: "attribute"}
82         // |            _setFocusAttr: "focusNode"      (shorthand)
83         // |            _setFocusAttr: ""               (shorthand, maps to this.domNode)
84         //              Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus
85         //
86         //              - DOM node innerHTML
87         //      |               _setTitleAttr: { node: "titleNode", type: "innerHTML" }
88         //              Maps this.title to this.titleNode.innerHTML
89         //
90         //              - DOM node innerText
91         //      |               _setTitleAttr: { node: "titleNode", type: "innerText" }
92         //              Maps this.title to this.titleNode.innerText
93         //
94         //              - DOM node CSS class
95         // |            _setMyClassAttr: { node: "domNode", type: "class" }
96         //              Maps this.myClass to this.domNode.className
97         //
98         //              If the value of _setXXXAttr is an array, then each element in the array matches one of the
99         //              formats of the above list.
100         //
101         //              If the custom setter is null, no action is performed other than saving the new value
102         //              in the widget (in this).
103         //
104         //              If no custom setter is defined for an attribute, then it will be copied
105         //              to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise.
106         //              That's only done though for attributes that match DOMNode attributes (title,
107         //              alt, aria-labelledby, etc.)
108
109         // id: [const] String
110         //              A unique, opaque ID string that can be assigned by users or by the
111         //              system. If the developer passes an ID which is known not to be
112         //              unique, the specified ID is ignored and the system-generated ID is
113         //              used instead.
114         id: "",
115         _setIdAttr: "domNode",  // to copy to this.domNode even for auto-generated id's
116
117         // lang: [const] String
118         //              Rarely used.  Overrides the default Dojo locale used to render this widget,
119         //              as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
120         //              Value must be among the list of locales specified during by the Dojo bootstrap,
121         //              formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
122         lang: "",
123         // set on domNode even when there's a focus node.   but don't set lang="", since that's invalid.
124         _setLangAttr: nonEmptyAttrToDom("lang"),
125
126         // dir: [const] String
127         //              Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
128         //              attribute. Either left-to-right "ltr" or right-to-left "rtl".  If undefined, widgets renders in page's
129         //              default direction.
130         dir: "",
131         // set on domNode even when there's a focus node.   but don't set dir="", since that's invalid.
132         _setDirAttr: nonEmptyAttrToDom("dir"),  // to set on domNode even when there's a focus node
133
134         // textDir: String
135         //              Bi-directional support, the main variable which is responsible for the direction of the text.
136         //              The text direction can be different than the GUI direction by using this parameter in creation
137         //              of a widget.
138         //              Allowed values:
139         //                      1. "ltr"
140         //                      2. "rtl"
141         //                      3. "auto" - contextual the direction of a text defined by first strong letter.
142         //              By default is as the page direction.
143         textDir: "",
144
145         // class: String
146         //              HTML class attribute
147         "class": "",
148         _setClassAttr: { node: "domNode", type: "class" },
149
150         // style: String||Object
151         //              HTML style attributes as cssText string or name/value hash
152         style: "",
153
154         // title: String
155         //              HTML title attribute.
156         //
157         //              For form widgets this specifies a tooltip to display when hovering over
158         //              the widget (just like the native HTML title attribute).
159         //
160         //              For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
161         //              etc., it's used to specify the tab label, accordion pane title, etc.
162         title: "",
163
164         // tooltip: String
165         //              When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
166         //              this specifies the tooltip to appear when the mouse is hovered over that text.
167         tooltip: "",
168
169         // baseClass: [protected] String
170         //              Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
171         //              widget state.
172         baseClass: "",
173
174         // srcNodeRef: [readonly] DomNode
175         //              pointer to original DOM node
176         srcNodeRef: null,
177
178         // domNode: [readonly] DomNode
179         //              This is our visible representation of the widget! Other DOM
180         //              Nodes may by assigned to other properties, usually through the
181         //              template system's data-dojo-attach-point syntax, but the domNode
182         //              property is the canonical "top level" node in widget UI.
183         domNode: null,
184
185         // containerNode: [readonly] DomNode
186         //              Designates where children of the source DOM node will be placed.
187         //              "Children" in this case refers to both DOM nodes and widgets.
188         //              For example, for myWidget:
189         //
190         //              |       <div data-dojo-type=myWidget>
191         //              |               <b> here's a plain DOM node
192         //              |               <span data-dojo-type=subWidget>and a widget</span>
193         //              |               <i> and another plain DOM node </i>
194         //              |       </div>
195         //
196         //              containerNode would point to:
197         //
198         //              |               <b> here's a plain DOM node
199         //              |               <span data-dojo-type=subWidget>and a widget</span>
200         //              |               <i> and another plain DOM node </i>
201         //
202         //              In templated widgets, "containerNode" is set via a
203         //              data-dojo-attach-point assignment.
204         //
205         //              containerNode must be defined for any widget that accepts innerHTML
206         //              (like ContentPane or BorderContainer or even Button), and conversely
207         //              is null for widgets that don't, like TextBox.
208         containerNode: null,
209
210 /*=====
211         // _started: Boolean
212         //              startup() has completed.
213         _started: false,
214 =====*/
215
216         // attributeMap: [protected] Object
217         //              Deprecated.   Instead of attributeMap, widget should have a _setXXXAttr attribute
218         //              for each XXX attribute to be mapped to the DOM.
219         //
220         //              attributeMap sets up a "binding" between attributes (aka properties)
221         //              of the widget and the widget's DOM.
222         //              Changes to widget attributes listed in attributeMap will be
223         //              reflected into the DOM.
224         //
225         //              For example, calling set('title', 'hello')
226         //              on a TitlePane will automatically cause the TitlePane's DOM to update
227         //              with the new title.
228         //
229         //              attributeMap is a hash where the key is an attribute of the widget,
230         //              and the value reflects a binding to a:
231         //
232         //              - DOM node attribute
233         // |            focus: {node: "focusNode", type: "attribute"}
234         //              Maps this.focus to this.focusNode.focus
235         //
236         //              - DOM node innerHTML
237         //      |               title: { node: "titleNode", type: "innerHTML" }
238         //              Maps this.title to this.titleNode.innerHTML
239         //
240         //              - DOM node innerText
241         //      |               title: { node: "titleNode", type: "innerText" }
242         //              Maps this.title to this.titleNode.innerText
243         //
244         //              - DOM node CSS class
245         // |            myClass: { node: "domNode", type: "class" }
246         //              Maps this.myClass to this.domNode.className
247         //
248         //              If the value is an array, then each element in the array matches one of the
249         //              formats of the above list.
250         //
251         //              There are also some shorthands for backwards compatibility:
252         //              - string --> { node: string, type: "attribute" }, for example:
253         //      |       "focusNode" ---> { node: "focusNode", type: "attribute" }
254         //              - "" --> { node: "domNode", type: "attribute" }
255         attributeMap: {},
256
257         // _blankGif: [protected] String
258         //              Path to a blank 1x1 image.
259         //              Used by <img> nodes in templates that really get their image via CSS background-image.
260         _blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"),
261
262         //////////// INITIALIZATION METHODS ///////////////////////////////////////
263
264         postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
265                 // summary:
266                 //              Kicks off widget instantiation.  See create() for details.
267                 // tags:
268                 //              private
269                 this.create(params, srcNodeRef);
270         },
271
272         create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
273                 // summary:
274                 //              Kick off the life-cycle of a widget
275                 // params:
276                 //              Hash of initialization parameters for widget, including
277                 //              scalar values (like title, duration etc.) and functions,
278                 //              typically callbacks like onClick.
279                 // srcNodeRef:
280                 //              If a srcNodeRef (DOM node) is specified:
281                 //                      - use srcNodeRef.innerHTML as my contents
282                 //                      - if this is a behavioral widget then apply behavior
283                 //                        to that srcNodeRef
284                 //                      - otherwise, replace srcNodeRef with my generated DOM
285                 //                        tree
286                 // description:
287                 //              Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
288                 //              etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html
289                 //              for a discussion of the widget creation lifecycle.
290                 //
291                 //              Of course, adventurous developers could override create entirely, but this should
292                 //              only be done as a last resort.
293                 // tags:
294                 //              private
295
296                 // store pointer to original DOM tree
297                 this.srcNodeRef = dom.byId(srcNodeRef);
298
299                 // For garbage collection.  An array of listener handles returned by this.connect() / this.subscribe()
300                 this._connects = [];
301
302                 // For widgets internal to this widget, invisible to calling code
303                 this._supportingWidgets = [];
304
305                 // this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test)
306                 if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
307
308                 // mix in our passed parameters
309                 if(params){
310                         this.params = params;
311                         lang.mixin(this, params);
312                 }
313                 this.postMixInProperties();
314
315                 // generate an id for the widget if one wasn't specified
316                 // (be sure to do this before buildRendering() because that function might
317                 // expect the id to be there.)
318                 if(!this.id){
319                         this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_"));
320                 }
321                 registry.add(this);
322
323                 this.buildRendering();
324
325                 if(this.domNode){
326                         // Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
327                         // Also calls custom setters for all attributes with custom setters.
328                         this._applyAttributes();
329
330                         // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
331                         // For 2.0, move this after postCreate().  postCreate() shouldn't depend on the
332                         // widget being attached to the DOM since it isn't when a widget is created programmatically like
333                         // new MyWidget({}).   See #11635.
334                         var source = this.srcNodeRef;
335                         if(source && source.parentNode && this.domNode !== source){
336                                 source.parentNode.replaceChild(this.domNode, source);
337                         }
338                 }
339
340                 if(this.domNode){
341                         // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
342                         // assuming that dojo._scopeName even exists in 2.0
343                         this.domNode.setAttribute("widgetId", this.id);
344                 }
345                 this.postCreate();
346
347                 // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
348                 if(this.srcNodeRef && !this.srcNodeRef.parentNode){
349                         delete this.srcNodeRef;
350                 }
351
352                 this._created = true;
353         },
354
355         _applyAttributes: function(){
356                 // summary:
357                 //              Step during widget creation to copy  widget attributes to the
358                 //              DOM according to attributeMap and _setXXXAttr objects, and also to call
359                 //              custom _setXXXAttr() methods.
360                 //
361                 //              Skips over blank/false attribute values, unless they were explicitly specified
362                 //              as parameters to the widget, since those are the default anyway,
363                 //              and setting tabIndex="" is different than not setting tabIndex at all.
364                 //
365                 //              For backwards-compatibility reasons attributeMap overrides _setXXXAttr when
366                 //              _setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap.
367                 // tags:
368                 //              private
369
370                 // Get list of attributes where this.set(name, value) will do something beyond
371                 // setting this[name] = value.  Specifically, attributes that have:
372                 //              - associated _setXXXAttr() method/hash/string/array
373                 //              - entries in attributeMap.
374                 var ctor = this.constructor,
375                         list = ctor._setterAttrs;
376                 if(!list){
377                         list = (ctor._setterAttrs = []);
378                         for(var attr in this.attributeMap){
379                                 list.push(attr);
380                         }
381
382                         var proto = ctor.prototype;
383                         for(var fxName in proto){
384                                 if(fxName in this.attributeMap){ continue; }
385                                 var setterName = "_set" + fxName.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); }) + "Attr";
386                                 if(setterName in proto){
387                                         list.push(fxName);
388                                 }
389                         }
390                 }
391
392                 // Call this.set() for each attribute that was either specified as parameter to constructor,
393                 // or was found above and has a default non-null value.   For correlated attributes like value and displayedValue, the one
394                 // specified as a parameter should take precedence, so apply attributes in this.params last.
395                 // Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is
396                 // NaN and thus is not ignored like a default value of "".
397                 array.forEach(list, function(attr){
398                         if(this.params && attr in this.params){
399                                 // skip this one, do it below
400                         }else if(this[attr]){
401                                 this.set(attr, this[attr]);
402                         }
403                 }, this);
404                 for(var param in this.params){
405                         this.set(param, this[param]);
406                 }
407         },
408
409         postMixInProperties: function(){
410                 // summary:
411                 //              Called after the parameters to the widget have been read-in,
412                 //              but before the widget template is instantiated. Especially
413                 //              useful to set properties that are referenced in the widget
414                 //              template.
415                 // tags:
416                 //              protected
417         },
418
419         buildRendering: function(){
420                 // summary:
421                 //              Construct the UI for this widget, setting this.domNode.
422                 //              Most widgets will mixin `dijit._TemplatedMixin`, which implements this method.
423                 // tags:
424                 //              protected
425
426                 if(!this.domNode){
427                         // Create root node if it wasn't created by _Templated
428                         this.domNode = this.srcNodeRef || domConstruct.create('div');
429                 }
430
431                 // baseClass is a single class name or occasionally a space-separated list of names.
432                 // Add those classes to the DOMNode.  If RTL mode then also add with Rtl suffix.
433                 // TODO: make baseClass custom setter
434                 if(this.baseClass){
435                         var classes = this.baseClass.split(" ");
436                         if(!this.isLeftToRight()){
437                                 classes = classes.concat( array.map(classes, function(name){ return name+"Rtl"; }));
438                         }
439                         domClass.add(this.domNode, classes);
440                 }
441         },
442
443         postCreate: function(){
444                 // summary:
445                 //              Processing after the DOM fragment is created
446                 // description:
447                 //              Called after the DOM fragment has been created, but not necessarily
448                 //              added to the document.  Do not include any operations which rely on
449                 //              node dimensions or placement.
450                 // tags:
451                 //              protected
452         },
453
454         startup: function(){
455                 // summary:
456                 //              Processing after the DOM fragment is added to the document
457                 // description:
458                 //              Called after a widget and its children have been created and added to the page,
459                 //              and all related widgets have finished their create() cycle, up through postCreate().
460                 //              This is useful for composite widgets that need to control or layout sub-widgets.
461                 //              Many layout widgets can use this as a wiring phase.
462                 if(this._started){ return; }
463                 this._started = true;
464                 array.forEach(this.getChildren(), function(obj){
465                         if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
466                                 obj.startup();
467                                 obj._started = true;
468                         }
469                 });
470         },
471
472         //////////// DESTROY FUNCTIONS ////////////////////////////////
473
474         destroyRecursive: function(/*Boolean?*/ preserveDom){
475                 // summary:
476                 //              Destroy this widget and its descendants
477                 // description:
478                 //              This is the generic "destructor" function that all widget users
479                 //              should call to cleanly discard with a widget. Once a widget is
480                 //              destroyed, it is removed from the manager object.
481                 // preserveDom:
482                 //              If true, this method will leave the original DOM structure
483                 //              alone of descendant Widgets. Note: This will NOT work with
484                 //              dijit._Templated widgets.
485
486                 this._beingDestroyed = true;
487                 this.destroyDescendants(preserveDom);
488                 this.destroy(preserveDom);
489         },
490
491         destroy: function(/*Boolean*/ preserveDom){
492                 // summary:
493                 //              Destroy this widget, but not its descendants.
494                 //              This method will, however, destroy internal widgets such as those used within a template.
495                 // preserveDom: Boolean
496                 //              If true, this method will leave the original DOM structure alone.
497                 //              Note: This will not yet work with _Templated widgets
498
499                 this._beingDestroyed = true;
500                 this.uninitialize();
501
502                 // remove this.connect() and this.subscribe() listeners
503                 var c;
504                 while((c = this._connects.pop())){
505                         c.remove();
506                 }
507
508                 // destroy widgets created as part of template, etc.
509                 var w;
510                 while((w = this._supportingWidgets.pop())){
511                         if(w.destroyRecursive){
512                                 w.destroyRecursive();
513                         }else if(w.destroy){
514                                 w.destroy();
515                         }
516                 }
517
518                 this.destroyRendering(preserveDom);
519                 registry.remove(this.id);
520                 this._destroyed = true;
521         },
522
523         destroyRendering: function(/*Boolean?*/ preserveDom){
524                 // summary:
525                 //              Destroys the DOM nodes associated with this widget
526                 // preserveDom:
527                 //              If true, this method will leave the original DOM structure alone
528                 //              during tear-down. Note: this will not work with _Templated
529                 //              widgets yet.
530                 // tags:
531                 //              protected
532
533                 if(this.bgIframe){
534                         this.bgIframe.destroy(preserveDom);
535                         delete this.bgIframe;
536                 }
537
538                 if(this.domNode){
539                         if(preserveDom){
540                                 domAttr.remove(this.domNode, "widgetId");
541                         }else{
542                                 domConstruct.destroy(this.domNode);
543                         }
544                         delete this.domNode;
545                 }
546
547                 if(this.srcNodeRef){
548                         if(!preserveDom){
549                                 domConstruct.destroy(this.srcNodeRef);
550                         }
551                         delete this.srcNodeRef;
552                 }
553         },
554
555         destroyDescendants: function(/*Boolean?*/ preserveDom){
556                 // summary:
557                 //              Recursively destroy the children of this widget and their
558                 //              descendants.
559                 // preserveDom:
560                 //              If true, the preserveDom attribute is passed to all descendant
561                 //              widget's .destroy() method. Not for use with _Templated
562                 //              widgets.
563
564                 // get all direct descendants and destroy them recursively
565                 array.forEach(this.getChildren(), function(widget){
566                         if(widget.destroyRecursive){
567                                 widget.destroyRecursive(preserveDom);
568                         }
569                 });
570         },
571
572         uninitialize: function(){
573                 // summary:
574                 //              Stub function. Override to implement custom widget tear-down
575                 //              behavior.
576                 // tags:
577                 //              protected
578                 return false;
579         },
580
581         ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
582
583         _setStyleAttr: function(/*String||Object*/ value){
584                 // summary:
585                 //              Sets the style attribute of the widget according to value,
586                 //              which is either a hash like {height: "5px", width: "3px"}
587                 //              or a plain string
588                 // description:
589                 //              Determines which node to set the style on based on style setting
590                 //              in attributeMap.
591                 // tags:
592                 //              protected
593
594                 var mapNode = this.domNode;
595
596                 // Note: technically we should revert any style setting made in a previous call
597                 // to his method, but that's difficult to keep track of.
598
599                 if(lang.isObject(value)){
600                         domStyle.set(mapNode, value);
601                 }else{
602                         if(mapNode.style.cssText){
603                                 mapNode.style.cssText += "; " + value;
604                         }else{
605                                 mapNode.style.cssText = value;
606                         }
607                 }
608
609                 this._set("style", value);
610         },
611
612         _attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
613                 // summary:
614                 //              Reflect a widget attribute (title, tabIndex, duration etc.) to
615                 //              the widget DOM, as specified by commands parameter.
616                 //              If commands isn't specified then it's looked up from attributeMap.
617                 //              Note some attributes like "type"
618                 //              cannot be processed this way as they are not mutable.
619                 //
620                 // tags:
621                 //              private
622
623                 commands = arguments.length >= 3 ? commands : this.attributeMap[attr];
624
625                 array.forEach(lang.isArray(commands) ? commands : [commands], function(command){
626
627                         // Get target node and what we are doing to that node
628                         var mapNode = this[command.node || command || "domNode"];       // DOM node
629                         var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute
630
631                         switch(type){
632                                 case "attribute":
633                                         if(lang.isFunction(value)){ // functions execute in the context of the widget
634                                                 value = lang.hitch(this, value);
635                                         }
636
637                                         // Get the name of the DOM node attribute; usually it's the same
638                                         // as the name of the attribute in the widget (attr), but can be overridden.
639                                         // Also maps handler names to lowercase, like onSubmit --> onsubmit
640                                         var attrName = command.attribute ? command.attribute :
641                                                 (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
642
643                                         domAttr.set(mapNode, attrName, value);
644                                         break;
645                                 case "innerText":
646                                         mapNode.innerHTML = "";
647                                         mapNode.appendChild(win.doc.createTextNode(value));
648                                         break;
649                                 case "innerHTML":
650                                         mapNode.innerHTML = value;
651                                         break;
652                                 case "class":
653                                         domClass.replace(mapNode, value, this[attr]);
654                                         break;
655                         }
656                 }, this);
657         },
658
659         get: function(name){
660                 // summary:
661                 //              Get a property from a widget.
662                 //      name:
663                 //              The property to get.
664                 // description:
665                 //              Get a named property from a widget. The property may
666                 //              potentially be retrieved via a getter method. If no getter is defined, this
667                 //              just retrieves the object's property.
668                 //
669                 //              For example, if the widget has properties `foo` and `bar`
670                 //              and a method named `_getFooAttr()`, calling:
671                 //              `myWidget.get("foo")` would be equivalent to calling
672                 //              `widget._getFooAttr()` and `myWidget.get("bar")`
673                 //              would be equivalent to the expression
674                 //              `widget.bar2`
675                 var names = this._getAttrNames(name);
676                 return this[names.g] ? this[names.g]() : this[name];
677         },
678
679         set: function(name, value){
680                 // summary:
681                 //              Set a property on a widget
682                 //      name:
683                 //              The property to set.
684                 //      value:
685                 //              The value to set in the property.
686                 // description:
687                 //              Sets named properties on a widget which may potentially be handled by a
688                 //              setter in the widget.
689                 //
690                 //              For example, if the widget has properties `foo` and `bar`
691                 //              and a method named `_setFooAttr()`, calling
692                 //              `myWidget.set("foo", "Howdy!")` would be equivalent to calling
693                 //              `widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)`
694                 //              would be equivalent to the statement `widget.bar = 3;`
695                 //
696                 //              set() may also be called with a hash of name/value pairs, ex:
697                 //
698                 //      |       myWidget.set({
699                 //      |               foo: "Howdy",
700                 //      |               bar: 3
701                 //      |       });
702                 //
703                 //      This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)`
704
705                 if(typeof name === "object"){
706                         for(var x in name){
707                                 this.set(x, name[x]);
708                         }
709                         return this;
710                 }
711                 var names = this._getAttrNames(name),
712                         setter = this[names.s];
713                 if(lang.isFunction(setter)){
714                         // use the explicit setter
715                         var result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
716                 }else{
717                         // Mapping from widget attribute to DOMNode attribute/value/etc.
718                         // Map according to:
719                         //              1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0)
720                         //              2. _setFooAttr: {...} type attribute in the widget (if one exists)
721                         //              3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick.
722                         // Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar
723                         // attribute name (ex: accept-charset attribute matches jsObject.acceptCharset).
724                         // Note also that Tree.focusNode() is a function not a DOMNode, so test for that.
725                         var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode",
726                                 tag = this[defaultNode].tagName,
727                                 attrsForTag = tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode])),
728                                 map =   name in this.attributeMap ? this.attributeMap[name] :
729                                                 names.s in this ? this[names.s] :
730                                                 ((names.l in attrsForTag && typeof value != "function") ||
731                                                         /^aria-|^data-|^role$/.test(name)) ? defaultNode : null;
732                         if(map != null){
733                                 this._attrToDom(name, value, map);
734                         }
735                         this._set(name, value);
736                 }
737                 return result || this;
738         },
739
740         _attrPairNames: {},             // shared between all widgets
741         _getAttrNames: function(name){
742                 // summary:
743                 //              Helper function for get() and set().
744                 //              Caches attribute name values so we don't do the string ops every time.
745                 // tags:
746                 //              private
747
748                 var apn = this._attrPairNames;
749                 if(apn[name]){ return apn[name]; }
750                 var uc = name.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); });
751                 return (apn[name] = {
752                         n: name+"Node",
753                         s: "_set"+uc+"Attr",    // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr
754                         g: "_get"+uc+"Attr",
755                         l: uc.toLowerCase()             // lowercase name w/out dashes, ex: acceptcharset
756                 });
757         },
758
759         _set: function(/*String*/ name, /*anything*/ value){
760                 // summary:
761                 //              Helper function to set new value for specified attribute, and call handlers
762                 //              registered with watch() if the value has changed.
763                 var oldValue = this[name];
764                 this[name] = value;
765                 if(this._watchCallbacks && this._created && value !== oldValue){
766                         this._watchCallbacks(name, oldValue, value);
767                 }
768         },
769
770         on: function(/*String*/ type, /*Function*/ func){
771                 // summary:
772                 //              Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }).
773                 // description:
774                 //              Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`.
775                 //              Note that the function is not run in any particular scope, so if (for example) you want it to run in the
776                 //              widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`.
777
778                 return aspect.after(this, this._onMap(type), func, true);
779         },
780
781         _onMap: function(/*String*/ type){
782                 // summary:
783                 //              Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove")
784                 var ctor = this.constructor, map = ctor._onMap;
785                 if(!map){
786                         map = (ctor._onMap = {});
787                         for(var attr in ctor.prototype){
788                                 if(/^on/.test(attr)){
789                                         map[attr.replace(/^on/, "").toLowerCase()] = attr;
790                                 }
791                         }
792                 }
793                 return map[type.toLowerCase()]; // String
794         },
795
796         toString: function(){
797                 // summary:
798                 //              Returns a string that represents the widget
799                 // description:
800                 //              When a widget is cast to a string, this method will be used to generate the
801                 //              output. Currently, it does not implement any sort of reversible
802                 //              serialization.
803                 return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
804         },
805
806         getChildren: function(){
807                 // summary:
808                 //              Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
809                 //              Does not return nested widgets, nor widgets that are part of this widget's template.
810                 return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit._Widget[]
811         },
812
813         getParent: function(){
814                 // summary:
815                 //              Returns the parent widget of this widget
816                 return registry.getEnclosingWidget(this.domNode.parentNode);
817         },
818
819         connect: function(
820                         /*Object|null*/ obj,
821                         /*String|Function*/ event,
822                         /*String|Function*/ method){
823                 // summary:
824                 //              Connects specified obj/event to specified method of this object
825                 //              and registers for disconnect() on widget destroy.
826                 // description:
827                 //              Provide widget-specific analog to dojo.connect, except with the
828                 //              implicit use of this widget as the target object.
829                 //              Events connected with `this.connect` are disconnected upon
830                 //              destruction.
831                 // returns:
832                 //              A handle that can be passed to `disconnect` in order to disconnect before
833                 //              the widget is destroyed.
834                 // example:
835                 //      |       var btn = new dijit.form.Button();
836                 //      |       // when foo.bar() is called, call the listener we're going to
837                 //      |       // provide in the scope of btn
838                 //      |       btn.connect(foo, "bar", function(){
839                 //      |               console.debug(this.toString());
840                 //      |       });
841                 // tags:
842                 //              protected
843
844                 var handle = connect.connect(obj, event, this, method);
845                 this._connects.push(handle);
846                 return handle;          // _Widget.Handle
847         },
848
849         disconnect: function(handle){
850                 // summary:
851                 //              Disconnects handle created by `connect`.
852                 //              Also removes handle from this widget's list of connects.
853                 // tags:
854                 //              protected
855                 var i = array.indexOf(this._connects, handle);
856                 if(i != -1){
857                         handle.remove();
858                         this._connects.splice(i, 1);
859                 }
860         },
861
862         subscribe: function(t, method){
863                 // summary:
864                 //              Subscribes to the specified topic and calls the specified method
865                 //              of this object and registers for unsubscribe() on widget destroy.
866                 // description:
867                 //              Provide widget-specific analog to dojo.subscribe, except with the
868                 //              implicit use of this widget as the target object.
869                 // t: String
870                 //              The topic
871                 // method: Function
872                 //              The callback
873                 // example:
874                 //      |       var btn = new dijit.form.Button();
875                 //      |       // when /my/topic is published, this button changes its label to
876                 //      |   // be the parameter of the topic.
877                 //      |       btn.subscribe("/my/topic", function(v){
878                 //      |               this.set("label", v);
879                 //      |       });
880                 // tags:
881                 //              protected
882                 var handle = topic.subscribe(t, lang.hitch(this, method));
883                 this._connects.push(handle);
884                 return handle;          // _Widget.Handle
885         },
886
887         unsubscribe: function(/*Object*/ handle){
888                 // summary:
889                 //              Unsubscribes handle created by this.subscribe.
890                 //              Also removes handle from this widget's list of subscriptions
891                 // tags:
892                 //              protected
893                 this.disconnect(handle);
894         },
895
896         isLeftToRight: function(){
897                 // summary:
898                 //              Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
899                 // tags:
900                 //              protected
901                 return this.dir ? (this.dir == "ltr") : domGeometry.isBodyLtr(); //Boolean
902         },
903
904         isFocusable: function(){
905                 // summary:
906                 //              Return true if this widget can currently be focused
907                 //              and false if not
908                 return this.focus && (domStyle.get(this.domNode, "display") != "none");
909         },
910
911         placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
912                 // summary:
913                 //              Place this widget's domNode reference somewhere in the DOM based
914                 //              on standard domConstruct.place conventions, or passing a Widget reference that
915                 //              contains and addChild member.
916                 //
917                 // description:
918                 //              A convenience function provided in all _Widgets, providing a simple
919                 //              shorthand mechanism to put an existing (or newly created) Widget
920                 //              somewhere in the dom, and allow chaining.
921                 //
922                 // reference:
923                 //              The String id of a domNode, a domNode reference, or a reference to a Widget possessing
924                 //              an addChild method.
925                 //
926                 // position:
927                 //              If passed a string or domNode reference, the position argument
928                 //              accepts a string just as domConstruct.place does, one of: "first", "last",
929                 //              "before", or "after".
930                 //
931                 //              If passed a _Widget reference, and that widget reference has an ".addChild" method,
932                 //              it will be called passing this widget instance into that method, supplying the optional
933                 //              position index passed.
934                 //
935                 // returns:
936                 //              dijit._Widget
937                 //              Provides a useful return of the newly created dijit._Widget instance so you
938                 //              can "chain" this function by instantiating, placing, then saving the return value
939                 //              to a variable.
940                 //
941                 // example:
942                 // |    // create a Button with no srcNodeRef, and place it in the body:
943                 // |    var button = new dijit.form.Button({ label:"click" }).placeAt(win.body());
944                 // |    // now, 'button' is still the widget reference to the newly created button
945                 // |    button.on("click", function(e){ console.log('click'); }));
946                 //
947                 // example:
948                 // |    // create a button out of a node with id="src" and append it to id="wrapper":
949                 // |    var button = new dijit.form.Button({},"src").placeAt("wrapper");
950                 //
951                 // example:
952                 // |    // place a new button as the first element of some div
953                 // |    var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
954                 //
955                 // example:
956                 // |    // create a contentpane and add it to a TabContainer
957                 // |    var tc = dijit.byId("myTabs");
958                 // |    new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
959
960                 if(reference.declaredClass && reference.addChild){
961                         reference.addChild(this, position);
962                 }else{
963                         domConstruct.place(this.domNode, reference, position);
964                 }
965                 return this;
966         },
967
968         getTextDir: function(/*String*/ text,/*String*/ originalDir){
969                 // summary:
970                 //              Return direction of the text.
971                 //              The function overridden in the _BidiSupport module,
972                 //              its main purpose is to calculate the direction of the
973                 //              text, if was defined by the programmer through textDir.
974                 //      tags:
975                 //              protected.
976                 return originalDir;
977         },
978
979         applyTextDir: function(/*===== element, text =====*/){
980                 // summary:
981                 //              The function overridden in the _BidiSupport module,
982                 //              originally used for setting element.dir according to this.textDir.
983                 //              In this case does nothing.
984                 // element: DOMNode
985                 // text: String
986                 // tags:
987                 //              protected.
988         },
989
990         defer: function(fcn, delay){ 
991                 // summary:
992                 //              Wrapper to setTimeout to avoid deferred functions executing
993                 //              after the originating widget has been destroyed.
994                 //              Returns an object handle with a remove method (that returns null) (replaces clearTimeout).
995                 // fcn: function reference
996                 // delay: Optional number (defaults to 0)
997                 // tags:
998                 //              protected.
999                 var timer = setTimeout(lang.hitch(this, 
1000                         function(){ 
1001                                 timer = null;
1002                                 if(!this._destroyed){ 
1003                                         lang.hitch(this, fcn)(); 
1004                                 } 
1005                         }),
1006                         delay || 0
1007                 );
1008                 return {
1009                         remove: function(){
1010                                         if(timer){
1011                                                 clearTimeout(timer);
1012                                                 timer = null;
1013                                         }
1014                                         return null; // so this works well: handle = handle.remove();
1015                                 }
1016                 };
1017         }
1018 });
1019
1020 });