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