_WidgetBase.js 28 KB

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