source: Dev/trunk/src/client/dijit/_WidgetBase.js @ 513

Last change on this file since 513 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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