4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
13 var each = tinymce.each,
15 isWebKit = tinymce.isWebKit,
17 Entities = tinymce.html.Entities,
18 simpleSelectorRe = /^([a-z0-9],?)+$/i,
19 blockElementsMap = tinymce.html.Schema.blockElementsMap,
20 whiteSpaceRegExp = /^[ \t\r\n]*$/;
23 * Utility class for various DOM manipulation and retrival functions.
25 * @class tinymce.dom.DOMUtils
27 * // Add a class to an element by id in the page
28 * tinymce.DOM.addClass('someid', 'someclass');
30 * // Add a class to an element by id inside the editor
31 * tinyMCE.activeEditor.dom.addClass('someid', 'someclass');
33 tinymce.create('tinymce.dom.DOMUtils', {
37 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
40 "class" : "className",
41 className : "className",
43 disabled : "disabled",
44 maxlength : "maxLength",
45 readonly : "readOnly",
46 selected : "selected",
54 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
58 * @param {Document} d Document reference to bind the utility class to.
59 * @param {settings} s Optional settings collection.
61 DOMUtils : function(d, s) {
62 var t = this, globalStyle, name;
69 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
70 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
71 t.hasOuterHTML = "outerHTML" in d.createElement("a");
73 t.settings = s = tinymce.extend({
79 t.styles = new tinymce.html.Styles({
80 url_converter : s.url_converter,
81 url_converter_scope : s.url_converter_scope
84 // Fix IE6SP2 flicker and check it failed for pre SP2
87 d.execCommand('BackgroundImageCache', false, true);
93 if (isIE && s.schema) {
94 // Add missing HTML 4/5 elements to IE
95 ('abbr article aside audio canvas ' +
96 'details figcaption figure footer ' +
97 'header hgroup mark menu meter nav ' +
98 'output progress section summary ' +
99 'time video').replace(/\w+/g, function(name) {
100 d.createElement(name);
103 // Create all custom elements
104 for (name in s.schema.getCustomElements()) {
105 d.createElement(name);
109 tinymce.addUnload(t.destroy, t);
113 * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not
114 * go above the point of this root node.
117 * @return {Element} Root element for the utility class.
119 getRoot : function() {
120 var t = this, s = t.settings;
122 return (s && t.get(s.root_element)) || t.doc.body;
126 * Returns the viewport of the window.
128 * @method getViewPort
129 * @param {Window} w Optional window to get viewport of.
130 * @return {Object} Viewport object with fields x, y, w and h.
132 getViewPort : function(w) {
135 w = !w ? this.win : w;
137 b = this.boxModel ? d.documentElement : d.body;
139 // Returns viewport size excluding scrollbars
141 x : w.pageXOffset || b.scrollLeft,
142 y : w.pageYOffset || b.scrollTop,
143 w : w.innerWidth || b.clientWidth,
144 h : w.innerHeight || b.clientHeight
149 * Returns the rectangle for a specific element.
152 * @param {Element/String} e Element object or element ID to get rectange from.
153 * @return {object} Rectange for specified element object with x, y, w, h fields.
155 getRect : function(e) {
171 * Returns the size dimensions of the specified element.
174 * @param {Element/String} e Element object or element ID to get rectange from.
175 * @return {object} Rectange for specified element object with w, h fields.
177 getSize : function(e) {
181 w = t.getStyle(e, 'width');
182 h = t.getStyle(e, 'height');
184 // Non pixel value, then force offset/clientWidth
185 if (w.indexOf('px') === -1)
188 // Non pixel value, then force offset/clientWidth
189 if (h.indexOf('px') === -1)
193 w : parseInt(w) || e.offsetWidth || e.clientWidth,
194 h : parseInt(h) || e.offsetHeight || e.clientHeight
199 * Returns a node by the specified selector function. This function will
200 * loop through all parent nodes and call the specified function for each node.
201 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
202 * and the node it found will be returned.
205 * @param {Node/String} n DOM node to search parents on or ID string.
206 * @param {function} f Selection function to execute on each node or CSS pattern.
207 * @param {Node} r Optional root element, never go below this point.
208 * @return {Node} DOM Node or null if it wasn't found.
210 getParent : function(n, f, r) {
211 return this.getParents(n, f, r, false);
215 * Returns a node list of all parents matching the specified selector function or pattern.
216 * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
219 * @param {Node/String} n DOM node to search parents on or ID string.
220 * @param {function} f Selection function to execute on each node or CSS pattern.
221 * @param {Node} r Optional root element, never go below this point.
222 * @return {Array} Array of nodes or null if it wasn't found.
224 getParents : function(n, f, r, c) {
225 var t = this, na, se = t.settings, o = [];
231 r = r || t.getRoot();
233 // Wrap node name as func
234 if (is(f, 'string')) {
238 f = function(n) {return n.nodeType == 1;};
247 if (n == r || !n.nodeType || n.nodeType === 9)
264 * Returns the specified element by ID or the input element if it isn't a string.
267 * @param {String/Element} n Element id to look for or element to just pass though.
268 * @return {Element} Element matching the specified id or null if it wasn't found.
273 if (e && this.doc && typeof(e) == 'string') {
275 e = this.doc.getElementById(e);
277 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
279 return this.doc.getElementsByName(n)[1];
286 * Returns the next node that matches selector or function
289 * @param {Node} node Node to find siblings from.
290 * @param {String/function} selector Selector CSS expression or function.
291 * @return {Node} Next node item matching the selector or null if it wasn't found.
293 getNext : function(node, selector) {
294 return this._findSib(node, selector, 'nextSibling');
298 * Returns the previous node that matches selector or function
301 * @param {Node} node Node to find siblings from.
302 * @param {String/function} selector Selector CSS expression or function.
303 * @return {Node} Previous node item matching the selector or null if it wasn't found.
305 getPrev : function(node, selector) {
306 return this._findSib(node, selector, 'previousSibling');
312 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
313 * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough
314 * on more complex patterns.
317 * @param {String} p CSS level 1 pattern to select/find elements by.
318 * @param {Object} s Optional root element/scope element to search in.
319 * @return {Array} Array with all matched elements.
321 * // Adds a class to all paragraphs in the currently active editor
322 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
324 * // Adds a class to all spans that has the test class in the currently active editor
325 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass')
327 select : function(pa, s) {
330 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
334 * Returns true/false if the specified element matches the specified css pattern.
337 * @param {Node/NodeList} n DOM node to match or an array of nodes to match.
338 * @param {String} selector CSS pattern to match the element agains.
340 is : function(n, selector) {
343 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
344 if (n.length === undefined) {
345 // Simple all selector
346 if (selector === '*')
347 return n.nodeType == 1;
349 // Simple selector just elements
350 if (simpleSelectorRe.test(selector)) {
351 selector = selector.toLowerCase().split(/,/);
352 n = n.nodeName.toLowerCase();
354 for (i = selector.length - 1; i >= 0; i--) {
355 if (selector[i] == n)
363 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
369 * Adds the specified element to another element or elements.
372 * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to.
373 * @param {String/Element} n Name of new element to add or existing element to add.
374 * @param {Object} a Optional object collection with arguments to add to the new element(s).
375 * @param {String} h Optional inner HTML contents to add for each element.
376 * @param {Boolean} c Optional internal state to indicate if it should create or add.
377 * @return {Element/Array} Element that got created or array with elements if multiple elements where passed.
379 * // Adds a new paragraph to the end of the active editor
380 * tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content');
382 add : function(p, n, a, h, c) {
385 return this.run(p, function(p) {
388 e = is(n, 'string') ? t.doc.createElement(n) : n;
398 return !c ? p.appendChild(e) : e;
403 * Creates a new element.
406 * @param {String} n Name of new element.
407 * @param {Object} a Optional object name/value collection with element attributes.
408 * @param {String} h Optional HTML string to set as inner HTML of the element.
409 * @return {Element} HTML DOM node element that got created.
411 * // Adds an element where the caret/selection is in the active editor
412 * var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content');
413 * tinyMCE.activeEditor.selection.setNode(el);
415 create : function(n, a, h) {
416 return this.add(this.doc.createElement(n), n, a, h, 1);
420 * Create HTML string for element. The element will be closed unless an empty inner HTML string is passed.
423 * @param {String} n Name of new element.
424 * @param {Object} a Optional object name/value collection with element attributes.
425 * @param {String} h Optional HTML string to set as inner HTML of the element.
426 * @return {String} String with new HTML element like for example: <a href="#">test</a>.
428 * // Creates a html chunk and inserts it at the current selection/caret location
429 * tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line'));
431 createHTML : function(n, a, h) {
432 var o = '', t = this, k;
437 if (a.hasOwnProperty(k))
438 o += ' ' + k + '="' + t.encode(a[k]) + '"';
441 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
442 if (typeof(h) != "undefined")
443 return o + '>' + h + '</' + n + '>';
449 * Removes/deletes the specified element(s) from the DOM.
452 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
453 * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be placed at the location of the removed element.
454 * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input.
456 * // Removes all paragraphs in the active editor
457 * tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p'));
459 * // Removes a element by id in the document
460 * tinyMCE.DOM.remove('mydiv');
462 remove : function(node, keep_children) {
463 return this.run(node, function(node) {
464 var child, parent = node.parentNode;
470 while (child = node.firstChild) {
471 // IE 8 will crash if you don't remove completely empty text nodes
472 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
473 parent.insertBefore(child, node);
475 node.removeChild(child);
479 return parent.removeChild(node);
484 * Sets the CSS style value on a HTML element. The name can be a camelcase string
485 * or the CSS style name like background-color.
488 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
489 * @param {String} na Name of the style value to set.
490 * @param {String} v Value to set on the style.
492 * // Sets a style value on all paragraphs in the currently active editor
493 * tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red');
495 * // Sets a style value to an element by id in the current document
496 * tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red');
498 setStyle : function(n, na, v) {
501 return t.run(n, function(e) {
506 // Camelcase it, if needed
507 na = na.replace(/-(\D)/g, function(a, b){
508 return b.toUpperCase();
511 // Default px suffix on these
512 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
517 // IE specific opacity
519 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
521 if (!n.currentStyle || !n.currentStyle.hasLayout)
522 s.display = 'inline-block';
525 // Fix for older browsers
526 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
530 isIE ? s.styleFloat = v : s.cssFloat = v;
537 // Force update of the style data
538 if (t.settings.update_styles)
539 t.setAttrib(e, 'data-mce-style');
544 * Returns the current style or runtime/computed value of a element.
547 * @param {String/Element} n HTML element or element id string to get style from.
548 * @param {String} na Style name to return.
549 * @param {Boolean} c Computed style.
550 * @return {String} Current style or computed style value of a element.
552 getStyle : function(n, na, c) {
559 if (this.doc.defaultView && c) {
561 na = na.replace(/[A-Z]/g, function(a){
566 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
568 // Old safari might fail
573 // Camelcase it, if needed
574 na = na.replace(/-(\D)/g, function(a, b){
575 return b.toUpperCase();
579 na = isIE ? 'styleFloat' : 'cssFloat';
582 if (n.currentStyle && c)
583 return n.currentStyle[na];
585 return n.style ? n.style[na] : undefined;
589 * Sets multiple styles on the specified element(s).
592 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
593 * @param {Object} o Name/Value collection of style items to add to the element(s).
595 * // Sets styles on all paragraphs in the currently active editor
596 * tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'});
598 * // Sets styles to an element by id in the current document
599 * tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'});
601 setStyles : function(e, o) {
602 var t = this, s = t.settings, ol;
604 ol = s.update_styles;
607 each(o, function(v, n) {
612 s.update_styles = ol;
614 t.setAttrib(e, s.cssText);
618 * Removes all attributes from an element or elements.
620 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
622 removeAllAttribs: function(e) {
623 return this.run(e, function(e) {
624 var i, attrs = e.attributes;
625 for (i = attrs.length - 1; i >= 0; i--) {
626 e.removeAttributeNode(attrs.item(i));
632 * Sets the specified attributes value of a element or elements.
635 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
636 * @param {String} n Name of attribute to set.
637 * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead.
639 * // Sets an attribute to all paragraphs in the active editor
640 * tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass');
642 * // Sets an attribute to a specific element in the current page
643 * tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass');
645 setAttrib : function(e, n, v) {
653 if (t.settings.strict)
656 return this.run(e, function(e) {
661 if (!is(v, 'string')) {
662 each(v, function(v, n) {
669 // No mce_style for elements with these since they might get resized by the user
671 if (v && !t._isRes(v))
672 e.setAttribute('data-mce-style', v, 2);
674 e.removeAttribute('data-mce-style', 2);
681 e.className = v || ''; // Fix IE null bug
688 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
690 t.setAttrib(e, 'data-mce-' + n, v, 2);
696 e.setAttribute('data-mce-style', v);
700 if (is(v) && v !== null && v.length !== 0)
701 e.setAttribute(n, '' + v, 2);
703 e.removeAttribute(n, 2);
708 * Sets the specified attributes of a element or elements.
711 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on.
712 * @param {Object} o Name/Value collection of attribute items to add to the element(s).
714 * // Sets some attributes to all paragraphs in the active editor
715 * tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'});
717 * // Sets some attributes to a specific element in the current page
718 * tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'});
720 setAttribs : function(e, o) {
723 return this.run(e, function(e) {
724 each(o, function(v, n) {
725 t.setAttrib(e, n, v);
731 * Returns the specified attribute by name.
734 * @param {String/Element} e Element string id or DOM element to get attribute from.
735 * @param {String} n Name of attribute to get.
736 * @param {String} dv Optional default value to return if the attribute didn't exist.
737 * @return {String} Attribute value string, default value or null if the attribute wasn't found.
739 getAttrib : function(e, n, dv) {
740 var v, t = this, undef;
744 if (!e || e.nodeType !== 1)
745 return dv === undef ? false : dv;
750 // Try the mce variant for these
751 if (/^(src|href|style|coords|shape)$/.test(n)) {
752 v = e.getAttribute("data-mce-" + n);
758 if (isIE && t.props[n]) {
760 v = v && v.nodeValue ? v.nodeValue : v;
764 v = e.getAttribute(n, 2);
766 // Check boolean attribs
767 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
768 if (e[t.props[n]] === true && v === '')
774 // Inner input elements will override attributes on form elements
775 if (e.nodeName === "FORM" && e.getAttributeNode(n))
776 return e.getAttributeNode(n).nodeValue;
779 v = v || e.style.cssText;
782 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
784 if (t.settings.keep_values && !t._isRes(v))
785 e.setAttribute('data-mce-style', v);
789 // Remove Apple and WebKit stuff
790 if (isWebKit && n === "class" && v)
791 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
798 // IE returns 1 as default value
805 // IE returns +0 as default value for size
806 if (v === '+0' || v === 20 || v === 0)
823 // IE returns -1 as default value
831 // IE returns default value
832 if (v === 32768 || v === 2147483647 || v === '32768')
851 // IE has odd anonymous function for event attributes
852 if (n.indexOf('on') === 0 && v)
853 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
857 return (v !== undef && v !== null && v !== '') ? '' + v : dv;
861 * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields.
864 * @param {Element/String} n HTML element or element id to get x, y position from.
865 * @param {Element} ro Optional root element to stop calculations at.
866 * @return {object} Absolute position of the specified element object with x, y fields.
868 getPos : function(n, ro) {
869 var t = this, x = 0, y = 0, e, d = t.doc, r;
875 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
876 if (n.getBoundingClientRect) {
877 n = n.getBoundingClientRect();
878 e = t.boxModel ? d.documentElement : d.body;
880 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
881 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
882 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
883 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
885 return {x : x, y : y};
889 while (r && r != ro && r.nodeType) {
890 x += r.offsetLeft || 0;
891 y += r.offsetTop || 0;
896 while (r && r != ro && r.nodeType) {
897 x -= r.scrollLeft || 0;
898 y -= r.scrollTop || 0;
903 return {x : x, y : y};
907 * Parses the specified style value into an object collection. This parser will also
908 * merge and remove any redundant items that browsers might have added. It will also convert non hex
909 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
912 * @param {String} st Style value to parse for example: border:1px solid red;.
913 * @return {Object} Object representation of that style like {border : '1px solid red'}
915 parseStyle : function(st) {
916 return this.styles.parse(st);
920 * Serializes the specified style object into a string.
922 * @method serializeStyle
923 * @param {Object} o Object to serialize as string for example: {border : '1px solid red'}
924 * @param {String} name Optional element name.
925 * @return {String} String representation of the style object for example: border: 1px solid red.
927 serializeStyle : function(o, name) {
928 return this.styles.serialize(o, name);
932 * Imports/loads the specified CSS file into the document bound to the class.
935 * @param {String} u URL to CSS file to load.
937 * // Loads a CSS file dynamically into the current document
938 * tinymce.DOM.loadCSS('somepath/some.css');
940 * // Loads a CSS file into the currently active editor instance
941 * tinyMCE.activeEditor.dom.loadCSS('somepath/some.css');
943 * // Loads a CSS file into an editor instance by id
944 * tinyMCE.get('someid').dom.loadCSS('somepath/some.css');
946 * // Loads multiple CSS files into the current document
947 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
949 loadCSS : function(u) {
950 var t = this, d = t.doc, head;
955 head = t.select('head')[0];
957 each(u.split(','), function(u) {
964 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
966 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
967 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
968 // It's ugly but it seems to work fine.
969 if (isIE && d.documentMode && d.recalc) {
970 link.onload = function() {
978 head.appendChild(link);
983 * Adds a class to the specified element or elements.
986 * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
987 * @param {String} c Class name to add to each element.
988 * @return {String/Array} String with new class value or array with new class values for all elements.
990 * // Adds a class to all paragraphs in the active editor
991 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
993 * // Adds a class to a specific element in the current page
994 * tinyMCE.DOM.addClass('mydiv', 'myclass');
996 addClass : function(e, c) {
997 return this.run(e, function(e) {
1003 if (this.hasClass(e, c))
1006 o = this.removeClass(e, c);
1008 return e.className = (o != '' ? (o + ' ') : '') + c;
1013 * Removes a class from the specified element or elements.
1015 * @method removeClass
1016 * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
1017 * @param {String} c Class name to remove to each element.
1018 * @return {String/Array} String with new class value or array with new class values for all elements.
1020 * // Removes a class from all paragraphs in the active editor
1021 * tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
1023 * // Removes a class from a specific element in the current page
1024 * tinyMCE.DOM.removeClass('mydiv', 'myclass');
1026 removeClass : function(e, c) {
1029 return t.run(e, function(e) {
1032 if (t.hasClass(e, c)) {
1034 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
1036 v = e.className.replace(re, ' ');
1037 v = tinymce.trim(v != ' ' ? v : '');
1043 e.removeAttribute('class');
1044 e.removeAttribute('className');
1055 * Returns true if the specified element has the specified class.
1058 * @param {String/Element} n HTML element or element id string to check CSS class on.
1059 * @param {String} c CSS class to check for.
1060 * @return {Boolean} true/false if the specified element has the specified class.
1062 hasClass : function(n, c) {
1068 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
1072 * Shows the specified element(s) by ID by setting the "display" style.
1075 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show.
1077 show : function(e) {
1078 return this.setStyle(e, 'display', 'block');
1082 * Hides the specified element(s) by ID by setting the "display" style.
1085 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
1087 * // Hides a element by id in the document
1088 * tinymce.DOM.hide('myid');
1090 hide : function(e) {
1091 return this.setStyle(e, 'display', 'none');
1095 * Returns true/false if the element is hidden or not by checking the "display" style.
1098 * @param {String/Element} e Id or element to check display state on.
1099 * @return {Boolean} true/false if the element is hidden or not.
1101 isHidden : function(e) {
1104 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
1108 * Returns a unique id. This can be useful when generating elements on the fly.
1109 * This method will not check if the element allready exists.
1112 * @param {String} p Optional prefix to add infront of all ids defaults to "mce_".
1113 * @return {String} Unique id.
1115 uniqueId : function(p) {
1116 return (!p ? 'mce_' : p) + (this.counter++);
1120 * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means
1121 * URLs will get converted, hex color values fixed etc. Check processHTML for details.
1124 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside.
1125 * @param {String} h HTML content to set as inner HTML of the element.
1127 * // Sets the inner HTML of all paragraphs in the active editor
1128 * tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html');
1130 * // Sets the inner HTML of a element by id in the document
1131 * tinyMCE.DOM.setHTML('mydiv', 'some inner html');
1133 setHTML : function(element, html) {
1136 return self.run(element, function(element) {
1138 // Remove all child nodes, IE keeps empty text nodes in DOM
1139 while (element.firstChild)
1140 element.removeChild(element.firstChild);
1143 // IE will remove comments from the beginning
1144 // unless you padd the contents with something
1145 element.innerHTML = '<br />' + html;
1146 element.removeChild(element.firstChild);
1148 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
1149 // This seems to fix this problem
1151 // Create new div with HTML contents and a BR infront to keep comments
1152 element = self.create('div');
1153 element.innerHTML = '<br />' + html;
1155 // Add all children from div to target
1156 each (element.childNodes, function(node, i) {
1159 element.appendChild(node);
1163 element.innerHTML = html;
1170 * Returns the outer HTML of an element.
1172 * @method getOuterHTML
1173 * @param {String/Element} elm Element ID or element object to get outer HTML from.
1174 * @return {String} Outer HTML string.
1176 * tinymce.DOM.getOuterHTML(editorElement);
1177 * tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody());
1179 getOuterHTML : function(elm) {
1180 var doc, self = this;
1182 elm = self.get(elm);
1187 if (elm.nodeType === 1 && self.hasOuterHTML)
1188 return elm.outerHTML;
1190 doc = (elm.ownerDocument || self.doc).createElement("body");
1191 doc.appendChild(elm.cloneNode(true));
1193 return doc.innerHTML;
1197 * Sets the specified outer HTML on a element or elements.
1199 * @method setOuterHTML
1200 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on.
1201 * @param {Object} h HTML code to set as outer value for the element.
1202 * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class.
1204 * // Sets the outer HTML of all paragraphs in the active editor
1205 * tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '<div>some html</div>');
1207 * // Sets the outer HTML of a element by id in the document
1208 * tinyMCE.DOM.setOuterHTML('mydiv', '<div>some html</div>');
1210 setOuterHTML : function(e, h, d) {
1213 function setHTML(e, h, d) {
1216 tp = d.createElement("body");
1221 t.insertAfter(n.cloneNode(true), e);
1222 n = n.previousSibling;
1228 return this.run(e, function(e) {
1231 // Only set HTML on elements
1232 if (e.nodeType == 1) {
1233 d = d || e.ownerDocument || t.doc;
1237 // Try outerHTML for IE it sometimes produces an unknown runtime error
1238 if (isIE && e.nodeType == 1)
1243 // Fix for unknown runtime error
1253 * Entity decode a string, resolves any HTML entities like å.
1256 * @param {String} s String to decode entities on.
1257 * @return {String} Entity decoded string.
1259 decode : Entities.decode,
1262 * Entity encodes a string, encodes the most common entities <>"& into entities.
1265 * @param {String} text String to encode with entities.
1266 * @return {String} Entity encoded string.
1268 encode : Entities.encodeAllRaw,
1271 * Inserts a element after the reference element.
1273 * @method insertAfter
1274 * @param {Element} node Element to insert after the reference.
1275 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
1276 * @return {Element/Array} Element that got added or an array with elements.
1278 insertAfter : function(node, reference_node) {
1279 reference_node = this.get(reference_node);
1281 return this.run(node, function(node) {
1282 var parent, nextSibling;
1284 parent = reference_node.parentNode;
1285 nextSibling = reference_node.nextSibling;
1288 parent.insertBefore(node, nextSibling);
1290 parent.appendChild(node);
1297 * Returns true/false if the specified element is a block element or not.
1300 * @param {Node/String} node Element/Node to check.
1301 * @return {Boolean} True/False state if the node is a block element or not.
1303 isBlock : function(node) {
1304 var type = node.nodeType;
1306 // If it's a node then check the type and use the nodeName
1308 return !!(type === 1 && blockElementsMap[node.nodeName]);
1310 return !!blockElementsMap[node];
1314 * Replaces the specified element or elements with the specified element, the new element will
1315 * be cloned if multiple inputs elements are passed.
1318 * @param {Element} n New element to replace old ones with.
1319 * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace.
1320 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
1322 replace : function(n, o, k) {
1326 n = n.cloneNode(true);
1328 return t.run(o, function(o) {
1330 each(tinymce.grep(o.childNodes), function(c) {
1335 return o.parentNode.replaceChild(n, o);
1340 * Renames the specified element to a new name and keep it's attributes and children.
1343 * @param {Element} elm Element to rename.
1344 * @param {String} name Name of the new element.
1345 * @return New element or the old element if it needed renaming.
1347 rename : function(elm, name) {
1348 var t = this, newElm;
1350 if (elm.nodeName != name.toUpperCase()) {
1351 // Rename block element
1352 newElm = t.create(name);
1354 // Copy attribs to new block
1355 each(t.getAttribs(elm), function(attr_node) {
1356 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
1360 t.replace(newElm, elm, 1);
1363 return newElm || elm;
1367 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
1369 * @method findCommonAncestor
1370 * @param {Element} a Element to find common ancestor of.
1371 * @param {Element} b Element to find common ancestor of.
1372 * @return {Element} Common ancestor element of the two input elements.
1374 findCommonAncestor : function(a, b) {
1380 while (pe && ps != pe)
1389 if (!ps && a.ownerDocument)
1390 return a.ownerDocument.documentElement;
1396 * Parses the specified RGB color value and returns a hex version of that color.
1399 * @param {String} s RGB string value like rgb(1,2,3)
1400 * @return {String} Hex version of that RGB value like #FF00FF.
1402 toHex : function(s) {
1403 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
1406 s = parseInt(s).toString(16);
1408 return s.length > 1 ? s : '0' + s; // 0 -> 00
1412 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
1421 * Returns a array of all single CSS classes in the document. A single CSS class is a simple
1422 * rule like ".class" complex ones like "div td.class" will not be added to output.
1424 * @method getClasses
1425 * @return {Array} Array with class objects each object has a class field might be other fields in the future.
1427 getClasses : function() {
1428 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
1433 function addClasses(s) {
1435 each(s.imports, function(r) {
1439 each(s.cssRules || s.rules, function(r) {
1440 // Real type or fake it on IE
1441 switch (r.type || 1) {
1444 if (r.selectorText) {
1445 each(r.selectorText.split(','), function(v) {
1446 v = v.replace(/^\s*|\s*$|^\s\./g, "");
1448 // Is internal or it doesn't contain a class
1449 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
1452 // Remove everything but class name
1454 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
1457 if (f && !(v = f(v, ov)))
1461 cl.push({'class' : v});
1470 addClasses(r.styleSheet);
1477 each(t.doc.styleSheets, addClasses);
1489 * Executes the specified function on the element by id or dom element node or array of elements/id.
1492 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
1493 * @param {function} f Function to execute for each item.
1494 * @param {Object} s Optional scope to execute the function in.
1495 * @return {Object/Array} Single object or array with objects depending on multiple input or not.
1497 run : function(e, f, s) {
1500 if (t.doc && typeof(e) === 'string')
1507 if (!e.nodeType && (e.length || e.length === 0)) {
1510 each(e, function(e, i) {
1512 if (typeof(e) == 'string')
1513 e = t.doc.getElementById(e);
1515 o.push(f.call(s, e, i));
1522 return f.call(s, e);
1526 * Returns an NodeList with attributes for the element.
1528 * @method getAttribs
1529 * @param {HTMLElement/string} n Element node or string id to get attributes from.
1530 * @return {NodeList} NodeList with attributes.
1532 getAttribs : function(n) {
1543 // Object will throw exception in IE
1544 if (n.nodeName == 'OBJECT')
1545 return n.attributes;
1547 // IE doesn't keep the selected attribute if you clone option elements
1548 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
1549 o.push({specified : 1, nodeName : 'selected'});
1551 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
1552 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
1553 o.push({specified : 1, nodeName : a});
1559 return n.attributes;
1563 * Returns true/false if the specified node is to be considered empty or not.
1566 * tinymce.DOM.isEmpty(node, {img : true});
1568 * @param {Object} elements Optional name/value object with elements that are automatically treated as non empty elements.
1569 * @return {Boolean} true/false if the node is empty or not.
1571 isEmpty : function(node, elements) {
1572 var self = this, i, attributes, type, walker, name;
1574 node = node.firstChild;
1576 walker = new tinymce.dom.TreeWalker(node);
1577 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
1580 type = node.nodeType;
1583 // Ignore bogus elements
1584 if (node.getAttribute('data-mce-bogus'))
1587 // Keep empty elements like <img />
1588 if (elements && elements[node.nodeName.toLowerCase()])
1591 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
1592 attributes = self.getAttribs(node);
1593 i = node.attributes.length;
1595 name = node.attributes[i].nodeName;
1596 if (name === "name" || name === 'data-mce-bookmark')
1601 // Keep non whitespace text nodes
1602 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
1604 } while (node = walker.next());
1611 * Destroys all internal references to the DOM to solve IE leak issues.
1615 destroy : function(s) {
1621 t.win = t.doc = t.root = t.events = null;
1623 // Manual destroy then remove unload handler
1625 tinymce.removeUnload(t.destroy);
1629 * Created a new DOM Range object. This will use the native DOM Range API if it's
1630 * available if it's not it will fallback to the custom TinyMCE implementation.
1633 * @return {DOMRange} DOM Range object.
1635 * var rng = tinymce.DOM.createRng();
1636 * alert(rng.startContainer + "," + rng.startOffset);
1638 createRng : function() {
1641 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
1645 * Returns the index of the specified node within it's parent.
1647 * @param {Node} node Node to look for.
1648 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
1649 * @return {Number} Index of the specified node.
1651 nodeIndex : function(node, normalized) {
1652 var idx = 0, lastNodeType, lastNode, nodeType;
1655 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
1656 nodeType = node.nodeType;
1658 // Normalize text nodes
1659 if (normalized && nodeType == 3) {
1660 if (nodeType == lastNodeType || !node.nodeValue.length)
1664 lastNodeType = nodeType;
1672 * Splits an element into two new elements and places the specified split
1673 * element or element between the new ones. For example splitting the paragraph at the bold element in
1674 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
1677 * @param {Element} pe Parent element to split.
1678 * @param {Element} e Element to split at.
1679 * @param {Element} re Optional replacement element to replace the split element by.
1680 * @return {Element} Returns the split element or the replacement element if that is specified.
1682 split : function(pe, e, re) {
1683 var t = this, r = t.createRng(), bef, aft, pa;
1685 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
1686 // but we don't want that in our code since it serves no purpose for the end user
1687 // For example if this is chopped:
1688 // <p>text 1<span><b>CHOP</b></span>text 2</p>
1690 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
1691 // this function will then trim of empty edges and produce:
1692 // <p>text 1</p><b>CHOP</b><p>text 2</p>
1693 function trim(node) {
1694 var i, children = node.childNodes, type = node.nodeType;
1696 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
1699 for (i = children.length - 1; i >= 0; i--)
1703 // Keep non whitespace text nodes
1704 if (type == 3 && node.nodeValue.length > 0) {
1705 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
1706 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
1708 } else if (type == 1) {
1709 // If the only child is a bookmark then move it up
1710 children = node.childNodes;
1711 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
1712 node.parentNode.insertBefore(children[0], node);
1714 // Keep non empty elements or img, hr etc
1715 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
1727 r.setStart(pe.parentNode, t.nodeIndex(pe));
1728 r.setEnd(e.parentNode, t.nodeIndex(e));
1729 bef = r.extractContents();
1733 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
1734 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
1735 aft = r.extractContents();
1737 // Insert before chunk
1739 pa.insertBefore(trim(bef), pe);
1741 // Insert middle chunk
1743 pa.replaceChild(re, e);
1745 pa.insertBefore(e, pe);
1747 // Insert after chunk
1748 pa.insertBefore(trim(aft), pe);
1756 * Adds an event handler to the specified object.
1759 * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents.
1760 * @param {String} n Name of event handler to add for example: click.
1761 * @param {function} f Function to execute when the event occurs.
1762 * @param {Object} s Optional scope to execute the function in.
1763 * @return {function} Function callback handler the same as the one passed in.
1765 bind : function(target, name, func, scope) {
1769 t.events = new tinymce.dom.EventUtils();
1771 return t.events.add(target, name, func, scope || this);
1775 * Removes the specified event handler by name and function from a element or collection of elements.
1778 * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
1779 * @param {String} n Event handler name like for example: "click"
1780 * @param {function} f Function to remove.
1781 * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in.
1783 unbind : function(target, name, func) {
1787 t.events = new tinymce.dom.EventUtils();
1789 return t.events.remove(target, name, func);
1794 dumpRng : function(r) {
1795 return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset;
1800 _findSib : function(node, selector, name) {
1801 var t = this, f = selector;
1804 // If expression make a function of it using is
1805 if (is(f, 'string')) {
1806 f = function(node) {
1807 return t.is(node, selector);
1811 // Loop all siblings
1812 for (node = node[name]; node; node = node[name]) {
1821 _isRes : function(c) {
1822 // Is live resizble element
1823 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
1827 walk : function(n, f, s) {
1828 var d = this.doc, w;
1830 if (d.createTreeWalker) {
1831 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
1833 while ((n = w.nextNode()) != null)
1834 f.call(s || this, n);
1836 tinymce.walk(n, f, 'childNodes', s);
1841 toRGB : function(s) {
1842 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
1849 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
1858 * Instance of DOMUtils for the current document.
1862 * @type tinymce.dom.DOMUtils
1864 * // Example of how to add a class to some element by id
1865 * tinymce.DOM.addClass('someid', 'someclass');
1867 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});