NodeList-traverse.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. define(["./query", "./_base/lang", "./_base/array"], function(dquery, lang, array){
  2. // module:
  3. // dojo/NodeList-traverse
  4. /*=====
  5. return function(){
  6. // summary:
  7. // Adds chainable methods to dojo/query() / NodeList instances for traversing the DOM
  8. };
  9. =====*/
  10. var NodeList = dquery.NodeList;
  11. lang.extend(NodeList, {
  12. _buildArrayFromCallback: function(/*Function*/ callback){
  13. // summary:
  14. // builds a new array of possibly differing size based on the input list.
  15. // Since the returned array is likely of different size than the input array,
  16. // the array's map function cannot be used.
  17. var ary = [];
  18. for(var i = 0; i < this.length; i++){
  19. var items = callback.call(this[i], this[i], ary);
  20. if(items){
  21. ary = ary.concat(items);
  22. }
  23. }
  24. return ary; //Array
  25. },
  26. _getUniqueAsNodeList: function(/*Array*/ nodes){
  27. // summary:
  28. // given a list of nodes, make sure only unique
  29. // elements are returned as our NodeList object.
  30. // Does not call _stash().
  31. var ary = [];
  32. //Using for loop for better speed.
  33. for(var i = 0, node; node = nodes[i]; i++){
  34. //Should be a faster way to do this. dojo/query has a private
  35. //_zip function that may be inspirational, but there are pathways
  36. //in query that force nozip?
  37. if(node.nodeType == 1 && array.indexOf(ary, node) == -1){
  38. ary.push(node);
  39. }
  40. }
  41. return this._wrap(ary, null, this._NodeListCtor); // dojo/NodeList
  42. },
  43. _getUniqueNodeListWithParent: function(/*Array*/ nodes, /*String*/ query){
  44. // summary:
  45. // gets unique element nodes, filters them further
  46. // with an optional query and then calls _stash to track parent NodeList.
  47. var ary = this._getUniqueAsNodeList(nodes);
  48. ary = (query ? dquery._filterResult(ary, query) : ary);
  49. return ary._stash(this); // dojo/NodeList
  50. },
  51. _getRelatedUniqueNodes: function(/*String?*/ query, /*Function*/ callback){
  52. // summary:
  53. // cycles over all the nodes and calls a callback
  54. // to collect nodes for a possible inclusion in a result.
  55. // The callback will get two args: callback(node, ary),
  56. // where ary is the array being used to collect the nodes.
  57. return this._getUniqueNodeListWithParent(this._buildArrayFromCallback(callback), query); // dojo/NodeList
  58. },
  59. children: function(/*String?*/ query){
  60. // summary:
  61. // Returns all immediate child elements for nodes in this dojo/NodeList.
  62. // Optionally takes a query to filter the child elements.
  63. // description:
  64. // .end() can be used on the returned dojo/NodeList to get back to the
  65. // original dojo/NodeList.
  66. // query:
  67. // a CSS selector.
  68. // returns:
  69. // all immediate child elements for the nodes in this dojo/NodeList.
  70. // example:
  71. // assume a DOM created by this markup:
  72. // | <div class="container">
  73. // | <div class="red">Red One</div>
  74. // | Some Text
  75. // | <div class="blue">Blue One</div>
  76. // | <div class="red">Red Two</div>
  77. // | <div class="blue">Blue Two</div>
  78. // | </div>
  79. // Running this code:
  80. // | require(["dojo/query", "dojo/NodeList-traverse"
  81. // | ], function(query){
  82. // | query(".container").children();
  83. // | });
  84. // returns the four divs that are children of the container div.
  85. // Running this code:
  86. // | dojo.query(".container").children(".red");
  87. // returns the two divs that have the class "red".
  88. return this._getRelatedUniqueNodes(query, function(node, ary){
  89. return lang._toArray(node.childNodes);
  90. }); // dojo/NodeList
  91. },
  92. closest: function(/*String*/ query, /*String|DOMNode?*/ root){
  93. // summary:
  94. // Returns closest parent that matches query, including current node in this
  95. // dojo/NodeList if it matches the query.
  96. // description:
  97. // .end() can be used on the returned dojo/NodeList to get back to the
  98. // original dojo/NodeList.
  99. // query:
  100. // a CSS selector.
  101. // root:
  102. // If specified, query is relative to "root" rather than document body.
  103. // returns:
  104. // the closest parent that matches the query, including the current
  105. // node in this dojo/NodeList if it matches the query.
  106. // example:
  107. // assume a DOM created by this markup:
  108. // | <div class="container">
  109. // | <div class="red">Red One</div>
  110. // | Some Text
  111. // | <div class="blue">Blue One</div>
  112. // | <div class="red">Red Two</div>
  113. // | <div class="blue">Blue Two</div>
  114. // | </div>
  115. // Running this code:
  116. // | require(["dojo/query", "dojo/NodeList-traverse"
  117. // | ], function(query){
  118. // | query(".red").closest(".container");
  119. // | });
  120. // returns the div with class "container".
  121. return this._getRelatedUniqueNodes(null, function(node, ary){
  122. do{
  123. if(dquery._filterResult([node], query, root).length){
  124. return node;
  125. }
  126. }while(node != root && (node = node.parentNode) && node.nodeType == 1);
  127. return null; //To make rhino strict checking happy.
  128. }); // dojo/NodeList
  129. },
  130. parent: function(/*String?*/ query){
  131. // summary:
  132. // Returns immediate parent elements for nodes in this dojo/NodeList.
  133. // Optionally takes a query to filter the parent elements.
  134. // description:
  135. // .end() can be used on the returned dojo/NodeList to get back to the
  136. // original dojo/NodeList.
  137. // query:
  138. // a CSS selector.
  139. // returns:
  140. // immediate parent elements for nodes in this dojo/NodeList.
  141. // example:
  142. // assume a DOM created by this markup:
  143. // | <div class="container">
  144. // | <div class="red">Red One</div>
  145. // | <div class="blue first"><span class="text">Blue One</span></div>
  146. // | <div class="red">Red Two</div>
  147. // | <div class="blue"><span class="text">Blue Two</span></div>
  148. // | </div>
  149. // Running this code:
  150. // | require(["dojo/query", "dojo/NodeList-traverse"
  151. // | ], function(query){
  152. // | query(".text").parent();
  153. // | });
  154. // returns the two divs with class "blue".
  155. // Running this code:
  156. // | query(".text").parent(".first");
  157. // returns the one div with class "blue" and "first".
  158. return this._getRelatedUniqueNodes(query, function(node, ary){
  159. return node.parentNode;
  160. }); // dojo/NodeList
  161. },
  162. parents: function(/*String?*/ query){
  163. // summary:
  164. // Returns all parent elements for nodes in this dojo/NodeList.
  165. // Optionally takes a query to filter the child elements.
  166. // description:
  167. // .end() can be used on the returned dojo/NodeList to get back to the
  168. // original dojo/NodeList.
  169. // query:
  170. // a CSS selector.
  171. // returns:
  172. // all parent elements for nodes in this dojo/NodeList.
  173. // example:
  174. // assume a DOM created by this markup:
  175. // | <div class="container">
  176. // | <div class="red">Red One</div>
  177. // | <div class="blue first"><span class="text">Blue One</span></div>
  178. // | <div class="red">Red Two</div>
  179. // | <div class="blue"><span class="text">Blue Two</span></div>
  180. // | </div>
  181. // Running this code:
  182. // | require(["dojo/query", "dojo/NodeList-traverse"
  183. // | ], function(query){
  184. // | query(".text").parents();
  185. // | });
  186. // returns the two divs with class "blue", the div with class "container",
  187. // | the body element and the html element.
  188. // Running this code:
  189. // | query(".text").parents(".container");
  190. // returns the one div with class "container".
  191. return this._getRelatedUniqueNodes(query, function(node, ary){
  192. var pary = [];
  193. while(node.parentNode){
  194. node = node.parentNode;
  195. pary.push(node);
  196. }
  197. return pary;
  198. }); // dojo/NodeList
  199. },
  200. siblings: function(/*String?*/ query){
  201. // summary:
  202. // Returns all sibling elements for nodes in this dojo/NodeList.
  203. // Optionally takes a query to filter the sibling elements.
  204. // description:
  205. // .end() can be used on the returned dojo/NodeList to get back to the
  206. // original dojo/NodeList.
  207. // query:
  208. // a CSS selector.
  209. // returns:
  210. // all sibling elements for nodes in this dojo/NodeList.
  211. // example:
  212. // assume a DOM created by this markup:
  213. // | <div class="container">
  214. // | <div class="red">Red One</div>
  215. // | Some Text
  216. // | <div class="blue first">Blue One</div>
  217. // | <div class="red">Red Two</div>
  218. // | <div class="blue">Blue Two</div>
  219. // | </div>
  220. // Running this code:
  221. // | require(["dojo/query", "dojo/NodeList-traverse"
  222. // | ], function(query){
  223. // | query(".first").siblings();
  224. // | });
  225. // returns the two divs with class "red" and the other div
  226. // | with class "blue" that does not have "first".
  227. // Running this code:
  228. // | query(".first").siblings(".red");
  229. // returns the two div with class "red".
  230. return this._getRelatedUniqueNodes(query, function(node, ary){
  231. var pary = [];
  232. var nodes = (node.parentNode && node.parentNode.childNodes);
  233. for(var i = 0; i < nodes.length; i++){
  234. if(nodes[i] != node){
  235. pary.push(nodes[i]);
  236. }
  237. }
  238. return pary;
  239. }); // dojo/NodeList
  240. },
  241. next: function(/*String?*/ query){
  242. // summary:
  243. // Returns the next element for nodes in this dojo/NodeList.
  244. // Optionally takes a query to filter the next elements.
  245. // description:
  246. // .end() can be used on the returned dojo/NodeList to get back to the
  247. // original dojo/NodeList.
  248. // query:
  249. // a CSS selector.
  250. // returns:
  251. // the next element for nodes in this dojo/NodeList.
  252. // example:
  253. // assume a DOM created by this markup:
  254. // | <div class="container">
  255. // | <div class="red">Red One</div>
  256. // | Some Text
  257. // | <div class="blue first">Blue One</div>
  258. // | <div class="red">Red Two</div>
  259. // | <div class="blue last">Blue Two</div>
  260. // | </div>
  261. // Running this code:
  262. // | require(["dojo/query", "dojo/NodeList-traverse"
  263. // | ], function(query){
  264. // | query(".first").next();
  265. // | });
  266. // returns the div with class "red" and has innerHTML of "Red Two".
  267. // Running this code:
  268. // | dojo.query(".last").next(".red");
  269. // does not return any elements.
  270. return this._getRelatedUniqueNodes(query, function(node, ary){
  271. var next = node.nextSibling;
  272. while(next && next.nodeType != 1){
  273. next = next.nextSibling;
  274. }
  275. return next;
  276. }); // dojo/NodeList
  277. },
  278. nextAll: function(/*String?*/ query){
  279. // summary:
  280. // Returns all sibling elements that come after the nodes in this dojo/NodeList.
  281. // Optionally takes a query to filter the sibling elements.
  282. // description:
  283. // .end() can be used on the returned dojo/NodeList to get back to the
  284. // original dojo/NodeList.
  285. // query:
  286. // a CSS selector.
  287. // returns:
  288. // all sibling elements that come after the nodes in this dojo/NodeList.
  289. // example:
  290. // assume a DOM created by this markup:
  291. // | <div class="container">
  292. // | <div class="red">Red One</div>
  293. // | Some Text
  294. // | <div class="blue first">Blue One</div>
  295. // | <div class="red next">Red Two</div>
  296. // | <div class="blue next">Blue Two</div>
  297. // | </div>
  298. // Running this code:
  299. // | require(["dojo/query", "dojo/NodeList-traverse"
  300. // | ], function(query){
  301. // | query(".first").nextAll();
  302. // | });
  303. // returns the two divs with class of "next".
  304. // Running this code:
  305. // | query(".first").nextAll(".red");
  306. // returns the one div with class "red" and innerHTML "Red Two".
  307. return this._getRelatedUniqueNodes(query, function(node, ary){
  308. var pary = [];
  309. var next = node;
  310. while((next = next.nextSibling)){
  311. if(next.nodeType == 1){
  312. pary.push(next);
  313. }
  314. }
  315. return pary;
  316. }); // dojo/NodeList
  317. },
  318. prev: function(/*String?*/ query){
  319. // summary:
  320. // Returns the previous element for nodes in this dojo/NodeList.
  321. // Optionally takes a query to filter the previous elements.
  322. // description:
  323. // .end() can be used on the returned dojo/NodeList to get back to the
  324. // original dojo/NodeList.
  325. // query:
  326. // a CSS selector.
  327. // returns:
  328. // the previous element for nodes in this dojo/NodeList.
  329. // example:
  330. // assume a DOM created by this markup:
  331. // | <div class="container">
  332. // | <div class="red">Red One</div>
  333. // | Some Text
  334. // | <div class="blue first">Blue One</div>
  335. // | <div class="red">Red Two</div>
  336. // | <div class="blue">Blue Two</div>
  337. // | </div>
  338. // Running this code:
  339. // | require(["dojo/query", "dojo/NodeList-traverse"
  340. // | ], function(query){
  341. // | query(".first").prev();
  342. // | });
  343. // returns the div with class "red" and has innerHTML of "Red One".
  344. // Running this code:
  345. // | query(".first").prev(".blue");
  346. // does not return any elements.
  347. return this._getRelatedUniqueNodes(query, function(node, ary){
  348. var prev = node.previousSibling;
  349. while(prev && prev.nodeType != 1){
  350. prev = prev.previousSibling;
  351. }
  352. return prev;
  353. }); // dojo/NodeList
  354. },
  355. prevAll: function(/*String?*/ query){
  356. // summary:
  357. // Returns all sibling elements that come before the nodes in this dojo/NodeList.
  358. // Optionally takes a query to filter the sibling elements.
  359. // description:
  360. // The returned nodes will be in reverse DOM order -- the first node in the list will
  361. // be the node closest to the original node/NodeList.
  362. // .end() can be used on the returned dojo/NodeList to get back to the
  363. // original dojo/NodeList.
  364. // query:
  365. // a CSS selector.
  366. // returns:
  367. // all sibling elements that come before the nodes in this dojo/NodeList.
  368. // example:
  369. // assume a DOM created by this markup:
  370. // | <div class="container">
  371. // | <div class="red prev">Red One</div>
  372. // | Some Text
  373. // | <div class="blue prev">Blue One</div>
  374. // | <div class="red second">Red Two</div>
  375. // | <div class="blue">Blue Two</div>
  376. // | </div>
  377. // Running this code:
  378. // | require(["dojo/query", "dojo/NodeList-traverse"
  379. // | ], function(query){
  380. // | query(".second").prevAll();
  381. // | });
  382. // returns the two divs with class of "prev".
  383. // Running this code:
  384. // | query(".first").prevAll(".red");
  385. // returns the one div with class "red prev" and innerHTML "Red One".
  386. return this._getRelatedUniqueNodes(query, function(node, ary){
  387. var pary = [];
  388. var prev = node;
  389. while((prev = prev.previousSibling)){
  390. if(prev.nodeType == 1){
  391. pary.push(prev);
  392. }
  393. }
  394. return pary;
  395. }); // dojo/NodeList
  396. },
  397. andSelf: function(){
  398. // summary:
  399. // Adds the nodes from the previous dojo/NodeList to the current dojo/NodeList.
  400. // description:
  401. // .end() can be used on the returned dojo/NodeList to get back to the
  402. // original dojo/NodeList.
  403. // example:
  404. // assume a DOM created by this markup:
  405. // | <div class="container">
  406. // | <div class="red prev">Red One</div>
  407. // | Some Text
  408. // | <div class="blue prev">Blue One</div>
  409. // | <div class="red second">Red Two</div>
  410. // | <div class="blue">Blue Two</div>
  411. // | </div>
  412. // Running this code:
  413. // | require(["dojo/query", "dojo/NodeList-traverse"
  414. // | ], function(query){
  415. // | query(".second").prevAll().andSelf();
  416. // | });
  417. // returns the two divs with class of "prev", as well as the div with class "second".
  418. return this.concat(this._parent); // dojo/NodeList
  419. },
  420. //Alternate methods for the :first/:last/:even/:odd pseudos.
  421. first: function(){
  422. // summary:
  423. // Returns the first node in this dojo/NodeList as a dojo/NodeList.
  424. // description:
  425. // .end() can be used on the returned dojo/NodeList to get back to the
  426. // original dojo/NodeList.
  427. // returns:
  428. // the first node in this dojo/NodeList
  429. // example:
  430. // assume a DOM created by this markup:
  431. // | <div class="container">
  432. // | <div class="red">Red One</div>
  433. // | <div class="blue first">Blue One</div>
  434. // | <div class="red">Red Two</div>
  435. // | <div class="blue last">Blue Two</div>
  436. // | </div>
  437. // Running this code:
  438. // | require(["dojo/query", "dojo/NodeList-traverse"
  439. // | ], function(query){
  440. // | query(".blue").first();
  441. // | });
  442. // returns the div with class "blue" and "first".
  443. return this._wrap(((this[0] && [this[0]]) || []), this); // dojo/NodeList
  444. },
  445. last: function(){
  446. // summary:
  447. // Returns the last node in this dojo/NodeList as a dojo/NodeList.
  448. // description:
  449. // .end() can be used on the returned dojo/NodeList to get back to the
  450. // original dojo/NodeList.
  451. // returns:
  452. // the last node in this dojo/NodeList
  453. // example:
  454. // assume a DOM created by this markup:
  455. // | <div class="container">
  456. // | <div class="red">Red One</div>
  457. // | <div class="blue first">Blue One</div>
  458. // | <div class="red">Red Two</div>
  459. // | <div class="blue last">Blue Two</div>
  460. // | </div>
  461. // Running this code:
  462. // | require(["dojo/query", "dojo/NodeList-traverse"
  463. // | ], function(query){
  464. // | query(".blue").last();
  465. // | });
  466. // returns the last div with class "blue",
  467. return this._wrap((this.length ? [this[this.length - 1]] : []), this); // dojo/NodeList
  468. },
  469. even: function(){
  470. // summary:
  471. // Returns the even nodes in this dojo/NodeList as a dojo/NodeList.
  472. // description:
  473. // .end() can be used on the returned dojo/NodeList to get back to the
  474. // original dojo/NodeList.
  475. // returns:
  476. // the even nodes in this dojo/NodeList
  477. // example:
  478. // assume a DOM created by this markup:
  479. // | <div class="container">
  480. // | <div class="interior red">Red One</div>
  481. // | <div class="interior blue">Blue One</div>
  482. // | <div class="interior red">Red Two</div>
  483. // | <div class="interior blue">Blue Two</div>
  484. // | </div>
  485. // Running this code:
  486. // | require(["dojo/query", "dojo/NodeList-traverse"
  487. // | ], function(query){
  488. // | query(".interior").even();
  489. // | });
  490. // returns the two divs with class "blue"
  491. return this.filter(function(item, i){
  492. return i % 2 != 0;
  493. }); // dojo/NodeList
  494. },
  495. odd: function(){
  496. // summary:
  497. // Returns the odd nodes in this dojo/NodeList as a dojo/NodeList.
  498. // description:
  499. // .end() can be used on the returned dojo/NodeList to get back to the
  500. // original dojo/NodeList.
  501. // returns:
  502. // the odd nodes in this dojo/NodeList
  503. // example:
  504. // assume a DOM created by this markup:
  505. // | <div class="container">
  506. // | <div class="interior red">Red One</div>
  507. // | <div class="interior blue">Blue One</div>
  508. // | <div class="interior red">Red Two</div>
  509. // | <div class="interior blue">Blue Two</div>
  510. // | </div>
  511. // Running this code:
  512. // | require(["dojo/query", "dojo/NodeList-traverse"
  513. // | ], function(query){
  514. // | query(".interior").odd();
  515. // | });
  516. // returns the two divs with class "red"
  517. return this.filter(function(item, i){
  518. return i % 2 == 0;
  519. }); // dojo/NodeList
  520. }
  521. });
  522. return NodeList;
  523. });