DomQuery.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. /**
  2. * DomQuery.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. *
  10. * Some of this logic is based on jQuery code that is released under
  11. * MIT license that grants us to sublicense it under LGPL.
  12. *
  13. * @ignore-file
  14. */
  15. /**
  16. * @class tinymce.dom.DomQuery
  17. */
  18. define("tinymce/dom/DomQuery", [
  19. "tinymce/dom/EventUtils",
  20. "tinymce/dom/Sizzle"
  21. ], function(EventUtils, Sizzle) {
  22. var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
  23. var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
  24. var Event = EventUtils.Event;
  25. function isDefined(obj) {
  26. return typeof obj !== "undefined";
  27. }
  28. function isString(obj) {
  29. return typeof obj === "string";
  30. }
  31. function createFragment(html) {
  32. var frag, node, container;
  33. container = doc.createElement("div");
  34. frag = doc.createDocumentFragment();
  35. container.innerHTML = html;
  36. while ((node = container.firstChild)) {
  37. frag.appendChild(node);
  38. }
  39. return frag;
  40. }
  41. function domManipulate(targetNodes, sourceItem, callback) {
  42. var i;
  43. if (typeof sourceItem === "string") {
  44. sourceItem = createFragment(sourceItem);
  45. } else if (sourceItem.length) {
  46. for (i = 0; i < sourceItem.length; i++) {
  47. domManipulate(targetNodes, sourceItem[i], callback);
  48. }
  49. return targetNodes;
  50. }
  51. i = targetNodes.length;
  52. while (i--) {
  53. callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem);
  54. }
  55. return targetNodes;
  56. }
  57. function hasClass(node, className) {
  58. return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
  59. }
  60. /**
  61. * Makes a map object out of a string that gets separated by a delimiter.
  62. *
  63. * @method makeMap
  64. * @param {String} items Item string to split.
  65. * @param {Object} map Optional object to add items to.
  66. * @return {Object} name/value object with items as keys.
  67. */
  68. function makeMap(items, map) {
  69. var i;
  70. items = items || [];
  71. if (typeof(items) == "string") {
  72. items = items.split(' ');
  73. }
  74. map = map || {};
  75. i = items.length;
  76. while (i--) {
  77. map[items[i]] = {};
  78. }
  79. return map;
  80. }
  81. var numericCssMap = makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom');
  82. function DomQuery(selector, context) {
  83. /*eslint new-cap:0 */
  84. return new DomQuery.fn.init(selector, context);
  85. }
  86. /**
  87. * Extends the specified object with another object.
  88. *
  89. * @method extend
  90. * @param {Object} target Object to extend.
  91. * @param {Object..} obj Multiple objects to extend with.
  92. * @return {Object} Same as target, the extended object.
  93. */
  94. function extend(target) {
  95. var args = arguments, arg, i, key;
  96. for (i = 1; i < args.length; i++) {
  97. arg = args[i];
  98. for (key in arg) {
  99. target[key] = arg[key];
  100. }
  101. }
  102. return target;
  103. }
  104. /**
  105. * Converts the specified object into a real JavaScript array.
  106. *
  107. * @method toArray
  108. * @param {Object} obj Object to convert into array.
  109. * @return {Array} Array object based in input.
  110. */
  111. function toArray(obj) {
  112. var array = [], i, l;
  113. for (i = 0, l = obj.length; i < l; i++) {
  114. array[i] = obj[i];
  115. }
  116. return array;
  117. }
  118. /**
  119. * Returns the index of the specified item inside the array.
  120. *
  121. * @method inArray
  122. * @param {Object} item Item to look for.
  123. * @param {Array} array Array to look for item in.
  124. * @return {Number} Index of the item or -1.
  125. */
  126. function inArray(item, array) {
  127. var i;
  128. if (array.indexOf) {
  129. return array.indexOf(item);
  130. }
  131. i = array.length;
  132. while (i--) {
  133. if (array[i] === item) {
  134. return i;
  135. }
  136. }
  137. return -1;
  138. }
  139. /**
  140. * Returns true/false if the specified object is an array.
  141. *
  142. * @method isArray
  143. * @param {Object} obj Object to check if it's an array.
  144. * @return {Boolean} true/false if the input object is array or not.
  145. */
  146. var isArray = Array.isArray || function(obj) {
  147. return Object.prototype.toString.call(obj) === "[object Array]";
  148. };
  149. var whiteSpaceRegExp = /^\s*|\s*$/g;
  150. function trim(str) {
  151. return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  152. }
  153. /**
  154. * Executes the callback function for each item in array/object. If you return false in the
  155. * callback it will break the loop.
  156. *
  157. * @method each
  158. * @param {Object} obj Object to iterate.
  159. * @param {function} callback Callback function to execute for each item.
  160. */
  161. function each(obj, callback) {
  162. var length, key, i, undef, value;
  163. if (obj) {
  164. length = obj.length;
  165. if (length === undef) {
  166. // Loop object items
  167. for (key in obj) {
  168. if (obj.hasOwnProperty(key)) {
  169. value = obj[key];
  170. if (callback.call(value, value, key) === false) {
  171. break;
  172. }
  173. }
  174. }
  175. } else {
  176. // Loop array items
  177. for (i = 0; i < length; i++) {
  178. value = obj[i];
  179. if (callback.call(value, value, key) === false) {
  180. break;
  181. }
  182. }
  183. }
  184. }
  185. return obj;
  186. }
  187. DomQuery.fn = DomQuery.prototype = {
  188. constructor: DomQuery,
  189. selector: "",
  190. length: 0,
  191. init: function(selector, context) {
  192. var self = this, match, node;
  193. if (!selector) {
  194. return self;
  195. }
  196. if (selector.nodeType) {
  197. self.context = self[0] = selector;
  198. self.length = 1;
  199. return self;
  200. }
  201. if (isString(selector)) {
  202. if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
  203. match = [null, selector, null];
  204. } else {
  205. match = rquickExpr.exec(selector);
  206. }
  207. if (match) {
  208. if (match[1]) {
  209. node = createFragment(selector).firstChild;
  210. while (node) {
  211. this.add(node);
  212. node = node.nextSibling;
  213. }
  214. } else {
  215. node = doc.getElementById(match[2]);
  216. if (node.id !== match[2]) {
  217. return self.find(selector);
  218. }
  219. self.length = 1;
  220. self[0] = node;
  221. }
  222. } else {
  223. return DomQuery(context || document).find(selector);
  224. }
  225. } else {
  226. this.add(selector);
  227. }
  228. return self;
  229. },
  230. toArray: function() {
  231. return toArray(this);
  232. },
  233. add: function(items) {
  234. var self = this;
  235. // Force single item into array
  236. if (!isArray(items)) {
  237. if (items instanceof DomQuery) {
  238. self.add(items.toArray());
  239. } else {
  240. push.call(self, items);
  241. }
  242. } else {
  243. push.apply(self, items);
  244. }
  245. return self;
  246. },
  247. attr: function(name, value) {
  248. var self = this;
  249. if (typeof name === "object") {
  250. each(name, function(value, name) {
  251. self.attr(name, value);
  252. });
  253. } else if (isDefined(value)) {
  254. this.each(function() {
  255. if (this.nodeType === 1) {
  256. this.setAttribute(name, value);
  257. }
  258. });
  259. } else {
  260. return self[0] && self[0].nodeType === 1 ? self[0].getAttribute(name) : undefined;
  261. }
  262. return self;
  263. },
  264. css: function(name, value) {
  265. var self = this;
  266. if (typeof name === "object") {
  267. each(name, function(value, name) {
  268. self.css(name, value);
  269. });
  270. } else {
  271. // Camelcase it, if needed
  272. name = name.replace(/-(\D)/g, function(a, b) {
  273. return b.toUpperCase();
  274. });
  275. if (isDefined(value)) {
  276. // Default px suffix on these
  277. if (typeof(value) === 'number' && !numericCssMap[name]) {
  278. value += 'px';
  279. }
  280. self.each(function() {
  281. var style = this.style;
  282. // IE specific opacity
  283. if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") {
  284. style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
  285. }
  286. try {
  287. style[name] = value;
  288. } catch (ex) {
  289. // Ignore
  290. }
  291. });
  292. } else {
  293. return self[0] ? self[0].style[name] : undefined;
  294. }
  295. }
  296. return self;
  297. },
  298. remove: function() {
  299. var self = this, node, i = this.length;
  300. while (i--) {
  301. node = self[i];
  302. Event.clean(node);
  303. if (node.parentNode) {
  304. node.parentNode.removeChild(node);
  305. }
  306. }
  307. return this;
  308. },
  309. empty: function() {
  310. var self = this, node, i = this.length;
  311. while (i--) {
  312. node = self[i];
  313. while (node.firstChild) {
  314. node.removeChild(node.firstChild);
  315. }
  316. }
  317. return this;
  318. },
  319. html: function(value) {
  320. var self = this, i;
  321. if (isDefined(value)) {
  322. i = self.length;
  323. while (i--) {
  324. self[i].innerHTML = value;
  325. }
  326. return self;
  327. }
  328. return self[0] ? self[0].innerHTML : '';
  329. },
  330. text: function(value) {
  331. var self = this, i;
  332. if (isDefined(value)) {
  333. i = self.length;
  334. while (i--) {
  335. self[i].innerText = self[0].textContent = value;
  336. }
  337. return self;
  338. }
  339. return self[0] ? self[0].innerText || self[0].textContent : '';
  340. },
  341. append: function() {
  342. return domManipulate(this, arguments, function(node) {
  343. if (this.nodeType === 1) {
  344. this.appendChild(node);
  345. }
  346. });
  347. },
  348. prepend: function() {
  349. return domManipulate(this, arguments, function(node) {
  350. if (this.nodeType === 1) {
  351. this.insertBefore(node, this.firstChild);
  352. }
  353. });
  354. },
  355. before: function() {
  356. var self = this;
  357. if (self[0] && self[0].parentNode) {
  358. return domManipulate(self, arguments, function(node) {
  359. this.parentNode.insertBefore(node, this.nextSibling);
  360. });
  361. }
  362. return self;
  363. },
  364. after: function() {
  365. var self = this;
  366. if (self[0] && self[0].parentNode) {
  367. return domManipulate(self, arguments, function(node) {
  368. this.parentNode.insertBefore(node, this);
  369. });
  370. }
  371. return self;
  372. },
  373. appendTo: function(val) {
  374. DomQuery(val).append(this);
  375. return this;
  376. },
  377. addClass: function(className) {
  378. return this.toggleClass(className, true);
  379. },
  380. removeClass: function(className) {
  381. return this.toggleClass(className, false);
  382. },
  383. toggleClass: function(className, state) {
  384. var self = this;
  385. if (className.indexOf(' ') !== -1) {
  386. each(className.split(' '), function() {
  387. self.toggleClass(this, state);
  388. });
  389. } else {
  390. self.each(function(node) {
  391. var existingClassName;
  392. if (hasClass(node, className) !== state) {
  393. existingClassName = node.className;
  394. if (state) {
  395. node.className += existingClassName ? ' ' + className : className;
  396. } else {
  397. node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
  398. }
  399. }
  400. });
  401. }
  402. return self;
  403. },
  404. hasClass: function(className) {
  405. return hasClass(this[0], className);
  406. },
  407. each: function(callback) {
  408. return each(this, callback);
  409. },
  410. on: function(name, callback) {
  411. return this.each(function() {
  412. Event.bind(this, name, callback);
  413. });
  414. },
  415. off: function(name, callback) {
  416. return this.each(function() {
  417. Event.unbind(this, name, callback);
  418. });
  419. },
  420. show: function() {
  421. return this.css('display', '');
  422. },
  423. hide: function() {
  424. return this.css('display', 'none');
  425. },
  426. slice: function() {
  427. return new DomQuery(slice.apply(this, arguments));
  428. },
  429. eq: function(index) {
  430. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  431. },
  432. first: function() {
  433. return this.eq(0);
  434. },
  435. last: function() {
  436. return this.eq(-1);
  437. },
  438. replaceWith: function(content) {
  439. var self = this;
  440. if (self[0]) {
  441. self[0].parentNode.replaceChild(DomQuery(content)[0], self[0]);
  442. }
  443. return self;
  444. },
  445. wrap: function(wrapper) {
  446. wrapper = DomQuery(wrapper)[0];
  447. return this.each(function() {
  448. var self = this, newWrapper = wrapper.cloneNode(false);
  449. self.parentNode.insertBefore(newWrapper, self);
  450. newWrapper.appendChild(self);
  451. });
  452. },
  453. unwrap: function() {
  454. return this.each(function() {
  455. var self = this, node = self.firstChild, currentNode;
  456. while (node) {
  457. currentNode = node;
  458. node = node.nextSibling;
  459. self.parentNode.insertBefore(currentNode, self);
  460. }
  461. });
  462. },
  463. clone: function() {
  464. var result = [];
  465. this.each(function() {
  466. result.push(this.cloneNode(true));
  467. });
  468. return DomQuery(result);
  469. },
  470. find: function(selector) {
  471. var i, l, ret = [];
  472. for (i = 0, l = this.length; i < l; i++) {
  473. DomQuery.find(selector, this[i], ret);
  474. }
  475. return DomQuery(ret);
  476. },
  477. push: push,
  478. sort: [].sort,
  479. splice: [].splice
  480. };
  481. // Static members
  482. extend(DomQuery, {
  483. extend: extend,
  484. toArray: toArray,
  485. inArray: inArray,
  486. isArray: isArray,
  487. each: each,
  488. trim: trim,
  489. makeMap: makeMap,
  490. // Sizzle
  491. find: Sizzle,
  492. expr: Sizzle.selectors,
  493. unique: Sizzle.uniqueSort,
  494. text: Sizzle.getText,
  495. isXMLDoc: Sizzle.isXML,
  496. contains: Sizzle.contains,
  497. filter: function(expr, elems, not) {
  498. if (not) {
  499. expr = ":not(" + expr + ")";
  500. }
  501. if (elems.length === 1) {
  502. elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
  503. } else {
  504. elems = DomQuery.find.matches(expr, elems);
  505. }
  506. return elems;
  507. }
  508. });
  509. function dir(el, prop, until) {
  510. var matched = [], cur = el[prop];
  511. while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) {
  512. if (cur.nodeType === 1) {
  513. matched.push(cur);
  514. }
  515. cur = cur[prop];
  516. }
  517. return matched;
  518. }
  519. function sibling(n, el, siblingName, nodeType) {
  520. var r = [];
  521. for(; n; n = n[siblingName]) {
  522. if ((!nodeType || n.nodeType === nodeType) && n !== el) {
  523. r.push(n);
  524. }
  525. }
  526. return r;
  527. }
  528. each({
  529. parent: function(node) {
  530. var parent = node.parentNode;
  531. return parent && parent.nodeType !== 11 ? parent : null;
  532. },
  533. parents: function(node) {
  534. return dir(node, "parentNode");
  535. },
  536. parentsUntil: function(node, until) {
  537. return dir(node, "parentNode", until);
  538. },
  539. next: function(node) {
  540. return sibling(node, 'nextSibling', 1);
  541. },
  542. prev: function(node) {
  543. return sibling(node, 'previousSibling', 1);
  544. },
  545. nextNodes: function(node) {
  546. return sibling(node, 'nextSibling');
  547. },
  548. prevNodes: function(node) {
  549. return sibling(node, 'previousSibling');
  550. },
  551. children: function(node) {
  552. return sibling(node.firstChild, 'nextSibling', 1);
  553. },
  554. contents: function(node) {
  555. return toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
  556. }
  557. }, function(name, fn){
  558. DomQuery.fn[name] = function(selector) {
  559. var self = this, result;
  560. if (self.length > 1) {
  561. throw new Error("DomQuery only supports traverse functions on a single node.");
  562. }
  563. if (self[0]) {
  564. result = fn(self[0], selector);
  565. }
  566. result = DomQuery(result);
  567. if (selector && name !== "parentsUntil") {
  568. return result.filter(selector);
  569. }
  570. return result;
  571. };
  572. });
  573. DomQuery.fn.filter = function(selector) {
  574. return DomQuery.filter(selector);
  575. };
  576. DomQuery.fn.is = function(selector) {
  577. return !!selector && this.filter(selector).length > 0;
  578. };
  579. DomQuery.fn.init.prototype = DomQuery.fn;
  580. return DomQuery;
  581. });