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