_WidgetBase.js 35 KB

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