DOMUtils.js 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104
  1. /**
  2. * DOMUtils.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. /**
  11. * Utility class for various DOM manipulation and retrieval functions.
  12. *
  13. * @class tinymce.dom.DOMUtils
  14. * @example
  15. * // Add a class to an element by id in the page
  16. * tinymce.DOM.addClass('someid', 'someclass');
  17. *
  18. * // Add a class to an element by id inside the editor
  19. * tinymce.activeEditor.dom.addClass('someid', 'someclass');
  20. */
  21. define("tinymce/dom/DOMUtils", [
  22. "tinymce/dom/Sizzle",
  23. "tinymce/html/Styles",
  24. "tinymce/dom/EventUtils",
  25. "tinymce/dom/TreeWalker",
  26. "tinymce/dom/Range",
  27. "tinymce/html/Entities",
  28. "tinymce/Env",
  29. "tinymce/util/Tools",
  30. "tinymce/dom/StyleSheetLoader"
  31. ], function(Sizzle, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
  32. // Shorten names
  33. var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend;
  34. var isWebKit = Env.webkit, isIE = Env.ie;
  35. var simpleSelectorRe = /^([a-z0-9],?)+$/i;
  36. var whiteSpaceRegExp = /^[ \t\r\n]*$/;
  37. var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
  38. /**
  39. * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
  40. *
  41. * @constructor
  42. * @method DOMUtils
  43. * @param {Document} d Document reference to bind the utility class to.
  44. * @param {settings} s Optional settings collection.
  45. */
  46. function DOMUtils(doc, settings) {
  47. var self = this, blockElementsMap;
  48. self.doc = doc;
  49. self.win = window;
  50. self.files = {};
  51. self.counter = 0;
  52. self.stdMode = !isIE || doc.documentMode >= 8;
  53. self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
  54. self.hasOuterHTML = "outerHTML" in doc.createElement("a");
  55. self.styleSheetLoader = new StyleSheetLoader(doc);
  56. this.boundEvents = [];
  57. self.settings = settings = extend({
  58. keep_values: false,
  59. hex_colors: 1
  60. }, settings);
  61. self.schema = settings.schema;
  62. self.styles = new Styles({
  63. url_converter: settings.url_converter,
  64. url_converter_scope: settings.url_converter_scope
  65. }, settings.schema);
  66. self.fixDoc(doc);
  67. self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
  68. blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
  69. /**
  70. * Returns true/false if the specified element is a block element or not.
  71. *
  72. * @method isBlock
  73. * @param {Node/String} node Element/Node to check.
  74. * @return {Boolean} True/False state if the node is a block element or not.
  75. */
  76. self.isBlock = function(node) {
  77. // Fix for #5446
  78. if (!node) {
  79. return false;
  80. }
  81. // This function is called in module pattern style since it might be executed with the wrong this scope
  82. var type = node.nodeType;
  83. // If it's a node then check the type and use the nodeName
  84. if (type) {
  85. return !!(type === 1 && blockElementsMap[node.nodeName]);
  86. }
  87. return !!blockElementsMap[node];
  88. };
  89. }
  90. DOMUtils.prototype = {
  91. root: null,
  92. props: {
  93. "for": "htmlFor",
  94. "class": "className",
  95. className: "className",
  96. checked: "checked",
  97. disabled: "disabled",
  98. maxlength: "maxLength",
  99. readonly: "readOnly",
  100. selected: "selected",
  101. value: "value",
  102. id: "id",
  103. name: "name",
  104. type: "type"
  105. },
  106. fixDoc: function(doc) {
  107. var settings = this.settings, name;
  108. if (isIE && settings.schema) {
  109. // Add missing HTML 4/5 elements to IE
  110. ('abbr article aside audio canvas ' +
  111. 'details figcaption figure footer ' +
  112. 'header hgroup mark menu meter nav ' +
  113. 'output progress section summary ' +
  114. 'time video').replace(/\w+/g, function(name) {
  115. doc.createElement(name);
  116. });
  117. // Create all custom elements
  118. for (name in settings.schema.getCustomElements()) {
  119. doc.createElement(name);
  120. }
  121. }
  122. },
  123. clone: function(node, deep) {
  124. var self = this, clone, doc;
  125. // TODO: Add feature detection here in the future
  126. if (!isIE || node.nodeType !== 1 || deep) {
  127. return node.cloneNode(deep);
  128. }
  129. doc = self.doc;
  130. // Make a HTML5 safe shallow copy
  131. if (!deep) {
  132. clone = doc.createElement(node.nodeName);
  133. // Copy attribs
  134. each(self.getAttribs(node), function(attr) {
  135. self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
  136. });
  137. return clone;
  138. }
  139. /*
  140. // Setup HTML5 patched document fragment
  141. if (!self.frag) {
  142. self.frag = doc.createDocumentFragment();
  143. self.fixDoc(self.frag);
  144. }
  145. // Make a deep copy by adding it to the document fragment then removing it this removed the :section
  146. clone = doc.createElement('div');
  147. self.frag.appendChild(clone);
  148. clone.innerHTML = node.outerHTML;
  149. self.frag.removeChild(clone);
  150. */
  151. return clone.firstChild;
  152. },
  153. /**
  154. * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
  155. * go above the point of this root node.
  156. *
  157. * @method getRoot
  158. * @return {Element} Root element for the utility class.
  159. */
  160. getRoot: function() {
  161. var self = this;
  162. return self.get(self.settings.root_element) || self.doc.body;
  163. },
  164. /**
  165. * Returns the viewport of the window.
  166. *
  167. * @method getViewPort
  168. * @param {Window} win Optional window to get viewport of.
  169. * @return {Object} Viewport object with fields x, y, w and h.
  170. */
  171. getViewPort: function(win) {
  172. var doc, rootElm;
  173. win = !win ? this.win : win;
  174. doc = win.document;
  175. rootElm = this.boxModel ? doc.documentElement : doc.body;
  176. // Returns viewport size excluding scrollbars
  177. return {
  178. x: win.pageXOffset || rootElm.scrollLeft,
  179. y: win.pageYOffset || rootElm.scrollTop,
  180. w: win.innerWidth || rootElm.clientWidth,
  181. h: win.innerHeight || rootElm.clientHeight
  182. };
  183. },
  184. /**
  185. * Returns the rectangle for a specific element.
  186. *
  187. * @method getRect
  188. * @param {Element/String} elm Element object or element ID to get rectangle from.
  189. * @return {object} Rectangle for specified element object with x, y, w, h fields.
  190. */
  191. getRect: function(elm) {
  192. var self = this, pos, size;
  193. elm = self.get(elm);
  194. pos = self.getPos(elm);
  195. size = self.getSize(elm);
  196. return {
  197. x: pos.x, y: pos.y,
  198. w: size.w, h: size.h
  199. };
  200. },
  201. /**
  202. * Returns the size dimensions of the specified element.
  203. *
  204. * @method getSize
  205. * @param {Element/String} elm Element object or element ID to get rectangle from.
  206. * @return {object} Rectangle for specified element object with w, h fields.
  207. */
  208. getSize: function(elm) {
  209. var self = this, w, h;
  210. elm = self.get(elm);
  211. w = self.getStyle(elm, 'width');
  212. h = self.getStyle(elm, 'height');
  213. // Non pixel value, then force offset/clientWidth
  214. if (w.indexOf('px') === -1) {
  215. w = 0;
  216. }
  217. // Non pixel value, then force offset/clientWidth
  218. if (h.indexOf('px') === -1) {
  219. h = 0;
  220. }
  221. return {
  222. w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
  223. h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
  224. };
  225. },
  226. /**
  227. * Returns a node by the specified selector function. This function will
  228. * loop through all parent nodes and call the specified function for each node.
  229. * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
  230. * and the node it found will be returned.
  231. *
  232. * @method getParent
  233. * @param {Node/String} node DOM node to search parents on or ID string.
  234. * @param {function} selector Selection function or CSS selector to execute on each node.
  235. * @param {Node} root Optional root element, never go below this point.
  236. * @return {Node} DOM Node or null if it wasn't found.
  237. */
  238. getParent: function(node, selector, root) {
  239. return this.getParents(node, selector, root, false);
  240. },
  241. /**
  242. * Returns a node list of all parents matching the specified selector function or pattern.
  243. * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
  244. *
  245. * @method getParents
  246. * @param {Node/String} node DOM node to search parents on or ID string.
  247. * @param {function} selector Selection function to execute on each node or CSS pattern.
  248. * @param {Node} root Optional root element, never go below this point.
  249. * @return {Array} Array of nodes or null if it wasn't found.
  250. */
  251. getParents: function(node, selector, root, collect) {
  252. var self = this, selectorVal, result = [];
  253. node = self.get(node);
  254. collect = collect === undefined;
  255. // Default root on inline mode
  256. root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
  257. // Wrap node name as func
  258. if (is(selector, 'string')) {
  259. selectorVal = selector;
  260. if (selector === '*') {
  261. selector = function(node) {return node.nodeType == 1;};
  262. } else {
  263. selector = function(node) {
  264. return self.is(node, selectorVal);
  265. };
  266. }
  267. }
  268. while (node) {
  269. if (node == root || !node.nodeType || node.nodeType === 9) {
  270. break;
  271. }
  272. if (!selector || selector(node)) {
  273. if (collect) {
  274. result.push(node);
  275. } else {
  276. return node;
  277. }
  278. }
  279. node = node.parentNode;
  280. }
  281. return collect ? result : null;
  282. },
  283. /**
  284. * Returns the specified element by ID or the input element if it isn't a string.
  285. *
  286. * @method get
  287. * @param {String/Element} n Element id to look for or element to just pass though.
  288. * @return {Element} Element matching the specified id or null if it wasn't found.
  289. */
  290. get: function(elm) {
  291. var name;
  292. if (elm && this.doc && typeof(elm) == 'string') {
  293. name = elm;
  294. elm = this.doc.getElementById(elm);
  295. // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  296. if (elm && elm.id !== name) {
  297. return this.doc.getElementsByName(name)[1];
  298. }
  299. }
  300. return elm;
  301. },
  302. /**
  303. * Returns the next node that matches selector or function
  304. *
  305. * @method getNext
  306. * @param {Node} node Node to find siblings from.
  307. * @param {String/function} selector Selector CSS expression or function.
  308. * @return {Node} Next node item matching the selector or null if it wasn't found.
  309. */
  310. getNext: function(node, selector) {
  311. return this._findSib(node, selector, 'nextSibling');
  312. },
  313. /**
  314. * Returns the previous node that matches selector or function
  315. *
  316. * @method getPrev
  317. * @param {Node} node Node to find siblings from.
  318. * @param {String/function} selector Selector CSS expression or function.
  319. * @return {Node} Previous node item matching the selector or null if it wasn't found.
  320. */
  321. getPrev: function(node, selector) {
  322. return this._findSib(node, selector, 'previousSibling');
  323. },
  324. // #ifndef jquery
  325. /**
  326. * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
  327. * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
  328. * on more complex patterns.
  329. *
  330. * @method select
  331. * @param {String} selector CSS level 3 pattern to select/find elements by.
  332. * @param {Object} scope Optional root element/scope element to search in.
  333. * @return {Array} Array with all matched elements.
  334. * @example
  335. * // Adds a class to all paragraphs in the currently active editor
  336. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  337. *
  338. * // Adds a class to all spans that have the test class in the currently active editor
  339. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
  340. */
  341. select: function(selector, scope) {
  342. var self = this;
  343. //Sizzle.selectors.cacheLength = 0;
  344. return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []);
  345. },
  346. /**
  347. * Returns true/false if the specified element matches the specified css pattern.
  348. *
  349. * @method is
  350. * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
  351. * @param {String} selector CSS pattern to match the element against.
  352. */
  353. is: function(elm, selector) {
  354. var i;
  355. // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  356. if (elm.length === undefined) {
  357. // Simple all selector
  358. if (selector === '*') {
  359. return elm.nodeType == 1;
  360. }
  361. // Simple selector just elements
  362. if (simpleSelectorRe.test(selector)) {
  363. selector = selector.toLowerCase().split(/,/);
  364. elm = elm.nodeName.toLowerCase();
  365. for (i = selector.length - 1; i >= 0; i--) {
  366. if (selector[i] == elm) {
  367. return true;
  368. }
  369. }
  370. return false;
  371. }
  372. }
  373. // Is non element
  374. if (elm.nodeType && elm.nodeType != 1) {
  375. return false;
  376. }
  377. var elms = elm.nodeType ? [elm] : elm;
  378. return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
  379. },
  380. // #endif
  381. /**
  382. * Adds the specified element to another element or elements.
  383. *
  384. * @method add
  385. * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
  386. * @param {String/Element} name Name of new element to add or existing element to add.
  387. * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
  388. * @param {String} html Optional inner HTML contents to add for each element.
  389. * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
  390. * were passed in.
  391. * @example
  392. * // Adds a new paragraph to the end of the active editor
  393. * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
  394. */
  395. add: function(parentElm, name, attrs, html, create) {
  396. var self = this;
  397. return this.run(parentElm, function(parentElm) {
  398. var newElm;
  399. newElm = is(name, 'string') ? self.doc.createElement(name) : name;
  400. self.setAttribs(newElm, attrs);
  401. if (html) {
  402. if (html.nodeType) {
  403. newElm.appendChild(html);
  404. } else {
  405. self.setHTML(newElm, html);
  406. }
  407. }
  408. return !create ? parentElm.appendChild(newElm) : newElm;
  409. });
  410. },
  411. /**
  412. * Creates a new element.
  413. *
  414. * @method create
  415. * @param {String} name Name of new element.
  416. * @param {Object} attrs Optional object name/value collection with element attributes.
  417. * @param {String} html Optional HTML string to set as inner HTML of the element.
  418. * @return {Element} HTML DOM node element that got created.
  419. * @example
  420. * // Adds an element where the caret/selection is in the active editor
  421. * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
  422. * tinymce.activeEditor.selection.setNode(el);
  423. */
  424. create: function(name, attrs, html) {
  425. return this.add(this.doc.createElement(name), name, attrs, html, 1);
  426. },
  427. /**
  428. * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
  429. *
  430. * @method createHTML
  431. * @param {String} name Name of new element.
  432. * @param {Object} attrs Optional object name/value collection with element attributes.
  433. * @param {String} html Optional HTML string to set as inner HTML of the element.
  434. * @return {String} String with new HTML element, for example: <a href="#">test</a>.
  435. * @example
  436. * // Creates a html chunk and inserts it at the current selection/caret location
  437. * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
  438. */
  439. createHTML: function(name, attrs, html) {
  440. var outHtml = '', key;
  441. outHtml += '<' + name;
  442. for (key in attrs) {
  443. if (attrs.hasOwnProperty(key) && attrs[key] !== null) {
  444. outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
  445. }
  446. }
  447. // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  448. if (typeof(html) != "undefined") {
  449. return outHtml + '>' + html + '</' + name + '>';
  450. }
  451. return outHtml + ' />';
  452. },
  453. /**
  454. * Creates a document fragment out of the specified HTML string.
  455. *
  456. * @method createFragment
  457. * @param {String} html Html string to create fragment from.
  458. * @return {DocumentFragment} Document fragment node.
  459. */
  460. createFragment: function(html) {
  461. var frag, node, doc = this.doc, container;
  462. container = doc.createElement("div");
  463. frag = doc.createDocumentFragment();
  464. if (html) {
  465. container.innerHTML = html;
  466. }
  467. while ((node = container.firstChild)) {
  468. frag.appendChild(node);
  469. }
  470. return frag;
  471. },
  472. /**
  473. * Removes/deletes the specified element(s) from the DOM.
  474. *
  475. * @method remove
  476. * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
  477. * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be
  478. * placed at the location of the removed element.
  479. * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
  480. * were passed in.
  481. * @example
  482. * // Removes all paragraphs in the active editor
  483. * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
  484. *
  485. * // Removes an element by id in the document
  486. * tinymce.DOM.remove('mydiv');
  487. */
  488. remove: function(node, keep_children) {
  489. return this.run(node, function(node) {
  490. var child, parent = node.parentNode;
  491. if (!parent) {
  492. return null;
  493. }
  494. if (keep_children) {
  495. while ((child = node.firstChild)) {
  496. // IE 8 will crash if you don't remove completely empty text nodes
  497. if (!isIE || child.nodeType !== 3 || child.nodeValue) {
  498. parent.insertBefore(child, node);
  499. } else {
  500. node.removeChild(child);
  501. }
  502. }
  503. }
  504. return parent.removeChild(node);
  505. });
  506. },
  507. /**
  508. * Sets the CSS style value on a HTML element. The name can be a camelcase string
  509. * or the CSS style name like background-color.
  510. *
  511. * @method setStyle
  512. * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
  513. * @param {String} na Name of the style value to set.
  514. * @param {String} v Value to set on the style.
  515. * @example
  516. * // Sets a style value on all paragraphs in the currently active editor
  517. * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
  518. *
  519. * // Sets a style value to an element by id in the current document
  520. * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
  521. */
  522. setStyle: function(elm, name, value) {
  523. return this.run(elm, function(elm) {
  524. var self = this, style, key;
  525. if (name) {
  526. if (typeof(name) === 'string') {
  527. style = elm.style;
  528. // Camelcase it, if needed
  529. name = name.replace(/-(\D)/g, function(a, b) {
  530. return b.toUpperCase();
  531. });
  532. // Default px suffix on these
  533. if (typeof(value) === 'number' && !numericCssMap[name]) {
  534. value += 'px';
  535. }
  536. // IE specific opacity
  537. if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") {
  538. style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
  539. }
  540. if (name == "float") {
  541. // Old IE vs modern browsers
  542. name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat";
  543. }
  544. try {
  545. style[name] = value;
  546. } catch (ex) {
  547. // Ignore IE errors
  548. }
  549. // Force update of the style data
  550. if (self.settings.update_styles) {
  551. elm.removeAttribute('data-mce-style');
  552. }
  553. } else {
  554. for (key in name) {
  555. self.setStyle(elm, key, name[key]);
  556. }
  557. }
  558. }
  559. });
  560. },
  561. /**
  562. * Returns the current style or runtime/computed value of an element.
  563. *
  564. * @method getStyle
  565. * @param {String/Element} elm HTML element or element id string to get style from.
  566. * @param {String} name Style name to return.
  567. * @param {Boolean} computed Computed style.
  568. * @return {String} Current style or computed style value of an element.
  569. */
  570. getStyle: function(elm, name, computed) {
  571. elm = this.get(elm);
  572. if (!elm) {
  573. return;
  574. }
  575. // W3C
  576. if (this.doc.defaultView && computed) {
  577. // Remove camelcase
  578. name = name.replace(/[A-Z]/g, function(a){
  579. return '-' + a;
  580. });
  581. try {
  582. return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  583. } catch (ex) {
  584. // Old safari might fail
  585. return null;
  586. }
  587. }
  588. // Camelcase it, if needed
  589. name = name.replace(/-(\D)/g, function(a, b) {
  590. return b.toUpperCase();
  591. });
  592. if (name == 'float') {
  593. name = isIE ? 'styleFloat' : 'cssFloat';
  594. }
  595. // IE & Opera
  596. if (elm.currentStyle && computed) {
  597. return elm.currentStyle[name];
  598. }
  599. return elm.style ? elm.style[name] : undefined;
  600. },
  601. /**
  602. * Sets multiple styles on the specified element(s).
  603. *
  604. * @method setStyles
  605. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
  606. * @param {Object} o Name/Value collection of style items to add to the element(s).
  607. * @example
  608. * // Sets styles on all paragraphs in the currently active editor
  609. * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
  610. *
  611. * // Sets styles to an element by id in the current document
  612. * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
  613. */
  614. setStyles: function(elm, styles) {
  615. this.setStyle(elm, styles);
  616. },
  617. css: function(elm, name, value) {
  618. this.setStyle(elm, name, value);
  619. },
  620. /**
  621. * Removes all attributes from an element or elements.
  622. *
  623. * @method removeAllAttribs
  624. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
  625. */
  626. removeAllAttribs: function(e) {
  627. return this.run(e, function(e) {
  628. var i, attrs = e.attributes;
  629. for (i = attrs.length - 1; i >= 0; i--) {
  630. e.removeAttributeNode(attrs.item(i));
  631. }
  632. });
  633. },
  634. /**
  635. * Sets the specified attribute of an element or elements.
  636. *
  637. * @method setAttrib
  638. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
  639. * @param {String} n Name of attribute to set.
  640. * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
  641. * @example
  642. * // Sets class attribute on all paragraphs in the active editor
  643. * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
  644. *
  645. * // Sets class attribute on a specific element in the current page
  646. * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
  647. */
  648. setAttrib: function(e, n, v) {
  649. var self = this;
  650. // What's the point
  651. if (!e || !n) {
  652. return;
  653. }
  654. return this.run(e, function(e) {
  655. var s = self.settings;
  656. var originalValue = e.getAttribute(n);
  657. if (v !== null) {
  658. switch (n) {
  659. case "style":
  660. if (!is(v, 'string')) {
  661. each(v, function(v, n) {
  662. self.setStyle(e, n, v);
  663. });
  664. return;
  665. }
  666. // No mce_style for elements with these since they might get resized by the user
  667. if (s.keep_values) {
  668. if (v) {
  669. e.setAttribute('data-mce-style', v, 2);
  670. } else {
  671. e.removeAttribute('data-mce-style', 2);
  672. }
  673. }
  674. e.style.cssText = v;
  675. break;
  676. case "class":
  677. e.className = v || ''; // Fix IE null bug
  678. break;
  679. case "src":
  680. case "href":
  681. if (s.keep_values) {
  682. if (s.url_converter) {
  683. v = s.url_converter.call(s.url_converter_scope || self, v, n, e);
  684. }
  685. self.setAttrib(e, 'data-mce-' + n, v, 2);
  686. }
  687. break;
  688. case "shape":
  689. e.setAttribute('data-mce-style', v);
  690. break;
  691. }
  692. }
  693. if (is(v) && v !== null && v.length !== 0) {
  694. e.setAttribute(n, '' + v, 2);
  695. } else {
  696. e.removeAttribute(n, 2);
  697. }
  698. // fire onChangeAttrib event for attributes that have changed
  699. if (originalValue != v && s.onSetAttrib) {
  700. s.onSetAttrib({attrElm: e, attrName: n, attrValue: v});
  701. }
  702. });
  703. },
  704. /**
  705. * Sets two or more specified attributes of an element or elements.
  706. *
  707. * @method setAttribs
  708. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
  709. * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
  710. * @example
  711. * // Sets class and title attributes on all paragraphs in the active editor
  712. * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
  713. *
  714. * // Sets class and title attributes on a specific element in the current page
  715. * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
  716. */
  717. setAttribs: function(elm, attrs) {
  718. var self = this;
  719. return this.run(elm, function(elm) {
  720. each(attrs, function(value, name) {
  721. self.setAttrib(elm, name, value);
  722. });
  723. });
  724. },
  725. /**
  726. * Returns the specified attribute by name.
  727. *
  728. * @method getAttrib
  729. * @param {String/Element} elm Element string id or DOM element to get attribute from.
  730. * @param {String} name Name of attribute to get.
  731. * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
  732. * @return {String} Attribute value string, default value or null if the attribute wasn't found.
  733. */
  734. getAttrib: function(elm, name, defaultVal) {
  735. var value, self = this, undef;
  736. elm = self.get(elm);
  737. if (!elm || elm.nodeType !== 1) {
  738. return defaultVal === undef ? false : defaultVal;
  739. }
  740. if (!is(defaultVal)) {
  741. defaultVal = '';
  742. }
  743. // Try the mce variant for these
  744. if (/^(src|href|style|coords|shape)$/.test(name)) {
  745. value = elm.getAttribute("data-mce-" + name);
  746. if (value) {
  747. return value;
  748. }
  749. }
  750. if (isIE && self.props[name]) {
  751. value = elm[self.props[name]];
  752. value = value && value.nodeValue ? value.nodeValue : value;
  753. }
  754. if (!value) {
  755. value = elm.getAttribute(name, 2);
  756. }
  757. // Check boolean attribs
  758. if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) {
  759. if (elm[self.props[name]] === true && value === '') {
  760. return name;
  761. }
  762. return value ? name : '';
  763. }
  764. // Inner input elements will override attributes on form elements
  765. if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) {
  766. return elm.getAttributeNode(name).nodeValue;
  767. }
  768. if (name === 'style') {
  769. value = value || elm.style.cssText;
  770. if (value) {
  771. value = self.serializeStyle(self.parseStyle(value), elm.nodeName);
  772. if (self.settings.keep_values) {
  773. elm.setAttribute('data-mce-style', value);
  774. }
  775. }
  776. }
  777. // Remove Apple and WebKit stuff
  778. if (isWebKit && name === "class" && value) {
  779. value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
  780. }
  781. // Handle IE issues
  782. if (isIE) {
  783. switch (name) {
  784. case 'rowspan':
  785. case 'colspan':
  786. // IE returns 1 as default value
  787. if (value === 1) {
  788. value = '';
  789. }
  790. break;
  791. case 'size':
  792. // IE returns +0 as default value for size
  793. if (value === '+0' || value === 20 || value === 0) {
  794. value = '';
  795. }
  796. break;
  797. case 'width':
  798. case 'height':
  799. case 'vspace':
  800. case 'checked':
  801. case 'disabled':
  802. case 'readonly':
  803. if (value === 0) {
  804. value = '';
  805. }
  806. break;
  807. case 'hspace':
  808. // IE returns -1 as default value
  809. if (value === -1) {
  810. value = '';
  811. }
  812. break;
  813. case 'maxlength':
  814. case 'tabindex':
  815. // IE returns default value
  816. if (value === 32768 || value === 2147483647 || value === '32768') {
  817. value = '';
  818. }
  819. break;
  820. case 'multiple':
  821. case 'compact':
  822. case 'noshade':
  823. case 'nowrap':
  824. if (value === 65535) {
  825. return name;
  826. }
  827. return defaultVal;
  828. case 'shape':
  829. value = value.toLowerCase();
  830. break;
  831. default:
  832. // IE has odd anonymous function for event attributes
  833. if (name.indexOf('on') === 0 && value) {
  834. value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
  835. }
  836. }
  837. }
  838. return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal;
  839. },
  840. /**
  841. * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
  842. *
  843. * @method getPos
  844. * @param {Element/String} elm HTML element or element id to get x, y position from.
  845. * @param {Element} rootElm Optional root element to stop calculations at.
  846. * @return {object} Absolute position of the specified element object with x, y fields.
  847. */
  848. getPos: function(elm, rootElm) {
  849. var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
  850. elm = self.get(elm);
  851. rootElm = rootElm || doc.body;
  852. if (elm) {
  853. // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
  854. if (rootElm === doc.body && elm.getBoundingClientRect) {
  855. pos = elm.getBoundingClientRect();
  856. rootElm = self.boxModel ? doc.documentElement : doc.body;
  857. // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
  858. // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
  859. x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
  860. y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;
  861. return {x: x, y: y};
  862. }
  863. offsetParent = elm;
  864. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  865. x += offsetParent.offsetLeft || 0;
  866. y += offsetParent.offsetTop || 0;
  867. offsetParent = offsetParent.offsetParent;
  868. }
  869. offsetParent = elm.parentNode;
  870. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  871. x -= offsetParent.scrollLeft || 0;
  872. y -= offsetParent.scrollTop || 0;
  873. offsetParent = offsetParent.parentNode;
  874. }
  875. }
  876. return {x: x, y: y};
  877. },
  878. /**
  879. * Parses the specified style value into an object collection. This parser will also
  880. * merge and remove any redundant items that browsers might have added. It will also convert non-hex
  881. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  882. *
  883. * @method parseStyle
  884. * @param {String} cssText Style value to parse, for example: border:1px solid red;.
  885. * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
  886. */
  887. parseStyle: function(cssText) {
  888. return this.styles.parse(cssText);
  889. },
  890. /**
  891. * Serializes the specified style object into a string.
  892. *
  893. * @method serializeStyle
  894. * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
  895. * @param {String} name Optional element name.
  896. * @return {String} String representation of the style object, for example: border: 1px solid red.
  897. */
  898. serializeStyle: function(styles, name) {
  899. return this.styles.serialize(styles, name);
  900. },
  901. /**
  902. * Adds a style element at the top of the document with the specified cssText content.
  903. *
  904. * @method addStyle
  905. * @param {String} cssText CSS Text style to add to top of head of document.
  906. */
  907. addStyle: function(cssText) {
  908. var self = this, doc = self.doc, head, styleElm;
  909. // Prevent inline from loading the same styles twice
  910. if (self !== DOMUtils.DOM && doc === document) {
  911. var addedStyles = DOMUtils.DOM.addedStyles;
  912. addedStyles = addedStyles || [];
  913. if (addedStyles[cssText]) {
  914. return;
  915. }
  916. addedStyles[cssText] = true;
  917. DOMUtils.DOM.addedStyles = addedStyles;
  918. }
  919. // Create style element if needed
  920. styleElm = doc.getElementById('mceDefaultStyles');
  921. if (!styleElm) {
  922. styleElm = doc.createElement('style');
  923. styleElm.id = 'mceDefaultStyles';
  924. styleElm.type = 'text/css';
  925. head = doc.getElementsByTagName('head')[0];
  926. if (head.firstChild) {
  927. head.insertBefore(styleElm, head.firstChild);
  928. } else {
  929. head.appendChild(styleElm);
  930. }
  931. }
  932. // Append style data to old or new style element
  933. if (styleElm.styleSheet) {
  934. styleElm.styleSheet.cssText += cssText;
  935. } else {
  936. styleElm.appendChild(doc.createTextNode(cssText));
  937. }
  938. },
  939. /**
  940. * Imports/loads the specified CSS file into the document bound to the class.
  941. *
  942. * @method loadCSS
  943. * @param {String} u URL to CSS file to load.
  944. * @example
  945. * // Loads a CSS file dynamically into the current document
  946. * tinymce.DOM.loadCSS('somepath/some.css');
  947. *
  948. * // Loads a CSS file into the currently active editor instance
  949. * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
  950. *
  951. * // Loads a CSS file into an editor instance by id
  952. * tinymce.get('someid').dom.loadCSS('somepath/some.css');
  953. *
  954. * // Loads multiple CSS files into the current document
  955. * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
  956. */
  957. loadCSS: function(url) {
  958. var self = this, doc = self.doc, head;
  959. // Prevent inline from loading the same CSS file twice
  960. if (self !== DOMUtils.DOM && doc === document) {
  961. DOMUtils.DOM.loadCSS(url);
  962. return;
  963. }
  964. if (!url) {
  965. url = '';
  966. }
  967. head = doc.getElementsByTagName('head')[0];
  968. each(url.split(','), function(url) {
  969. var link;
  970. if (self.files[url]) {
  971. return;
  972. }
  973. self.files[url] = true;
  974. link = self.create('link', {rel: 'stylesheet', href: url});
  975. // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  976. // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
  977. // It's ugly but it seems to work fine.
  978. if (isIE && doc.documentMode && doc.recalc) {
  979. link.onload = function() {
  980. if (doc.recalc) {
  981. doc.recalc();
  982. }
  983. link.onload = null;
  984. };
  985. }
  986. head.appendChild(link);
  987. });
  988. },
  989. /**
  990. * Adds a class to the specified element or elements.
  991. *
  992. * @method addClass
  993. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  994. * @param {String} cls Class name to add to each element.
  995. * @return {String/Array} String with new class value or array with new class values for all elements.
  996. * @example
  997. * // Adds a class to all paragraphs in the active editor
  998. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  999. *
  1000. * // Adds a class to a specific element in the current page
  1001. * tinymce.DOM.addClass('mydiv', 'myclass');
  1002. */
  1003. addClass: function(elm, cls) {
  1004. return this.run(elm, function(elm) {
  1005. var clsVal;
  1006. if (!cls) {
  1007. return 0;
  1008. }
  1009. if (this.hasClass(elm, cls)) {
  1010. return elm.className;
  1011. }
  1012. clsVal = this.removeClass(elm, cls);
  1013. elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls;
  1014. return clsVal;
  1015. });
  1016. },
  1017. /**
  1018. * Removes a class from the specified element or elements.
  1019. *
  1020. * @method removeClass
  1021. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  1022. * @param {String} cls Class name to remove from each element.
  1023. * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
  1024. * were passed in.
  1025. * @example
  1026. * // Removes a class from all paragraphs in the active editor
  1027. * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  1028. *
  1029. * // Removes a class from a specific element in the current page
  1030. * tinymce.DOM.removeClass('mydiv', 'myclass');
  1031. */
  1032. removeClass: function(elm, cls) {
  1033. var self = this, re;
  1034. return self.run(elm, function(elm) {
  1035. var val;
  1036. if (self.hasClass(elm, cls)) {
  1037. if (!re) {
  1038. re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g");
  1039. }
  1040. val = elm.className.replace(re, ' ');
  1041. val = trim(val != ' ' ? val : '');
  1042. elm.className = val;
  1043. // Empty class attr
  1044. if (!val) {
  1045. elm.removeAttribute('class');
  1046. elm.removeAttribute('className');
  1047. }
  1048. return val;
  1049. }
  1050. return elm.className;
  1051. });
  1052. },
  1053. /**
  1054. * Returns true if the specified element has the specified class.
  1055. *
  1056. * @method hasClass
  1057. * @param {String/Element} n HTML element or element id string to check CSS class on.
  1058. * @param {String} c CSS class to check for.
  1059. * @return {Boolean} true/false if the specified element has the specified class.
  1060. */
  1061. hasClass: function(elm, cls) {
  1062. elm = this.get(elm);
  1063. if (!elm || !cls) {
  1064. return false;
  1065. }
  1066. return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1;
  1067. },
  1068. /**
  1069. * Toggles the specified class on/off.
  1070. *
  1071. * @method toggleClass
  1072. * @param {Element} elm Element to toggle class on.
  1073. * @param {[type]} cls Class to toggle on/off.
  1074. * @param {[type]} state Optional state to set.
  1075. */
  1076. toggleClass: function(elm, cls, state) {
  1077. state = state === undefined ? !this.hasClass(elm, cls) : state;
  1078. if (this.hasClass(elm, cls) !== state) {
  1079. if (state) {
  1080. this.addClass(elm, cls);
  1081. } else {
  1082. this.removeClass(elm, cls);
  1083. }
  1084. }
  1085. },
  1086. /**
  1087. * Shows the specified element(s) by ID by setting the "display" style.
  1088. *
  1089. * @method show
  1090. * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
  1091. */
  1092. show: function(elm) {
  1093. return this.setStyle(elm, 'display', 'block');
  1094. },
  1095. /**
  1096. * Hides the specified element(s) by ID by setting the "display" style.
  1097. *
  1098. * @method hide
  1099. * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
  1100. * @example
  1101. * // Hides an element by id in the document
  1102. * tinymce.DOM.hide('myid');
  1103. */
  1104. hide: function(elm) {
  1105. return this.setStyle(elm, 'display', 'none');
  1106. },
  1107. /**
  1108. * Returns true/false if the element is hidden or not by checking the "display" style.
  1109. *
  1110. * @method isHidden
  1111. * @param {String/Element} e Id or element to check display state on.
  1112. * @return {Boolean} true/false if the element is hidden or not.
  1113. */
  1114. isHidden: function(elm) {
  1115. elm = this.get(elm);
  1116. return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none';
  1117. },
  1118. /**
  1119. * Returns a unique id. This can be useful when generating elements on the fly.
  1120. * This method will not check if the element already exists.
  1121. *
  1122. * @method uniqueId
  1123. * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
  1124. * @return {String} Unique id.
  1125. */
  1126. uniqueId: function(prefix) {
  1127. return (!prefix ? 'mce_' : prefix) + (this.counter++);
  1128. },
  1129. /**
  1130. * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
  1131. * URLs will get converted, hex color values fixed etc. Check processHTML for details.
  1132. *
  1133. * @method setHTML
  1134. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of.
  1135. * @param {String} h HTML content to set as inner HTML of the element.
  1136. * @example
  1137. * // Sets the inner HTML of all paragraphs in the active editor
  1138. * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
  1139. *
  1140. * // Sets the inner HTML of an element by id in the document
  1141. * tinymce.DOM.setHTML('mydiv', 'some inner html');
  1142. */
  1143. setHTML: function(element, html) {
  1144. var self = this;
  1145. return self.run(element, function(element) {
  1146. if (isIE) {
  1147. // Remove all child nodes, IE keeps empty text nodes in DOM
  1148. while (element.firstChild) {
  1149. element.removeChild(element.firstChild);
  1150. }
  1151. try {
  1152. // IE will remove comments from the beginning
  1153. // unless you padd the contents with something
  1154. element.innerHTML = '<br />' + html;
  1155. element.removeChild(element.firstChild);
  1156. } catch (ex) {
  1157. // IE sometimes produces an unknown runtime error on innerHTML if it's a block element
  1158. // within a block element for example a div inside a p
  1159. // This seems to fix this problem
  1160. // Create new div with HTML contents and a BR in front to keep comments
  1161. var newElement = self.create('div');
  1162. newElement.innerHTML = '<br />' + html;
  1163. // Add all children from div to target
  1164. each(grep(newElement.childNodes), function(node, i) {
  1165. // Skip br element
  1166. if (i && element.canHaveHTML) {
  1167. element.appendChild(node);
  1168. }
  1169. });
  1170. }
  1171. } else {
  1172. element.innerHTML = html;
  1173. }
  1174. return html;
  1175. });
  1176. },
  1177. /**
  1178. * Returns the outer HTML of an element.
  1179. *
  1180. * @method getOuterHTML
  1181. * @param {String/Element} elm Element ID or element object to get outer HTML from.
  1182. * @return {String} Outer HTML string.
  1183. * @example
  1184. * tinymce.DOM.getOuterHTML(editorElement);
  1185. * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
  1186. */
  1187. getOuterHTML: function(elm) {
  1188. var doc, self = this;
  1189. elm = self.get(elm);
  1190. if (!elm) {
  1191. return null;
  1192. }
  1193. if (elm.nodeType === 1 && self.hasOuterHTML) {
  1194. return elm.outerHTML;
  1195. }
  1196. doc = (elm.ownerDocument || self.doc).createElement("body");
  1197. doc.appendChild(elm.cloneNode(true));
  1198. return doc.innerHTML;
  1199. },
  1200. /**
  1201. * Sets the specified outer HTML on an element or elements.
  1202. *
  1203. * @method setOuterHTML
  1204. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
  1205. * @param {Object} html HTML code to set as outer value for the element.
  1206. * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
  1207. * @example
  1208. * // Sets the outer HTML of all paragraphs in the active editor
  1209. * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
  1210. *
  1211. * // Sets the outer HTML of an element by id in the document
  1212. * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
  1213. */
  1214. setOuterHTML: function(elm, html, doc) {
  1215. var self = this;
  1216. return self.run(elm, function(elm) {
  1217. function set() {
  1218. var node, tempElm;
  1219. tempElm = doc.createElement("body");
  1220. tempElm.innerHTML = html;
  1221. node = tempElm.lastChild;
  1222. while (node) {
  1223. self.insertAfter(node.cloneNode(true), elm);
  1224. node = node.previousSibling;
  1225. }
  1226. self.remove(elm);
  1227. }
  1228. // Only set HTML on elements
  1229. if (elm.nodeType == 1) {
  1230. doc = doc || elm.ownerDocument || self.doc;
  1231. if (isIE) {
  1232. try {
  1233. // Try outerHTML for IE it sometimes produces an unknown runtime error
  1234. if (elm.nodeType == 1 && self.hasOuterHTML) {
  1235. elm.outerHTML = html;
  1236. } else {
  1237. set();
  1238. }
  1239. } catch (ex) {
  1240. // Fix for unknown runtime error
  1241. set();
  1242. }
  1243. } else {
  1244. set();
  1245. }
  1246. }
  1247. });
  1248. },
  1249. /**
  1250. * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
  1251. *
  1252. * @method decode
  1253. * @param {String} s String to decode entities on.
  1254. * @return {String} Entity decoded string.
  1255. */
  1256. decode: Entities.decode,
  1257. /**
  1258. * Entity encodes a string. This method encodes the most common entities, such as <>"&.
  1259. *
  1260. * @method encode
  1261. * @param {String} text String to encode with entities.
  1262. * @return {String} Entity encoded string.
  1263. */
  1264. encode: Entities.encodeAllRaw,
  1265. /**
  1266. * Inserts an element after the reference element.
  1267. *
  1268. * @method insertAfter
  1269. * @param {Element} node Element to insert after the reference.
  1270. * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
  1271. * @return {Element/Array} Element that got added or an array with elements.
  1272. */
  1273. insertAfter: function(node, reference_node) {
  1274. reference_node = this.get(reference_node);
  1275. return this.run(node, function(node) {
  1276. var parent, nextSibling;
  1277. parent = reference_node.parentNode;
  1278. nextSibling = reference_node.nextSibling;
  1279. if (nextSibling) {
  1280. parent.insertBefore(node, nextSibling);
  1281. } else {
  1282. parent.appendChild(node);
  1283. }
  1284. return node;
  1285. });
  1286. },
  1287. /**
  1288. * Replaces the specified element or elements with the new element specified. The new element will
  1289. * be cloned if multiple input elements are passed in.
  1290. *
  1291. * @method replace
  1292. * @param {Element} newElm New element to replace old ones with.
  1293. * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
  1294. * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
  1295. */
  1296. replace: function(newElm, oldElm, keepChildren) {
  1297. var self = this;
  1298. return self.run(oldElm, function(oldElm) {
  1299. if (is(oldElm, 'array')) {
  1300. newElm = newElm.cloneNode(true);
  1301. }
  1302. if (keepChildren) {
  1303. each(grep(oldElm.childNodes), function(node) {
  1304. newElm.appendChild(node);
  1305. });
  1306. }
  1307. return oldElm.parentNode.replaceChild(newElm, oldElm);
  1308. });
  1309. },
  1310. /**
  1311. * Renames the specified element and keeps its attributes and children.
  1312. *
  1313. * @method rename
  1314. * @param {Element} elm Element to rename.
  1315. * @param {String} name Name of the new element.
  1316. * @return {Element} New element or the old element if it needed renaming.
  1317. */
  1318. rename: function(elm, name) {
  1319. var self = this, newElm;
  1320. if (elm.nodeName != name.toUpperCase()) {
  1321. // Rename block element
  1322. newElm = self.create(name);
  1323. // Copy attribs to new block
  1324. each(self.getAttribs(elm), function(attr_node) {
  1325. self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName));
  1326. });
  1327. // Replace block
  1328. self.replace(newElm, elm, 1);
  1329. }
  1330. return newElm || elm;
  1331. },
  1332. /**
  1333. * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
  1334. *
  1335. * @method findCommonAncestor
  1336. * @param {Element} a Element to find common ancestor of.
  1337. * @param {Element} b Element to find common ancestor of.
  1338. * @return {Element} Common ancestor element of the two input elements.
  1339. */
  1340. findCommonAncestor: function(a, b) {
  1341. var ps = a, pe;
  1342. while (ps) {
  1343. pe = b;
  1344. while (pe && ps != pe) {
  1345. pe = pe.parentNode;
  1346. }
  1347. if (ps == pe) {
  1348. break;
  1349. }
  1350. ps = ps.parentNode;
  1351. }
  1352. if (!ps && a.ownerDocument) {
  1353. return a.ownerDocument.documentElement;
  1354. }
  1355. return ps;
  1356. },
  1357. /**
  1358. * Parses the specified RGB color value and returns a hex version of that color.
  1359. *
  1360. * @method toHex
  1361. * @param {String} rgbVal RGB string value like rgb(1,2,3)
  1362. * @return {String} Hex version of that RGB value like #FF00FF.
  1363. */
  1364. toHex: function(rgbVal) {
  1365. return this.styles.toHex(Tools.trim(rgbVal));
  1366. },
  1367. /**
  1368. * Executes the specified function on the element by id or dom element node or array of elements/id.
  1369. *
  1370. * @method run
  1371. * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
  1372. * @param {function} f Function to execute for each item.
  1373. * @param {Object} s Optional scope to execute the function in.
  1374. * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
  1375. */
  1376. run: function(elm, func, scope) {
  1377. var self = this, result;
  1378. if (typeof(elm) === 'string') {
  1379. elm = self.get(elm);
  1380. }
  1381. if (!elm) {
  1382. return false;
  1383. }
  1384. scope = scope || this;
  1385. if (!elm.nodeType && (elm.length || elm.length === 0)) {
  1386. result = [];
  1387. each(elm, function(elm, i) {
  1388. if (elm) {
  1389. if (typeof(elm) == 'string') {
  1390. elm = self.get(elm);
  1391. }
  1392. result.push(func.call(scope, elm, i));
  1393. }
  1394. });
  1395. return result;
  1396. }
  1397. return func.call(scope, elm);
  1398. },
  1399. /**
  1400. * Returns a NodeList with attributes for the element.
  1401. *
  1402. * @method getAttribs
  1403. * @param {HTMLElement/string} elm Element node or string id to get attributes from.
  1404. * @return {NodeList} NodeList with attributes.
  1405. */
  1406. getAttribs: function(elm) {
  1407. var attrs;
  1408. elm = this.get(elm);
  1409. if (!elm) {
  1410. return [];
  1411. }
  1412. if (isIE) {
  1413. attrs = [];
  1414. // Object will throw exception in IE
  1415. if (elm.nodeName == 'OBJECT') {
  1416. return elm.attributes;
  1417. }
  1418. // IE doesn't keep the selected attribute if you clone option elements
  1419. if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
  1420. attrs.push({specified: 1, nodeName: 'selected'});
  1421. }
  1422. // It's crazy that this is faster in IE but it's because it returns all attributes all the time
  1423. var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
  1424. elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
  1425. attrs.push({specified: 1, nodeName: a});
  1426. });
  1427. return attrs;
  1428. }
  1429. return elm.attributes;
  1430. },
  1431. /**
  1432. * Returns true/false if the specified node is to be considered empty or not.
  1433. *
  1434. * @example
  1435. * tinymce.DOM.isEmpty(node, {img: true});
  1436. * @method isEmpty
  1437. * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
  1438. * @return {Boolean} true/false if the node is empty or not.
  1439. */
  1440. isEmpty: function(node, elements) {
  1441. var self = this, i, attributes, type, walker, name, brCount = 0;
  1442. node = node.firstChild;
  1443. if (node) {
  1444. walker = new TreeWalker(node, node.parentNode);
  1445. elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
  1446. do {
  1447. type = node.nodeType;
  1448. if (type === 1) {
  1449. // Ignore bogus elements
  1450. if (node.getAttribute('data-mce-bogus')) {
  1451. continue;
  1452. }
  1453. // Keep empty elements like <img />
  1454. name = node.nodeName.toLowerCase();
  1455. if (elements && elements[name]) {
  1456. // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
  1457. if (name === 'br') {
  1458. brCount++;
  1459. continue;
  1460. }
  1461. return false;
  1462. }
  1463. // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
  1464. attributes = self.getAttribs(node);
  1465. i = attributes.length;
  1466. while (i--) {
  1467. name = attributes[i].nodeName;
  1468. if (name === "name" || name === 'data-mce-bookmark') {
  1469. return false;
  1470. }
  1471. }
  1472. }
  1473. // Keep comment nodes
  1474. if (type == 8) {
  1475. return false;
  1476. }
  1477. // Keep non whitespace text nodes
  1478. if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
  1479. return false;
  1480. }
  1481. } while ((node = walker.next()));
  1482. }
  1483. return brCount <= 1;
  1484. },
  1485. /**
  1486. * Creates a new DOM Range object. This will use the native DOM Range API if it's
  1487. * available. If it's not, it will fall back to the custom TinyMCE implementation.
  1488. *
  1489. * @method createRng
  1490. * @return {DOMRange} DOM Range object.
  1491. * @example
  1492. * var rng = tinymce.DOM.createRng();
  1493. * alert(rng.startContainer + "," + rng.startOffset);
  1494. */
  1495. createRng: function() {
  1496. var doc = this.doc;
  1497. return doc.createRange ? doc.createRange() : new Range(this);
  1498. },
  1499. /**
  1500. * Returns the index of the specified node within its parent.
  1501. *
  1502. * @method nodeIndex
  1503. * @param {Node} node Node to look for.
  1504. * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
  1505. * @return {Number} Index of the specified node.
  1506. */
  1507. nodeIndex: function(node, normalized) {
  1508. var idx = 0, lastNodeType, nodeType;
  1509. if (node) {
  1510. for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
  1511. nodeType = node.nodeType;
  1512. // Normalize text nodes
  1513. if (normalized && nodeType == 3) {
  1514. if (nodeType == lastNodeType || !node.nodeValue.length) {
  1515. continue;
  1516. }
  1517. }
  1518. idx++;
  1519. lastNodeType = nodeType;
  1520. }
  1521. }
  1522. return idx;
  1523. },
  1524. /**
  1525. * Splits an element into two new elements and places the specified split
  1526. * element or elements between the new ones. For example splitting the paragraph at the bold element in
  1527. * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
  1528. *
  1529. * @method split
  1530. * @param {Element} parentElm Parent element to split.
  1531. * @param {Element} splitElm Element to split at.
  1532. * @param {Element} replacementElm Optional replacement element to replace the split element with.
  1533. * @return {Element} Returns the split element or the replacement element if that is specified.
  1534. */
  1535. split: function(parentElm, splitElm, replacementElm) {
  1536. var self = this, r = self.createRng(), bef, aft, pa;
  1537. // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
  1538. // but we don't want that in our code since it serves no purpose for the end user
  1539. // For example splitting this html at the bold element:
  1540. // <p>text 1<span><b>CHOP</b></span>text 2</p>
  1541. // would produce:
  1542. // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  1543. // this function will then trim off empty edges and produce:
  1544. // <p>text 1</p><b>CHOP</b><p>text 2</p>
  1545. function trimNode(node) {
  1546. var i, children = node.childNodes, type = node.nodeType;
  1547. function surroundedBySpans(node) {
  1548. var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
  1549. var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
  1550. return previousIsSpan && nextIsSpan;
  1551. }
  1552. if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
  1553. return;
  1554. }
  1555. for (i = children.length - 1; i >= 0; i--) {
  1556. trimNode(children[i]);
  1557. }
  1558. if (type != 9) {
  1559. // Keep non whitespace text nodes
  1560. if (type == 3 && node.nodeValue.length > 0) {
  1561. // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
  1562. // Also keep text nodes with only spaces if surrounded by spans.
  1563. // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
  1564. var trimmedLength = trim(node.nodeValue).length;
  1565. if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
  1566. return;
  1567. }
  1568. } else if (type == 1) {
  1569. // If the only child is a bookmark then move it up
  1570. children = node.childNodes;
  1571. // TODO fix this complex if
  1572. if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
  1573. children[0].getAttribute('data-mce-type') == 'bookmark') {
  1574. node.parentNode.insertBefore(children[0], node);
  1575. }
  1576. // Keep non empty elements or img, hr etc
  1577. if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
  1578. return;
  1579. }
  1580. }
  1581. self.remove(node);
  1582. }
  1583. return node;
  1584. }
  1585. if (parentElm && splitElm) {
  1586. // Get before chunk
  1587. r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
  1588. r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
  1589. bef = r.extractContents();
  1590. // Get after chunk
  1591. r = self.createRng();
  1592. r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
  1593. r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
  1594. aft = r.extractContents();
  1595. // Insert before chunk
  1596. pa = parentElm.parentNode;
  1597. pa.insertBefore(trimNode(bef), parentElm);
  1598. // Insert middle chunk
  1599. if (replacementElm) {
  1600. pa.replaceChild(replacementElm, splitElm);
  1601. } else {
  1602. pa.insertBefore(splitElm, parentElm);
  1603. }
  1604. // Insert after chunk
  1605. pa.insertBefore(trimNode(aft), parentElm);
  1606. self.remove(parentElm);
  1607. return replacementElm || splitElm;
  1608. }
  1609. },
  1610. /**
  1611. * Adds an event handler to the specified object.
  1612. *
  1613. * @method bind
  1614. * @param {Element/Document/Window/Array} target Target element to bind events to.
  1615. * handler to or an array of elements/ids/documents.
  1616. * @param {String} name Name of event handler to add, for example: click.
  1617. * @param {function} func Function to execute when the event occurs.
  1618. * @param {Object} scope Optional scope to execute the function in.
  1619. * @return {function} Function callback handler the same as the one passed in.
  1620. */
  1621. bind: function(target, name, func, scope) {
  1622. var self = this;
  1623. if (Tools.isArray(target)) {
  1624. var i = target.length;
  1625. while (i--) {
  1626. target[i] = self.bind(target[i], name, func, scope);
  1627. }
  1628. return target;
  1629. }
  1630. // Collect all window/document events bound by editor instance
  1631. if (self.settings.collect && (target === self.doc || target === self.win)) {
  1632. self.boundEvents.push([target, name, func, scope]);
  1633. }
  1634. return self.events.bind(target, name, func, scope || self);
  1635. },
  1636. /**
  1637. * Removes the specified event handler by name and function from an element or collection of elements.
  1638. *
  1639. * @method unbind
  1640. * @param {Element/Document/Window/Array} target Target element to unbind events on.
  1641. * @param {String} name Event handler name, for example: "click"
  1642. * @param {function} func Function to remove.
  1643. * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
  1644. * were passed in.
  1645. */
  1646. unbind: function(target, name, func) {
  1647. var self = this, i;
  1648. if (Tools.isArray(target)) {
  1649. i = target.length;
  1650. while (i--) {
  1651. target[i] = self.unbind(target[i], name, func);
  1652. }
  1653. return target;
  1654. }
  1655. // Remove any bound events matching the input
  1656. if (self.boundEvents && (target === self.doc || target === self.win)) {
  1657. i = self.boundEvents.length;
  1658. while (i--) {
  1659. var item = self.boundEvents[i];
  1660. if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
  1661. this.events.unbind(item[0], item[1], item[2]);
  1662. }
  1663. }
  1664. }
  1665. return this.events.unbind(target, name, func);
  1666. },
  1667. /**
  1668. * Fires the specified event name with object on target.
  1669. *
  1670. * @method fire
  1671. * @param {Node/Document/Window} target Target element or object to fire event on.
  1672. * @param {String} name Name of the event to fire.
  1673. * @param {Object} evt Event object to send.
  1674. * @return {Event} Event object.
  1675. */
  1676. fire: function(target, name, evt) {
  1677. return this.events.fire(target, name, evt);
  1678. },
  1679. // Returns the content editable state of a node
  1680. getContentEditable: function(node) {
  1681. var contentEditable;
  1682. // Check type
  1683. if (!node || node.nodeType != 1) {
  1684. return null;
  1685. }
  1686. // Check for fake content editable
  1687. contentEditable = node.getAttribute("data-mce-contenteditable");
  1688. if (contentEditable && contentEditable !== "inherit") {
  1689. return contentEditable;
  1690. }
  1691. // Check for real content editable
  1692. return node.contentEditable !== "inherit" ? node.contentEditable : null;
  1693. },
  1694. getContentEditableParent: function(node) {
  1695. var root = this.getRoot(), state = null;
  1696. for (; node && node !== root; node = node.parentNode) {
  1697. state = this.getContentEditable(node);
  1698. if (state !== null) {
  1699. break;
  1700. }
  1701. }
  1702. return state;
  1703. },
  1704. /**
  1705. * Destroys all internal references to the DOM to solve IE leak issues.
  1706. *
  1707. * @method destroy
  1708. */
  1709. destroy: function() {
  1710. var self = this;
  1711. // Unbind all events bound to window/document by editor instance
  1712. if (self.boundEvents) {
  1713. var i = self.boundEvents.length;
  1714. while (i--) {
  1715. var item = self.boundEvents[i];
  1716. this.events.unbind(item[0], item[1], item[2]);
  1717. }
  1718. self.boundEvents = null;
  1719. }
  1720. // Restore sizzle document to window.document
  1721. // Since the current document might be removed producing "Permission denied" on IE see #6325
  1722. if (Sizzle.setDocument) {
  1723. Sizzle.setDocument();
  1724. }
  1725. self.win = self.doc = self.root = self.events = self.frag = null;
  1726. },
  1727. isChildOf: function(node, parent) {
  1728. while (node) {
  1729. if (parent === node) {
  1730. return true;
  1731. }
  1732. node = node.parentNode;
  1733. }
  1734. return false;
  1735. },
  1736. // #ifdef debug
  1737. dumpRng: function(r) {
  1738. return (
  1739. 'startContainer: ' + r.startContainer.nodeName +
  1740. ', startOffset: ' + r.startOffset +
  1741. ', endContainer: ' + r.endContainer.nodeName +
  1742. ', endOffset: ' + r.endOffset
  1743. );
  1744. },
  1745. // #endif
  1746. _findSib: function(node, selector, name) {
  1747. var self = this, func = selector;
  1748. if (node) {
  1749. // If expression make a function of it using is
  1750. if (typeof(func) == 'string') {
  1751. func = function(node) {
  1752. return self.is(node, selector);
  1753. };
  1754. }
  1755. // Loop all siblings
  1756. for (node = node[name]; node; node = node[name]) {
  1757. if (func(node)) {
  1758. return node;
  1759. }
  1760. }
  1761. }
  1762. return null;
  1763. }
  1764. };
  1765. /**
  1766. * Instance of DOMUtils for the current document.
  1767. *
  1768. * @static
  1769. * @property DOM
  1770. * @type tinymce.dom.DOMUtils
  1771. * @example
  1772. * // Example of how to add a class to some element by id
  1773. * tinymce.DOM.addClass('someid', 'someclass');
  1774. */
  1775. DOMUtils.DOM = new DOMUtils(document);
  1776. return DOMUtils;
  1777. });