source: Dev/branches/rest-dojo-ui/client/dijit/_WidgetBase.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 34.0 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.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/*=====
27var 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.
36if(!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": { ...
45var tagAttrs = {};
46function getAttrs(obj){
47        var ret = {};
48        for(var attr in obj){
49                ret[attr.toLowerCase()] = true;
50        }
51        return ret;
52}
53
54function 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
65return 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
991});
Note: See TracBrowser for help on using the repository browser.