NodeList-dom.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. define(["./_base/kernel", "./query", "./_base/array", "./_base/lang", "./dom-class", "./dom-construct", "./dom-geometry", "./dom-attr", "./dom-style"], function(dojo, query, array, lang, domCls, domCtr, domGeom, domAttr, domStyle){
  2. // module:
  3. // dojo/NodeList-dom.js
  4. /*=====
  5. return function(){
  6. // summary:
  7. // Adds DOM related methods to NodeList, and returns NodeList constructor.
  8. };
  9. =====*/
  10. var magicGuard = function(a){
  11. // summary:
  12. // the guard function for dojo/dom-attr() and dojo/dom-style()
  13. return a.length == 1 && (typeof a[0] == "string"); // inline'd type check
  14. };
  15. var orphan = function(node){
  16. // summary:
  17. // function to orphan nodes
  18. var p = node.parentNode;
  19. if(p){
  20. p.removeChild(node);
  21. }
  22. };
  23. // FIXME: should we move orphan() to dojo/_base/html?
  24. var NodeList = query.NodeList,
  25. awc = NodeList._adaptWithCondition,
  26. aafe = NodeList._adaptAsForEach,
  27. aam = NodeList._adaptAsMap;
  28. function getSet(module){
  29. return function(node, name, value){
  30. if(arguments.length == 2){
  31. return module[typeof name == "string" ? "get" : "set"](node, name);
  32. }
  33. // setter
  34. return module.set(node, name, value);
  35. };
  36. }
  37. lang.extend(NodeList, {
  38. _normalize: function(/*String||Element||Object||NodeList*/content, /*DOMNode?*/refNode){
  39. // summary:
  40. // normalizes data to an array of items to insert.
  41. // description:
  42. // If content is an object, it can have special properties "template" and
  43. // "parse". If "template" is defined, then the template value is run through
  44. // dojo/string.substitute (if dojo/string.substitute() has been required elsewhere),
  45. // or if templateFunc is a function on the content, that function will be used to
  46. // transform the template into a final string to be used for for passing to dojo/dom-construct.toDom().
  47. // If content.parse is true, then it is remembered for later, for when the content
  48. // nodes are inserted into the DOM. At that point, the nodes will be parsed for widgets
  49. // (if dojo/parser has been required elsewhere).
  50. //Wanted to just use a DocumentFragment, but for the array/NodeList
  51. //case that meant using cloneNode, but we may not want that.
  52. //Cloning should only happen if the node operations span
  53. //multiple refNodes. Also, need a real array, not a NodeList from the
  54. //DOM since the node movements could change those NodeLists.
  55. var parse = content.parse === true;
  56. //Do we have an object that needs to be run through a template?
  57. if(typeof content.template == "string"){
  58. var templateFunc = content.templateFunc || (dojo.string && dojo.string.substitute);
  59. content = templateFunc ? templateFunc(content.template, content) : content;
  60. }
  61. var type = (typeof content);
  62. if(type == "string" || type == "number"){
  63. content = domCtr.toDom(content, (refNode && refNode.ownerDocument));
  64. if(content.nodeType == 11){
  65. //DocumentFragment. It cannot handle cloneNode calls, so pull out the children.
  66. content = lang._toArray(content.childNodes);
  67. }else{
  68. content = [content];
  69. }
  70. }else if(!lang.isArrayLike(content)){
  71. content = [content];
  72. }else if(!lang.isArray(content)){
  73. //To get to this point, content is array-like, but
  74. //not an array, which likely means a DOM NodeList. Convert it now.
  75. content = lang._toArray(content);
  76. }
  77. //Pass around the parse info
  78. if(parse){
  79. content._runParse = true;
  80. }
  81. return content; //Array
  82. },
  83. _cloneNode: function(/*DOMNode*/ node){
  84. // summary:
  85. // private utility to clone a node. Not very interesting in the vanilla
  86. // dojo/NodeList case, but delegates could do interesting things like
  87. // clone event handlers if that is derivable from the node.
  88. return node.cloneNode(true);
  89. },
  90. _place: function(/*Array*/ary, /*DOMNode*/refNode, /*String*/position, /*Boolean*/useClone){
  91. // summary:
  92. // private utility to handle placing an array of nodes relative to another node.
  93. // description:
  94. // Allows for cloning the nodes in the array, and for
  95. // optionally parsing widgets, if ary._runParse is true.
  96. //Avoid a disallowed operation if trying to do an innerHTML on a non-element node.
  97. if(refNode.nodeType != 1 && position == "only"){
  98. return;
  99. }
  100. var rNode = refNode, tempNode;
  101. //Always cycle backwards in case the array is really a
  102. //DOM NodeList and the DOM operations take it out of the live collection.
  103. var length = ary.length;
  104. for(var i = length - 1; i >= 0; i--){
  105. var node = (useClone ? this._cloneNode(ary[i]) : ary[i]);
  106. //If need widget parsing, use a temp node, instead of waiting after inserting into
  107. //real DOM because we need to start widget parsing at one node up from current node,
  108. //which could cause some already parsed widgets to be parsed again.
  109. if(ary._runParse && dojo.parser && dojo.parser.parse){
  110. if(!tempNode){
  111. tempNode = rNode.ownerDocument.createElement("div");
  112. }
  113. tempNode.appendChild(node);
  114. dojo.parser.parse(tempNode);
  115. node = tempNode.firstChild;
  116. while(tempNode.firstChild){
  117. tempNode.removeChild(tempNode.firstChild);
  118. }
  119. }
  120. if(i == length - 1){
  121. domCtr.place(node, rNode, position);
  122. }else{
  123. rNode.parentNode.insertBefore(node, rNode);
  124. }
  125. rNode = node;
  126. }
  127. },
  128. position: aam(domGeom.position),
  129. /*=====
  130. position: function(){
  131. // summary:
  132. // Returns border-box objects (x/y/w/h) of all elements in a node list
  133. // as an Array (*not* a NodeList). Acts like `dojo/dom-geometry-position`, though
  134. // assumes the node passed is each node in this list.
  135. return dojo.map(this, dojo.position); // Array
  136. },
  137. =====*/
  138. attr: awc(getSet(domAttr), magicGuard),
  139. /*=====
  140. attr: function(property, value){
  141. // summary:
  142. // gets or sets the DOM attribute for every element in the
  143. // NodeList. See also `dojo/dom-attr`
  144. // property: String
  145. // the attribute to get/set
  146. // value: String?
  147. // optional. The value to set the property to
  148. // returns:
  149. // if no value is passed, the result is an array of attribute values
  150. // If a value is passed, the return is this NodeList
  151. // example:
  152. // Make all nodes with a particular class focusable:
  153. // | require(["dojo/query", "dojo/NodeList-dom"], function(query){
  154. // | query(".focusable").attr("tabIndex", -1);
  155. // | });
  156. // example:
  157. // Disable a group of buttons:
  158. // | require(["dojo/query", "dojo/NodeList-dom"], function(query){
  159. // | query("button.group").attr("disabled", true);
  160. // | });
  161. // example:
  162. // innerHTML can be assigned or retrieved as well:
  163. // | // get the innerHTML (as an array) for each list item
  164. // | require(["dojo/query", "dojo/NodeList-dom"], function(query){
  165. // | var ih = query("li.replaceable").attr("innerHTML");
  166. // | });
  167. return; // dojo/NodeList|Array
  168. },
  169. =====*/
  170. style: awc(getSet(domStyle), magicGuard),
  171. /*=====
  172. style: function(property, value){
  173. // summary:
  174. // gets or sets the CSS property for every element in the NodeList
  175. // property: String
  176. // the CSS property to get/set, in JavaScript notation
  177. // ("lineHieght" instead of "line-height")
  178. // value: String?
  179. // optional. The value to set the property to
  180. // returns:
  181. // if no value is passed, the result is an array of strings.
  182. // If a value is passed, the return is this NodeList
  183. return; // dojo/NodeList
  184. return; // Array
  185. },
  186. =====*/
  187. addClass: aafe(domCls.add),
  188. /*=====
  189. addClass: function(className){
  190. // summary:
  191. // adds the specified class to every node in the list
  192. // className: String|Array
  193. // A String class name to add, or several space-separated class names,
  194. // or an array of class names.
  195. return; // dojo/NodeList
  196. },
  197. =====*/
  198. removeClass: aafe(domCls.remove),
  199. /*=====
  200. removeClass: function(className){
  201. // summary:
  202. // removes the specified class from every node in the list
  203. // className: String|Array?
  204. // An optional String class name to remove, or several space-separated
  205. // class names, or an array of class names. If omitted, all class names
  206. // will be deleted.
  207. // returns:
  208. // this list
  209. return; // dojo/NodeList
  210. },
  211. =====*/
  212. toggleClass: aafe(domCls.toggle),
  213. /*=====
  214. toggleClass: function(className, condition){
  215. // summary:
  216. // Adds a class to node if not present, or removes if present.
  217. // Pass a boolean condition if you want to explicitly add or remove.
  218. // condition: Boolean?
  219. // If passed, true means to add the class, false means to remove.
  220. // className: String
  221. // the CSS class to add
  222. return; // dojo/NodeList
  223. },
  224. =====*/
  225. replaceClass: aafe(domCls.replace),
  226. /*=====
  227. replaceClass: function(addClassStr, removeClassStr){
  228. // summary:
  229. // Replaces one or more classes on a node if not present.
  230. // Operates more quickly than calling `removeClass()` and `addClass()`
  231. // addClassStr: String|Array
  232. // A String class name to add, or several space-separated class names,
  233. // or an array of class names.
  234. // removeClassStr: String|Array?
  235. // A String class name to remove, or several space-separated class names,
  236. // or an array of class names.
  237. return; // dojo/NodeList
  238. },
  239. =====*/
  240. empty: aafe(domCtr.empty),
  241. /*=====
  242. empty: function(){
  243. // summary:
  244. // clears all content from each node in the list. Effectively
  245. // equivalent to removing all child nodes from every item in
  246. // the list.
  247. return this.forEach("item.innerHTML='';"); // dojo/NodeList
  248. // FIXME: should we be checking for and/or disposing of widgets below these nodes?
  249. },
  250. =====*/
  251. removeAttr: aafe(domAttr.remove),
  252. /*=====
  253. removeAttr: function(name){
  254. // summary:
  255. // Removes an attribute from each node in the list.
  256. // name: String
  257. // the name of the attribute to remove
  258. return; // dojo/NodeList
  259. },
  260. =====*/
  261. marginBox: aam(domGeom.getMarginBox),
  262. /*=====
  263. marginBox: function(){
  264. // summary:
  265. // Returns margin-box size of nodes
  266. return; // dojo/NodeList
  267. },
  268. =====*/
  269. // FIXME: connectPublisher()? connectRunOnce()?
  270. /*
  271. destroy: function(){
  272. // summary:
  273. // destroys every item in the list.
  274. this.forEach(d.destroy);
  275. // FIXME: should we be checking for and/or disposing of widgets below these nodes?
  276. },
  277. */
  278. place: function(/*String||Node*/ queryOrNode, /*String*/ position){
  279. // summary:
  280. // places elements of this node list relative to the first element matched
  281. // by queryOrNode. Returns the original NodeList. See: `dojo/dom-construct.place`
  282. // queryOrNode:
  283. // may be a string representing any valid CSS3 selector or a DOM node.
  284. // In the selector case, only the first matching element will be used
  285. // for relative positioning.
  286. // position:
  287. // can be one of:
  288. //
  289. // - "last" (default)
  290. // - "first"
  291. // - "before"
  292. // - "after"
  293. // - "only"
  294. // - "replace"
  295. //
  296. // or an offset in the childNodes property
  297. var item = query(queryOrNode)[0];
  298. return this.forEach(function(node){ domCtr.place(node, item, position); }); // dojo/NodeList
  299. },
  300. orphan: function(/*String?*/ filter){
  301. // summary:
  302. // removes elements in this list that match the filter
  303. // from their parents and returns them as a new NodeList.
  304. // filter:
  305. // CSS selector like ".foo" or "div > span"
  306. // returns:
  307. // NodeList containing the orphaned elements
  308. return (filter ? query._filterResult(this, filter) : this).forEach(orphan); // dojo/NodeList
  309. },
  310. adopt: function(/*String||Array||DomNode*/ queryOrListOrNode, /*String?*/ position){
  311. // summary:
  312. // places any/all elements in queryOrListOrNode at a
  313. // position relative to the first element in this list.
  314. // Returns a dojo/NodeList of the adopted elements.
  315. // queryOrListOrNode:
  316. // a DOM node or a query string or a query result.
  317. // Represents the nodes to be adopted relative to the
  318. // first element of this NodeList.
  319. // position:
  320. // can be one of:
  321. //
  322. // - "last" (default)
  323. // - "first"
  324. // - "before"
  325. // - "after"
  326. // - "only"
  327. // - "replace"
  328. //
  329. // or an offset in the childNodes property
  330. return query(queryOrListOrNode).place(this[0], position)._stash(this); // dojo/NodeList
  331. },
  332. // FIXME: do we need this?
  333. query: function(/*String*/ queryStr){
  334. // summary:
  335. // Returns a new list whose members match the passed query,
  336. // assuming elements of the current NodeList as the root for
  337. // each search.
  338. // example:
  339. // assume a DOM created by this markup:
  340. // | <div id="foo">
  341. // | <p>
  342. // | bacon is tasty, <span>dontcha think?</span>
  343. // | </p>
  344. // | </div>
  345. // | <div id="bar">
  346. // | <p>great comedians may not be funny <span>in person</span></p>
  347. // | </div>
  348. // If we are presented with the following definition for a NodeList:
  349. // | require(["dojo/dom", "dojo/query", "dojo/NodeList-dom"
  350. // | ], function(dom, query){
  351. // | var l = new NodeList(dom.byId("foo"), dom.byId("bar"));
  352. // it's possible to find all span elements under paragraphs
  353. // contained by these elements with this sub-query:
  354. // | var spans = l.query("p span");
  355. // | });
  356. // FIXME: probably slow
  357. if(!queryStr){ return this; }
  358. var ret = new NodeList;
  359. this.map(function(node){
  360. // FIXME: why would we ever get undefined here?
  361. query(queryStr, node).forEach(function(subNode){
  362. if(subNode !== undefined){
  363. ret.push(subNode);
  364. }
  365. });
  366. });
  367. return ret._stash(this); // dojo/NodeList
  368. },
  369. filter: function(/*String|Function*/ filter){
  370. // summary:
  371. // "masks" the built-in javascript filter() method (supported
  372. // in Dojo via `dojo.filter`) to support passing a simple
  373. // string filter in addition to supporting filtering function
  374. // objects.
  375. // filter:
  376. // If a string, a CSS rule like ".thinger" or "div > span".
  377. // example:
  378. // "regular" JS filter syntax as exposed in dojo.filter:
  379. // | require(["dojo/query", "dojo/NodeList-dom"
  380. // | ], function(query){
  381. // | query("*").filter(function(item){
  382. // | // highlight every paragraph
  383. // | return (item.nodeName == "p");
  384. // | }).style("backgroundColor", "yellow");
  385. // | });
  386. // example:
  387. // the same filtering using a CSS selector
  388. // | require(["dojo/query", "dojo/NodeList-dom"
  389. // | ], function(query){
  390. // | query("*").filter("p").styles("backgroundColor", "yellow");
  391. // | });
  392. var a = arguments, items = this, start = 0;
  393. if(typeof filter == "string"){ // inline'd type check
  394. items = query._filterResult(this, a[0]);
  395. if(a.length == 1){
  396. // if we only got a string query, pass back the filtered results
  397. return items._stash(this); // dojo/NodeList
  398. }
  399. // if we got a callback, run it over the filtered items
  400. start = 1;
  401. }
  402. return this._wrap(array.filter(items, a[start], a[start + 1]), this); // dojo/NodeList
  403. },
  404. /*
  405. // FIXME: should this be "copyTo" and include parenting info?
  406. clone: function(){
  407. // summary:
  408. // creates node clones of each element of this list
  409. // and returns a new list containing the clones
  410. },
  411. */
  412. addContent: function(/*String||DomNode||Object||dojo/NodeList*/ content, /*String||Integer?*/ position){
  413. // summary:
  414. // add a node, NodeList or some HTML as a string to every item in the
  415. // list. Returns the original list.
  416. // description:
  417. // a copy of the HTML content is added to each item in the
  418. // list, with an optional position argument. If no position
  419. // argument is provided, the content is appended to the end of
  420. // each item.
  421. // content:
  422. // DOM node, HTML in string format, a NodeList or an Object. If a DOM node or
  423. // NodeList, the content will be cloned if the current NodeList has more than one
  424. // element. Only the DOM nodes are cloned, no event handlers. If it is an Object,
  425. // it should be an object with at "template" String property that has the HTML string
  426. // to insert. If dojo.string has already been dojo.required, then dojo.string.substitute
  427. // will be used on the "template" to generate the final HTML string. Other allowed
  428. // properties on the object are: "parse" if the HTML
  429. // string should be parsed for widgets (dojo.require("dojo.parser") to get that
  430. // option to work), and "templateFunc" if a template function besides dojo.string.substitute
  431. // should be used to transform the "template".
  432. // position:
  433. // can be one of:
  434. //
  435. // - "last"||"end" (default)
  436. // - "first||"start"
  437. // - "before"
  438. // - "after"
  439. // - "replace" (replaces nodes in this NodeList with new content)
  440. // - "only" (removes other children of the nodes so new content is the only child)
  441. //
  442. // or an offset in the childNodes property
  443. // example:
  444. // appends content to the end if the position is omitted
  445. // | require(["dojo/query", "dojo/NodeList-dom"
  446. // | ], function(query){
  447. // | query("h3 > p").addContent("hey there!");
  448. // | });
  449. // example:
  450. // add something to the front of each element that has a
  451. // "thinger" property:
  452. // | require(["dojo/query", "dojo/NodeList-dom"
  453. // | ], function(query){
  454. // | query("[thinger]").addContent("...", "first");
  455. // | });
  456. // example:
  457. // adds a header before each element of the list
  458. // | require(["dojo/query", "dojo/NodeList-dom"
  459. // | ], function(query){
  460. // | query(".note").addContent("<h4>NOTE:</h4>", "before");
  461. // | });
  462. // example:
  463. // add a clone of a DOM node to the end of every element in
  464. // the list, removing it from its existing parent.
  465. // | require(["dojo/dom", "dojo/query", "dojo/NodeList-dom"
  466. // | ], function(dom, query){
  467. // | query(".note").addContent(dom.byId("foo"));
  468. // | });
  469. // example:
  470. // Append nodes from a templatized string.
  471. // | require(["dojo/string", "dojo/query", "dojo/NodeList-dom"
  472. // | ], function(string, query){
  473. // | query(".note").addContent({
  474. // | template: '<b>${id}: </b><span>${name}</span>',
  475. // | id: "user332",
  476. // | name: "Mr. Anderson"
  477. // | });
  478. // | });
  479. // example:
  480. // Append nodes from a templatized string that also has widgets parsed.
  481. // | require(["dojo/string", "dojo/parser", "dojo/query", "dojo/NodeList-dom"
  482. // | ], function(string, parser, query){
  483. // | var notes = query(".note").addContent({
  484. // | template: '<button dojoType="dijit/form/Button">${text}</button>',
  485. // | parse: true,
  486. // | text: "Send"
  487. // | });
  488. // | });
  489. content = this._normalize(content, this[0]);
  490. for(var i = 0, node; (node = this[i]); i++){
  491. if(content.length){
  492. this._place(content, node, position, i > 0);
  493. }else{
  494. // if it is an empty array, we empty the target node
  495. domCtr.empty(node);
  496. }
  497. }
  498. return this; // dojo/NodeList
  499. }
  500. });
  501. return NodeList;
  502. });