]>
Commit | Line | Data |
---|---|---|
81bea17a AD |
1 | /* |
2 | Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. | |
3 | Available via Academic Free License >= 2.1 OR the modified BSD license. | |
4 | see: http://dojotoolkit.org/license for details | |
5 | */ | |
6 | ||
7 | ||
8 | if(!dojo._hasResource["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. | |
9 | dojo._hasResource["dijit._WidgetBase"] = true; | |
10 | dojo.provide("dijit._WidgetBase"); | |
11 | dojo.require("dijit._base.manager"); | |
12 | dojo.require("dojo.Stateful"); | |
13 | ||
14 | ||
15 | (function(){ | |
16 | ||
17 | dojo.declare("dijit._WidgetBase", dojo.Stateful, { | |
18 | // summary: | |
19 | // Future base class for all Dijit widgets. | |
20 | // _Widget extends this class adding support for various features needed by desktop. | |
21 | ||
22 | // id: [const] String | |
23 | // A unique, opaque ID string that can be assigned by users or by the | |
24 | // system. If the developer passes an ID which is known not to be | |
25 | // unique, the specified ID is ignored and the system-generated ID is | |
26 | // used instead. | |
27 | id: "", | |
28 | ||
29 | // lang: [const] String | |
30 | // Rarely used. Overrides the default Dojo locale used to render this widget, | |
31 | // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute. | |
32 | // Value must be among the list of locales specified during by the Dojo bootstrap, | |
33 | // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us). | |
34 | lang: "", | |
35 | ||
36 | // dir: [const] String | |
37 | // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir) | |
38 | // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's | |
39 | // default direction. | |
40 | dir: "", | |
41 | ||
42 | // class: String | |
43 | // HTML class attribute | |
44 | "class": "", | |
45 | ||
46 | // style: String||Object | |
47 | // HTML style attributes as cssText string or name/value hash | |
48 | style: "", | |
49 | ||
50 | // title: String | |
51 | // HTML title attribute. | |
52 | // | |
53 | // For form widgets this specifies a tooltip to display when hovering over | |
54 | // the widget (just like the native HTML title attribute). | |
55 | // | |
56 | // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer, | |
57 | // etc., it's used to specify the tab label, accordion pane title, etc. | |
58 | title: "", | |
59 | ||
60 | // tooltip: String | |
61 | // When this widget's title attribute is used to for a tab label, accordion pane title, etc., | |
62 | // this specifies the tooltip to appear when the mouse is hovered over that text. | |
63 | tooltip: "", | |
64 | ||
65 | // baseClass: [protected] String | |
66 | // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate | |
67 | // widget state. | |
68 | baseClass: "", | |
69 | ||
70 | // srcNodeRef: [readonly] DomNode | |
71 | // pointer to original DOM node | |
72 | srcNodeRef: null, | |
73 | ||
74 | // domNode: [readonly] DomNode | |
75 | // This is our visible representation of the widget! Other DOM | |
76 | // Nodes may by assigned to other properties, usually through the | |
77 | // template system's dojoAttachPoint syntax, but the domNode | |
78 | // property is the canonical "top level" node in widget UI. | |
79 | domNode: null, | |
80 | ||
81 | // containerNode: [readonly] DomNode | |
82 | // Designates where children of the source DOM node will be placed. | |
83 | // "Children" in this case refers to both DOM nodes and widgets. | |
84 | // For example, for myWidget: | |
85 | // | |
86 | // | <div dojoType=myWidget> | |
87 | // | <b> here's a plain DOM node | |
88 | // | <span dojoType=subWidget>and a widget</span> | |
89 | // | <i> and another plain DOM node </i> | |
90 | // | </div> | |
91 | // | |
92 | // containerNode would point to: | |
93 | // | |
94 | // | <b> here's a plain DOM node | |
95 | // | <span dojoType=subWidget>and a widget</span> | |
96 | // | <i> and another plain DOM node </i> | |
97 | // | |
98 | // In templated widgets, "containerNode" is set via a | |
99 | // dojoAttachPoint assignment. | |
100 | // | |
101 | // containerNode must be defined for any widget that accepts innerHTML | |
102 | // (like ContentPane or BorderContainer or even Button), and conversely | |
103 | // is null for widgets that don't, like TextBox. | |
104 | containerNode: null, | |
105 | ||
106 | /*===== | |
107 | // _started: Boolean | |
108 | // startup() has completed. | |
109 | _started: false, | |
110 | =====*/ | |
111 | ||
112 | // attributeMap: [protected] Object | |
113 | // attributeMap sets up a "binding" between attributes (aka properties) | |
114 | // of the widget and the widget's DOM. | |
115 | // Changes to widget attributes listed in attributeMap will be | |
116 | // reflected into the DOM. | |
117 | // | |
118 | // For example, calling set('title', 'hello') | |
119 | // on a TitlePane will automatically cause the TitlePane's DOM to update | |
120 | // with the new title. | |
121 | // | |
122 | // attributeMap is a hash where the key is an attribute of the widget, | |
123 | // and the value reflects a binding to a: | |
124 | // | |
125 | // - DOM node attribute | |
126 | // | focus: {node: "focusNode", type: "attribute"} | |
127 | // Maps this.focus to this.focusNode.focus | |
128 | // | |
129 | // - DOM node innerHTML | |
130 | // | title: { node: "titleNode", type: "innerHTML" } | |
131 | // Maps this.title to this.titleNode.innerHTML | |
132 | // | |
133 | // - DOM node innerText | |
134 | // | title: { node: "titleNode", type: "innerText" } | |
135 | // Maps this.title to this.titleNode.innerText | |
136 | // | |
137 | // - DOM node CSS class | |
138 | // | myClass: { node: "domNode", type: "class" } | |
139 | // Maps this.myClass to this.domNode.className | |
140 | // | |
141 | // If the value is an array, then each element in the array matches one of the | |
142 | // formats of the above list. | |
143 | // | |
144 | // There are also some shorthands for backwards compatibility: | |
145 | // - string --> { node: string, type: "attribute" }, for example: | |
146 | // | "focusNode" ---> { node: "focusNode", type: "attribute" } | |
147 | // - "" --> { node: "domNode", type: "attribute" } | |
148 | attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""}, | |
149 | ||
150 | // _blankGif: [protected] String | |
151 | // Path to a blank 1x1 image. | |
152 | // Used by <img> nodes in templates that really get their image via CSS background-image. | |
153 | _blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")).toString(), | |
154 | ||
155 | //////////// INITIALIZATION METHODS /////////////////////////////////////// | |
156 | ||
157 | postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){ | |
158 | // summary: | |
159 | // Kicks off widget instantiation. See create() for details. | |
160 | // tags: | |
161 | // private | |
162 | this.create(params, srcNodeRef); | |
163 | }, | |
164 | ||
165 | create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ | |
166 | // summary: | |
167 | // Kick off the life-cycle of a widget | |
168 | // params: | |
169 | // Hash of initialization parameters for widget, including | |
170 | // scalar values (like title, duration etc.) and functions, | |
171 | // typically callbacks like onClick. | |
172 | // srcNodeRef: | |
173 | // If a srcNodeRef (DOM node) is specified: | |
174 | // - use srcNodeRef.innerHTML as my contents | |
175 | // - if this is a behavioral widget then apply behavior | |
176 | // to that srcNodeRef | |
177 | // - otherwise, replace srcNodeRef with my generated DOM | |
178 | // tree | |
179 | // description: | |
180 | // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate, | |
181 | // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget | |
182 | // for a discussion of the widget creation lifecycle. | |
183 | // | |
184 | // Of course, adventurous developers could override create entirely, but this should | |
185 | // only be done as a last resort. | |
186 | // tags: | |
187 | // private | |
188 | ||
189 | // store pointer to original DOM tree | |
190 | this.srcNodeRef = dojo.byId(srcNodeRef); | |
191 | ||
192 | // For garbage collection. An array of handles returned by Widget.connect() | |
193 | // Each handle returned from Widget.connect() is an array of handles from dojo.connect() | |
194 | this._connects = []; | |
195 | ||
196 | // For garbage collection. An array of handles returned by Widget.subscribe() | |
197 | // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe() | |
198 | this._subscribes = []; | |
199 | ||
200 | // mix in our passed parameters | |
201 | if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; } | |
202 | if(params){ | |
203 | this.params = params; | |
204 | dojo._mixin(this, params); | |
205 | } | |
206 | this.postMixInProperties(); | |
207 | ||
208 | // generate an id for the widget if one wasn't specified | |
209 | // (be sure to do this before buildRendering() because that function might | |
210 | // expect the id to be there.) | |
211 | if(!this.id){ | |
212 | this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_")); | |
213 | } | |
214 | dijit.registry.add(this); | |
215 | ||
216 | this.buildRendering(); | |
217 | ||
218 | if(this.domNode){ | |
219 | // Copy attributes listed in attributeMap into the [newly created] DOM for the widget. | |
220 | // Also calls custom setters for all attributes with custom setters. | |
221 | this._applyAttributes(); | |
222 | ||
223 | // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree. | |
224 | // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the | |
225 | // widget being attached to the DOM since it isn't when a widget is created programmatically like | |
226 | // new MyWidget({}). See #11635. | |
227 | var source = this.srcNodeRef; | |
228 | if(source && source.parentNode && this.domNode !== source){ | |
229 | source.parentNode.replaceChild(this.domNode, source); | |
230 | } | |
231 | } | |
232 | ||
233 | if(this.domNode){ | |
234 | // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId", | |
235 | // assuming that dojo._scopeName even exists in 2.0 | |
236 | this.domNode.setAttribute("widgetId", this.id); | |
237 | } | |
238 | this.postCreate(); | |
239 | ||
240 | // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC. | |
241 | if(this.srcNodeRef && !this.srcNodeRef.parentNode){ | |
242 | delete this.srcNodeRef; | |
243 | } | |
244 | ||
245 | this._created = true; | |
246 | }, | |
247 | ||
248 | _applyAttributes: function(){ | |
249 | // summary: | |
250 | // Step during widget creation to copy all widget attributes to the | |
251 | // DOM as per attributeMap and _setXXXAttr functions. | |
252 | // description: | |
253 | // Skips over blank/false attribute values, unless they were explicitly specified | |
254 | // as parameters to the widget, since those are the default anyway, | |
255 | // and setting tabIndex="" is different than not setting tabIndex at all. | |
256 | // | |
257 | // It processes the attributes in the attribute map first, and then | |
258 | // it goes through and processes the attributes for the _setXXXAttr | |
259 | // functions that have been specified | |
260 | // tags: | |
261 | // private | |
262 | var condAttrApply = function(attr, scope){ | |
263 | if((scope.params && attr in scope.params) || scope[attr]){ | |
264 | scope.set(attr, scope[attr]); | |
265 | } | |
266 | }; | |
267 | ||
268 | // Do the attributes in attributeMap | |
269 | for(var attr in this.attributeMap){ | |
270 | condAttrApply(attr, this); | |
271 | } | |
272 | ||
273 | // And also any attributes with custom setters | |
274 | dojo.forEach(this._getSetterAttributes(), function(a){ | |
275 | if(!(a in this.attributeMap)){ | |
276 | condAttrApply(a, this); | |
277 | } | |
278 | }, this); | |
279 | }, | |
280 | ||
281 | _getSetterAttributes: function(){ | |
282 | // summary: | |
283 | // Returns list of attributes with custom setters for this widget | |
284 | var ctor = this.constructor; | |
285 | if(!ctor._setterAttrs){ | |
286 | var r = (ctor._setterAttrs = []), | |
287 | attrs, | |
288 | proto = ctor.prototype; | |
289 | for(var fxName in proto){ | |
290 | if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){ | |
291 | r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1)); | |
292 | } | |
293 | } | |
294 | } | |
295 | return ctor._setterAttrs; // String[] | |
296 | }, | |
297 | ||
298 | postMixInProperties: function(){ | |
299 | // summary: | |
300 | // Called after the parameters to the widget have been read-in, | |
301 | // but before the widget template is instantiated. Especially | |
302 | // useful to set properties that are referenced in the widget | |
303 | // template. | |
304 | // tags: | |
305 | // protected | |
306 | }, | |
307 | ||
308 | buildRendering: function(){ | |
309 | // summary: | |
310 | // Construct the UI for this widget, setting this.domNode | |
311 | // description: | |
312 | // Most widgets will mixin `dijit._Templated`, which implements this | |
313 | // method. | |
314 | // tags: | |
315 | // protected | |
316 | ||
317 | if(!this.domNode){ | |
318 | // Create root node if it wasn't created by _Templated | |
319 | this.domNode = this.srcNodeRef || dojo.create('div'); | |
320 | } | |
321 | ||
322 | // baseClass is a single class name or occasionally a space-separated list of names. | |
323 | // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix. | |
324 | // TODO: make baseClass custom setter | |
325 | if(this.baseClass){ | |
326 | var classes = this.baseClass.split(" "); | |
327 | if(!this.isLeftToRight()){ | |
328 | classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; })); | |
329 | } | |
330 | dojo.addClass(this.domNode, classes); | |
331 | } | |
332 | }, | |
333 | ||
334 | postCreate: function(){ | |
335 | // summary: | |
336 | // Processing after the DOM fragment is created | |
337 | // description: | |
338 | // Called after the DOM fragment has been created, but not necessarily | |
339 | // added to the document. Do not include any operations which rely on | |
340 | // node dimensions or placement. | |
341 | // tags: | |
342 | // protected | |
343 | }, | |
344 | ||
345 | startup: function(){ | |
346 | // summary: | |
347 | // Processing after the DOM fragment is added to the document | |
348 | // description: | |
349 | // Called after a widget and its children have been created and added to the page, | |
350 | // and all related widgets have finished their create() cycle, up through postCreate(). | |
351 | // This is useful for composite widgets that need to control or layout sub-widgets. | |
352 | // Many layout widgets can use this as a wiring phase. | |
353 | this._started = true; | |
354 | }, | |
355 | ||
356 | //////////// DESTROY FUNCTIONS //////////////////////////////// | |
357 | ||
358 | destroyRecursive: function(/*Boolean?*/ preserveDom){ | |
359 | // summary: | |
360 | // Destroy this widget and its descendants | |
361 | // description: | |
362 | // This is the generic "destructor" function that all widget users | |
363 | // should call to cleanly discard with a widget. Once a widget is | |
364 | // destroyed, it is removed from the manager object. | |
365 | // preserveDom: | |
366 | // If true, this method will leave the original DOM structure | |
367 | // alone of descendant Widgets. Note: This will NOT work with | |
368 | // dijit._Templated widgets. | |
369 | ||
370 | this._beingDestroyed = true; | |
371 | this.destroyDescendants(preserveDom); | |
372 | this.destroy(preserveDom); | |
373 | }, | |
374 | ||
375 | destroy: function(/*Boolean*/ preserveDom){ | |
376 | // summary: | |
377 | // Destroy this widget, but not its descendants. | |
378 | // This method will, however, destroy internal widgets such as those used within a template. | |
379 | // preserveDom: Boolean | |
380 | // If true, this method will leave the original DOM structure alone. | |
381 | // Note: This will not yet work with _Templated widgets | |
382 | ||
383 | this._beingDestroyed = true; | |
384 | this.uninitialize(); | |
385 | var d = dojo, | |
386 | dfe = d.forEach, | |
387 | dun = d.unsubscribe; | |
388 | dfe(this._connects, function(array){ | |
389 | dfe(array, d.disconnect); | |
390 | }); | |
391 | dfe(this._subscribes, function(handle){ | |
392 | dun(handle); | |
393 | }); | |
394 | ||
395 | // destroy widgets created as part of template, etc. | |
396 | dfe(this._supportingWidgets || [], function(w){ | |
397 | if(w.destroyRecursive){ | |
398 | w.destroyRecursive(); | |
399 | }else if(w.destroy){ | |
400 | w.destroy(); | |
401 | } | |
402 | }); | |
403 | ||
404 | this.destroyRendering(preserveDom); | |
405 | dijit.registry.remove(this.id); | |
406 | this._destroyed = true; | |
407 | }, | |
408 | ||
409 | destroyRendering: function(/*Boolean?*/ preserveDom){ | |
410 | // summary: | |
411 | // Destroys the DOM nodes associated with this widget | |
412 | // preserveDom: | |
413 | // If true, this method will leave the original DOM structure alone | |
414 | // during tear-down. Note: this will not work with _Templated | |
415 | // widgets yet. | |
416 | // tags: | |
417 | // protected | |
418 | ||
419 | if(this.bgIframe){ | |
420 | this.bgIframe.destroy(preserveDom); | |
421 | delete this.bgIframe; | |
422 | } | |
423 | ||
424 | if(this.domNode){ | |
425 | if(preserveDom){ | |
426 | dojo.removeAttr(this.domNode, "widgetId"); | |
427 | }else{ | |
428 | dojo.destroy(this.domNode); | |
429 | } | |
430 | delete this.domNode; | |
431 | } | |
432 | ||
433 | if(this.srcNodeRef){ | |
434 | if(!preserveDom){ | |
435 | dojo.destroy(this.srcNodeRef); | |
436 | } | |
437 | delete this.srcNodeRef; | |
438 | } | |
439 | }, | |
440 | ||
441 | destroyDescendants: function(/*Boolean?*/ preserveDom){ | |
442 | // summary: | |
443 | // Recursively destroy the children of this widget and their | |
444 | // descendants. | |
445 | // preserveDom: | |
446 | // If true, the preserveDom attribute is passed to all descendant | |
447 | // widget's .destroy() method. Not for use with _Templated | |
448 | // widgets. | |
449 | ||
450 | // get all direct descendants and destroy them recursively | |
451 | dojo.forEach(this.getChildren(), function(widget){ | |
452 | if(widget.destroyRecursive){ | |
453 | widget.destroyRecursive(preserveDom); | |
454 | } | |
455 | }); | |
456 | }, | |
457 | ||
458 | uninitialize: function(){ | |
459 | // summary: | |
460 | // Stub function. Override to implement custom widget tear-down | |
461 | // behavior. | |
462 | // tags: | |
463 | // protected | |
464 | return false; | |
465 | }, | |
466 | ||
467 | ////////////////// GET/SET, CUSTOM SETTERS, ETC. /////////////////// | |
468 | ||
469 | _setClassAttr: function(/*String*/ value){ | |
470 | // summary: | |
471 | // Custom setter for the CSS "class" attribute | |
472 | // tags: | |
473 | // protected | |
474 | var mapNode = this[this.attributeMap["class"] || 'domNode']; | |
475 | dojo.replaceClass(mapNode, value, this["class"]); | |
476 | this._set("class", value); | |
477 | }, | |
478 | ||
479 | _setStyleAttr: function(/*String||Object*/ value){ | |
480 | // summary: | |
481 | // Sets the style attribute of the widget according to value, | |
482 | // which is either a hash like {height: "5px", width: "3px"} | |
483 | // or a plain string | |
484 | // description: | |
485 | // Determines which node to set the style on based on style setting | |
486 | // in attributeMap. | |
487 | // tags: | |
488 | // protected | |
489 | ||
490 | var mapNode = this[this.attributeMap.style || 'domNode']; | |
491 | ||
492 | // Note: technically we should revert any style setting made in a previous call | |
493 | // to his method, but that's difficult to keep track of. | |
494 | ||
495 | if(dojo.isObject(value)){ | |
496 | dojo.style(mapNode, value); | |
497 | }else{ | |
498 | if(mapNode.style.cssText){ | |
499 | mapNode.style.cssText += "; " + value; | |
500 | }else{ | |
501 | mapNode.style.cssText = value; | |
502 | } | |
503 | } | |
504 | ||
505 | this._set("style", value); | |
506 | }, | |
507 | ||
508 | _attrToDom: function(/*String*/ attr, /*String*/ value){ | |
509 | // summary: | |
510 | // Reflect a widget attribute (title, tabIndex, duration etc.) to | |
511 | // the widget DOM, as specified in attributeMap. | |
512 | // Note some attributes like "type" | |
513 | // cannot be processed this way as they are not mutable. | |
514 | // | |
515 | // tags: | |
516 | // private | |
517 | ||
518 | var commands = this.attributeMap[attr]; | |
519 | dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){ | |
520 | ||
521 | // Get target node and what we are doing to that node | |
522 | var mapNode = this[command.node || command || "domNode"]; // DOM node | |
523 | var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute | |
524 | ||
525 | switch(type){ | |
526 | case "attribute": | |
527 | if(dojo.isFunction(value)){ // functions execute in the context of the widget | |
528 | value = dojo.hitch(this, value); | |
529 | } | |
530 | ||
531 | // Get the name of the DOM node attribute; usually it's the same | |
532 | // as the name of the attribute in the widget (attr), but can be overridden. | |
533 | // Also maps handler names to lowercase, like onSubmit --> onsubmit | |
534 | var attrName = command.attribute ? command.attribute : | |
535 | (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr); | |
536 | ||
537 | dojo.attr(mapNode, attrName, value); | |
538 | break; | |
539 | case "innerText": | |
540 | mapNode.innerHTML = ""; | |
541 | mapNode.appendChild(dojo.doc.createTextNode(value)); | |
542 | break; | |
543 | case "innerHTML": | |
544 | mapNode.innerHTML = value; | |
545 | break; | |
546 | case "class": | |
547 | dojo.replaceClass(mapNode, value, this[attr]); | |
548 | break; | |
549 | } | |
550 | }, this); | |
551 | }, | |
552 | ||
553 | get: function(name){ | |
554 | // summary: | |
555 | // Get a property from a widget. | |
556 | // name: | |
557 | // The property to get. | |
558 | // description: | |
559 | // Get a named property from a widget. The property may | |
560 | // potentially be retrieved via a getter method. If no getter is defined, this | |
561 | // just retrieves the object's property. | |
562 | // For example, if the widget has a properties "foo" | |
563 | // and "bar" and a method named "_getFooAttr", calling: | |
564 | // | myWidget.get("foo"); | |
565 | // would be equivalent to writing: | |
566 | // | widget._getFooAttr(); | |
567 | // and: | |
568 | // | myWidget.get("bar"); | |
569 | // would be equivalent to writing: | |
570 | // | widget.bar; | |
571 | var names = this._getAttrNames(name); | |
572 | return this[names.g] ? this[names.g]() : this[name]; | |
573 | }, | |
574 | ||
575 | set: function(name, value){ | |
576 | // summary: | |
577 | // Set a property on a widget | |
578 | // name: | |
579 | // The property to set. | |
580 | // value: | |
581 | // The value to set in the property. | |
582 | // description: | |
583 | // Sets named properties on a widget which may potentially be handled by a | |
584 | // setter in the widget. | |
585 | // For example, if the widget has a properties "foo" | |
586 | // and "bar" and a method named "_setFooAttr", calling: | |
587 | // | myWidget.set("foo", "Howdy!"); | |
588 | // would be equivalent to writing: | |
589 | // | widget._setFooAttr("Howdy!"); | |
590 | // and: | |
591 | // | myWidget.set("bar", 3); | |
592 | // would be equivalent to writing: | |
593 | // | widget.bar = 3; | |
594 | // | |
595 | // set() may also be called with a hash of name/value pairs, ex: | |
596 | // | myWidget.set({ | |
597 | // | foo: "Howdy", | |
598 | // | bar: 3 | |
599 | // | }) | |
600 | // This is equivalent to calling set(foo, "Howdy") and set(bar, 3) | |
601 | ||
602 | if(typeof name === "object"){ | |
603 | for(var x in name){ | |
604 | this.set(x, name[x]); | |
605 | } | |
606 | return this; | |
607 | } | |
608 | var names = this._getAttrNames(name); | |
609 | if(this[names.s]){ | |
610 | // use the explicit setter | |
611 | var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1)); | |
612 | }else{ | |
613 | // if param is specified as DOM node attribute, copy it | |
614 | if(name in this.attributeMap){ | |
615 | this._attrToDom(name, value); | |
616 | } | |
617 | this._set(name, value); | |
618 | } | |
619 | return result || this; | |
620 | }, | |
621 | ||
622 | _attrPairNames: {}, // shared between all widgets | |
623 | _getAttrNames: function(name){ | |
624 | // summary: | |
625 | // Helper function for get() and set(). | |
626 | // Caches attribute name values so we don't do the string ops every time. | |
627 | // tags: | |
628 | // private | |
629 | ||
630 | var apn = this._attrPairNames; | |
631 | if(apn[name]){ return apn[name]; } | |
632 | var uc = name.charAt(0).toUpperCase() + name.substr(1); | |
633 | return (apn[name] = { | |
634 | n: name+"Node", | |
635 | s: "_set"+uc+"Attr", | |
636 | g: "_get"+uc+"Attr" | |
637 | }); | |
638 | }, | |
639 | ||
640 | _set: function(/*String*/ name, /*anything*/ value){ | |
641 | // summary: | |
642 | // Helper function to set new value for specified attribute, and call handlers | |
643 | // registered with watch() if the value has changed. | |
644 | var oldValue = this[name]; | |
645 | this[name] = value; | |
646 | if(this._watchCallbacks && this._created && value !== oldValue){ | |
647 | this._watchCallbacks(name, oldValue, value); | |
648 | } | |
649 | }, | |
650 | ||
651 | toString: function(){ | |
652 | // summary: | |
653 | // Returns a string that represents the widget | |
654 | // description: | |
655 | // When a widget is cast to a string, this method will be used to generate the | |
656 | // output. Currently, it does not implement any sort of reversible | |
657 | // serialization. | |
658 | return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String | |
659 | }, | |
660 | ||
661 | getDescendants: function(){ | |
662 | // summary: | |
663 | // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. | |
664 | // This method should generally be avoided as it returns widgets declared in templates, which are | |
665 | // supposed to be internal/hidden, but it's left here for back-compat reasons. | |
666 | ||
667 | return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[] | |
668 | }, | |
669 | ||
670 | getChildren: function(){ | |
671 | // summary: | |
672 | // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. | |
673 | // Does not return nested widgets, nor widgets that are part of this widget's template. | |
674 | return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[] | |
675 | }, | |
676 | ||
677 | connect: function( | |
678 | /*Object|null*/ obj, | |
679 | /*String|Function*/ event, | |
680 | /*String|Function*/ method){ | |
681 | // summary: | |
682 | // Connects specified obj/event to specified method of this object | |
683 | // and registers for disconnect() on widget destroy. | |
684 | // description: | |
685 | // Provide widget-specific analog to dojo.connect, except with the | |
686 | // implicit use of this widget as the target object. | |
687 | // Events connected with `this.connect` are disconnected upon | |
688 | // destruction. | |
689 | // returns: | |
690 | // A handle that can be passed to `disconnect` in order to disconnect before | |
691 | // the widget is destroyed. | |
692 | // example: | |
693 | // | var btn = new dijit.form.Button(); | |
694 | // | // when foo.bar() is called, call the listener we're going to | |
695 | // | // provide in the scope of btn | |
696 | // | btn.connect(foo, "bar", function(){ | |
697 | // | console.debug(this.toString()); | |
698 | // | }); | |
699 | // tags: | |
700 | // protected | |
701 | ||
702 | var handles = [dojo._connect(obj, event, this, method)]; | |
703 | this._connects.push(handles); | |
704 | return handles; // _Widget.Handle | |
705 | }, | |
706 | ||
707 | disconnect: function(/* _Widget.Handle */ handles){ | |
708 | // summary: | |
709 | // Disconnects handle created by `connect`. | |
710 | // Also removes handle from this widget's list of connects. | |
711 | // tags: | |
712 | // protected | |
713 | for(var i=0; i<this._connects.length; i++){ | |
714 | if(this._connects[i] == handles){ | |
715 | dojo.forEach(handles, dojo.disconnect); | |
716 | this._connects.splice(i, 1); | |
717 | return; | |
718 | } | |
719 | } | |
720 | }, | |
721 | ||
722 | subscribe: function( | |
723 | /*String*/ topic, | |
724 | /*String|Function*/ method){ | |
725 | // summary: | |
726 | // Subscribes to the specified topic and calls the specified method | |
727 | // of this object and registers for unsubscribe() on widget destroy. | |
728 | // description: | |
729 | // Provide widget-specific analog to dojo.subscribe, except with the | |
730 | // implicit use of this widget as the target object. | |
731 | // example: | |
732 | // | var btn = new dijit.form.Button(); | |
733 | // | // when /my/topic is published, this button changes its label to | |
734 | // | // be the parameter of the topic. | |
735 | // | btn.subscribe("/my/topic", function(v){ | |
736 | // | this.set("label", v); | |
737 | // | }); | |
738 | var handle = dojo.subscribe(topic, this, method); | |
739 | ||
740 | // return handles for Any widget that may need them | |
741 | this._subscribes.push(handle); | |
742 | return handle; | |
743 | }, | |
744 | ||
745 | unsubscribe: function(/*Object*/ handle){ | |
746 | // summary: | |
747 | // Unsubscribes handle created by this.subscribe. | |
748 | // Also removes handle from this widget's list of subscriptions | |
749 | for(var i=0; i<this._subscribes.length; i++){ | |
750 | if(this._subscribes[i] == handle){ | |
751 | dojo.unsubscribe(handle); | |
752 | this._subscribes.splice(i, 1); | |
753 | return; | |
754 | } | |
755 | } | |
756 | }, | |
757 | ||
758 | isLeftToRight: function(){ | |
759 | // summary: | |
760 | // Return this widget's explicit or implicit orientation (true for LTR, false for RTL) | |
761 | // tags: | |
762 | // protected | |
763 | return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean | |
764 | }, | |
765 | ||
766 | placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){ | |
767 | // summary: | |
768 | // Place this widget's domNode reference somewhere in the DOM based | |
769 | // on standard dojo.place conventions, or passing a Widget reference that | |
770 | // contains and addChild member. | |
771 | // | |
772 | // description: | |
773 | // A convenience function provided in all _Widgets, providing a simple | |
774 | // shorthand mechanism to put an existing (or newly created) Widget | |
775 | // somewhere in the dom, and allow chaining. | |
776 | // | |
777 | // reference: | |
778 | // The String id of a domNode, a domNode reference, or a reference to a Widget posessing | |
779 | // an addChild method. | |
780 | // | |
781 | // position: | |
782 | // If passed a string or domNode reference, the position argument | |
783 | // accepts a string just as dojo.place does, one of: "first", "last", | |
784 | // "before", or "after". | |
785 | // | |
786 | // If passed a _Widget reference, and that widget reference has an ".addChild" method, | |
787 | // it will be called passing this widget instance into that method, supplying the optional | |
788 | // position index passed. | |
789 | // | |
790 | // returns: | |
791 | // dijit._Widget | |
792 | // Provides a useful return of the newly created dijit._Widget instance so you | |
793 | // can "chain" this function by instantiating, placing, then saving the return value | |
794 | // to a variable. | |
795 | // | |
796 | // example: | |
797 | // | // create a Button with no srcNodeRef, and place it in the body: | |
798 | // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body()); | |
799 | // | // now, 'button' is still the widget reference to the newly created button | |
800 | // | dojo.connect(button, "onClick", function(e){ console.log('click'); }); | |
801 | // | |
802 | // example: | |
803 | // | // create a button out of a node with id="src" and append it to id="wrapper": | |
804 | // | var button = new dijit.form.Button({},"src").placeAt("wrapper"); | |
805 | // | |
806 | // example: | |
807 | // | // place a new button as the first element of some div | |
808 | // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first"); | |
809 | // | |
810 | // example: | |
811 | // | // create a contentpane and add it to a TabContainer | |
812 | // | var tc = dijit.byId("myTabs"); | |
813 | // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc) | |
814 | ||
815 | if(reference.declaredClass && reference.addChild){ | |
816 | reference.addChild(this, position); | |
817 | }else{ | |
818 | dojo.place(this.domNode, reference, position); | |
819 | } | |
820 | return this; | |
821 | } | |
822 | }); | |
823 | ||
824 | })(); | |
825 | ||
826 | } |