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;
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);
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);
104 tinymce.addUnload(t.destroy, t);
108 * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not
109 * go above the point of this root node.
112 * @return {Element} Root element for the utility class.
114 getRoot : function() {
115 var t = this, s = t.settings;
117 return (s && t.get(s.root_element)) || t.doc.body;
121 * Returns the viewport of the window.
123 * @method getViewPort
124 * @param {Window} w Optional window to get viewport of.
125 * @return {Object} Viewport object with fields x, y, w and h.
127 getViewPort : function(w) {
130 w = !w ? this.win : w;
132 b = this.boxModel ? d.documentElement : d.body;
134 // Returns viewport size excluding scrollbars
136 x : w.pageXOffset || b.scrollLeft,
137 y : w.pageYOffset || b.scrollTop,
138 w : w.innerWidth || b.clientWidth,
139 h : w.innerHeight || b.clientHeight
144 * Returns the rectangle for a specific element.
147 * @param {Element/String} e Element object or element ID to get rectange from.
148 * @return {object} Rectange for specified element object with x, y, w, h fields.
150 getRect : function(e) {
166 * Returns the size dimensions of the specified element.
169 * @param {Element/String} e Element object or element ID to get rectange from.
170 * @return {object} Rectange for specified element object with w, h fields.
172 getSize : function(e) {
176 w = t.getStyle(e, 'width');
177 h = t.getStyle(e, 'height');
179 // Non pixel value, then force offset/clientWidth
180 if (w.indexOf('px') === -1)
183 // Non pixel value, then force offset/clientWidth
184 if (h.indexOf('px') === -1)
188 w : parseInt(w) || e.offsetWidth || e.clientWidth,
189 h : parseInt(h) || e.offsetHeight || e.clientHeight
194 * Returns a node by the specified selector function. This function will
195 * loop through all parent nodes and call the specified function for each node.
196 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
197 * and the node it found will be returned.
200 * @param {Node/String} n DOM node to search parents on or ID string.
201 * @param {function} f Selection function to execute on each node or CSS pattern.
202 * @param {Node} r Optional root element, never go below this point.
203 * @return {Node} DOM Node or null if it wasn't found.
205 getParent : function(n, f, r) {
206 return this.getParents(n, f, r, false);
210 * Returns a node list of all parents matching the specified selector function or pattern.
211 * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
214 * @param {Node/String} n DOM node to search parents on or ID string.
215 * @param {function} f Selection function to execute on each node or CSS pattern.
216 * @param {Node} r Optional root element, never go below this point.
217 * @return {Array} Array of nodes or null if it wasn't found.
219 getParents : function(n, f, r, c) {
220 var t = this, na, se = t.settings, o = [];
226 r = r || t.getRoot();
228 // Wrap node name as func
229 if (is(f, 'string')) {
233 f = function(n) {return n.nodeType == 1;};
242 if (n == r || !n.nodeType || n.nodeType === 9)
259 * Returns the specified element by ID or the input element if it isn't a string.
262 * @param {String/Element} n Element id to look for or element to just pass though.
263 * @return {Element} Element matching the specified id or null if it wasn't found.
268 if (e && this.doc && typeof(e) == 'string') {
270 e = this.doc.getElementById(e);
272 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
274 return this.doc.getElementsByName(n)[1];
281 * Returns the next node that matches selector or function
284 * @param {Node} node Node to find siblings from.
285 * @param {String/function} selector Selector CSS expression or function.
286 * @return {Node} Next node item matching the selector or null if it wasn't found.
288 getNext : function(node, selector) {
289 return this._findSib(node, selector, 'nextSibling');
293 * Returns the previous node that matches selector or function
296 * @param {Node} node Node to find siblings from.
297 * @param {String/function} selector Selector CSS expression or function.
298 * @return {Node} Previous node item matching the selector or null if it wasn't found.
300 getPrev : function(node, selector) {
301 return this._findSib(node, selector, 'previousSibling');
307 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
308 * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough
309 * on more complex patterns.
312 * @param {String} p CSS level 1 pattern to select/find elements by.
313 * @param {Object} s Optional root element/scope element to search in.
314 * @return {Array} Array with all matched elements.
316 * // Adds a class to all paragraphs in the currently active editor
317 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
319 * // Adds a class to all spans that has the test class in the currently active editor
320 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass')
322 select : function(pa, s) {
325 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
329 * Returns true/false if the specified element matches the specified css pattern.
332 * @param {Node/NodeList} n DOM node to match or an array of nodes to match.
333 * @param {String} selector CSS pattern to match the element agains.
335 is : function(n, selector) {
338 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
339 if (n.length === undefined) {
340 // Simple all selector
341 if (selector === '*')
342 return n.nodeType == 1;
344 // Simple selector just elements
345 if (simpleSelectorRe.test(selector)) {
346 selector = selector.toLowerCase().split(/,/);
347 n = n.nodeName.toLowerCase();
349 for (i = selector.length - 1; i >= 0; i--) {
350 if (selector[i] == n)
358 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
364 * Adds the specified element to another element or elements.
367 * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to.
368 * @param {String/Element} n Name of new element to add or existing element to add.
369 * @param {Object} a Optional object collection with arguments to add to the new element(s).
370 * @param {String} h Optional inner HTML contents to add for each element.
371 * @param {Boolean} c Optional internal state to indicate if it should create or add.
372 * @return {Element/Array} Element that got created or array with elements if multiple elements where passed.
374 * // Adds a new paragraph to the end of the active editor
375 * tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content');
377 add : function(p, n, a, h, c) {
380 return this.run(p, function(p) {
383 e = is(n, 'string') ? t.doc.createElement(n) : n;
393 return !c ? p.appendChild(e) : e;
398 * Creates a new element.
401 * @param {String} n Name of new element.
402 * @param {Object} a Optional object name/value collection with element attributes.
403 * @param {String} h Optional HTML string to set as inner HTML of the element.
404 * @return {Element} HTML DOM node element that got created.
406 * // Adds an element where the caret/selection is in the active editor
407 * var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content');
408 * tinyMCE.activeEditor.selection.setNode(el);
410 create : function(n, a, h) {
411 return this.add(this.doc.createElement(n), n, a, h, 1);
415 * Create HTML string for element. The element will be closed unless an empty inner HTML string is passed.
418 * @param {String} n Name of new element.
419 * @param {Object} a Optional object name/value collection with element attributes.
420 * @param {String} h Optional HTML string to set as inner HTML of the element.
421 * @return {String} String with new HTML element like for example: <a href="#">test</a>.
423 * // Creates a html chunk and inserts it at the current selection/caret location
424 * tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line'));
426 createHTML : function(n, a, h) {
427 var o = '', t = this, k;
432 if (a.hasOwnProperty(k))
433 o += ' ' + k + '="' + t.encode(a[k]) + '"';
436 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
437 if (typeof(h) != "undefined")
438 return o + '>' + h + '</' + n + '>';
444 * Removes/deletes the specified element(s) from the DOM.
447 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
448 * @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.
449 * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input.
451 * // Removes all paragraphs in the active editor
452 * tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p'));
454 * // Removes a element by id in the document
455 * tinyMCE.DOM.remove('mydiv');
457 remove : function(node, keep_children) {
458 return this.run(node, function(node) {
459 var child, parent = node.parentNode;
465 while (child = node.firstChild) {
466 // IE 8 will crash if you don't remove completely empty text nodes
467 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
468 parent.insertBefore(child, node);
470 node.removeChild(child);
474 return parent.removeChild(node);
479 * Sets the CSS style value on a HTML element. The name can be a camelcase string
480 * or the CSS style name like background-color.
483 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
484 * @param {String} na Name of the style value to set.
485 * @param {String} v Value to set on the style.
487 * // Sets a style value on all paragraphs in the currently active editor
488 * tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red');
490 * // Sets a style value to an element by id in the current document
491 * tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red');
493 setStyle : function(n, na, v) {
496 return t.run(n, function(e) {
501 // Camelcase it, if needed
502 na = na.replace(/-(\D)/g, function(a, b){
503 return b.toUpperCase();
506 // Default px suffix on these
507 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
512 // IE specific opacity
514 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
516 if (!n.currentStyle || !n.currentStyle.hasLayout)
517 s.display = 'inline-block';
520 // Fix for older browsers
521 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
525 isIE ? s.styleFloat = v : s.cssFloat = v;
532 // Force update of the style data
533 if (t.settings.update_styles)
534 t.setAttrib(e, 'data-mce-style');
539 * Returns the current style or runtime/computed value of a element.
542 * @param {String/Element} n HTML element or element id string to get style from.
543 * @param {String} na Style name to return.
544 * @param {Boolean} c Computed style.
545 * @return {String} Current style or computed style value of a element.
547 getStyle : function(n, na, c) {
554 if (this.doc.defaultView && c) {
556 na = na.replace(/[A-Z]/g, function(a){
561 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
563 // Old safari might fail
568 // Camelcase it, if needed
569 na = na.replace(/-(\D)/g, function(a, b){
570 return b.toUpperCase();
574 na = isIE ? 'styleFloat' : 'cssFloat';
577 if (n.currentStyle && c)
578 return n.currentStyle[na];
580 return n.style ? n.style[na] : undefined;
584 * Sets multiple styles on the specified element(s).
587 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
588 * @param {Object} o Name/Value collection of style items to add to the element(s).
590 * // Sets styles on all paragraphs in the currently active editor
591 * tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'});
593 * // Sets styles to an element by id in the current document
594 * tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'});
596 setStyles : function(e, o) {
597 var t = this, s = t.settings, ol;
599 ol = s.update_styles;
602 each(o, function(v, n) {
607 s.update_styles = ol;
609 t.setAttrib(e, s.cssText);
613 * Removes all attributes from an element or elements.
615 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
617 removeAllAttribs: function(e) {
618 return this.run(e, function(e) {
619 var i, attrs = e.attributes;
620 for (i = attrs.length - 1; i >= 0; i--) {
621 e.removeAttributeNode(attrs.item(i));
627 * Sets the specified attributes value of a element or elements.
630 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
631 * @param {String} n Name of attribute to set.
632 * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead.
634 * // Sets an attribute to all paragraphs in the active editor
635 * tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass');
637 * // Sets an attribute to a specific element in the current page
638 * tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass');
640 setAttrib : function(e, n, v) {
648 if (t.settings.strict)
651 return this.run(e, function(e) {
656 if (!is(v, 'string')) {
657 each(v, function(v, n) {
664 // No mce_style for elements with these since they might get resized by the user
666 if (v && !t._isRes(v))
667 e.setAttribute('data-mce-style', v, 2);
669 e.removeAttribute('data-mce-style', 2);
676 e.className = v || ''; // Fix IE null bug
683 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
685 t.setAttrib(e, 'data-mce-' + n, v, 2);
691 e.setAttribute('data-mce-style', v);
695 if (is(v) && v !== null && v.length !== 0)
696 e.setAttribute(n, '' + v, 2);
698 e.removeAttribute(n, 2);
703 * Sets the specified attributes of a element or elements.
706 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on.
707 * @param {Object} o Name/Value collection of attribute items to add to the element(s).
709 * // Sets some attributes to all paragraphs in the active editor
710 * tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'});
712 * // Sets some attributes to a specific element in the current page
713 * tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'});
715 setAttribs : function(e, o) {
718 return this.run(e, function(e) {
719 each(o, function(v, n) {
720 t.setAttrib(e, n, v);
726 * Returns the specified attribute by name.
729 * @param {String/Element} e Element string id or DOM element to get attribute from.
730 * @param {String} n Name of attribute to get.
731 * @param {String} dv 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.
734 getAttrib : function(e, n, dv) {
739 if (!e || e.nodeType !== 1)
745 // Try the mce variant for these
746 if (/^(src|href|style|coords|shape)$/.test(n)) {
747 v = e.getAttribute("data-mce-" + n);
753 if (isIE && t.props[n]) {
755 v = v && v.nodeValue ? v.nodeValue : v;
759 v = e.getAttribute(n, 2);
761 // Check boolean attribs
762 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
763 if (e[t.props[n]] === true && v === '')
769 // Inner input elements will override attributes on form elements
770 if (e.nodeName === "FORM" && e.getAttributeNode(n))
771 return e.getAttributeNode(n).nodeValue;
774 v = v || e.style.cssText;
777 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
779 if (t.settings.keep_values && !t._isRes(v))
780 e.setAttribute('data-mce-style', v);
784 // Remove Apple and WebKit stuff
785 if (isWebKit && n === "class" && v)
786 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
793 // IE returns 1 as default value
800 // IE returns +0 as default value for size
801 if (v === '+0' || v === 20 || v === 0)
818 // IE returns -1 as default value
826 // IE returns default value
827 if (v === 32768 || v === 2147483647 || v === '32768')
846 // IE has odd anonymous function for event attributes
847 if (n.indexOf('on') === 0 && v)
848 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
852 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
856 * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields.
859 * @param {Element/String} n HTML element or element id to get x, y position from.
860 * @param {Element} ro Optional root element to stop calculations at.
861 * @return {object} Absolute position of the specified element object with x, y fields.
863 getPos : function(n, ro) {
864 var t = this, x = 0, y = 0, e, d = t.doc, r;
870 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
871 if (isIE && !t.stdMode) {
872 n = n.getBoundingClientRect();
873 e = t.boxModel ? d.documentElement : d.body;
874 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
875 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
877 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
881 while (r && r != ro && r.nodeType) {
882 x += r.offsetLeft || 0;
883 y += r.offsetTop || 0;
888 while (r && r != ro && r.nodeType) {
889 x -= r.scrollLeft || 0;
890 y -= r.scrollTop || 0;
895 return {x : x, y : y};
899 * Parses the specified style value into an object collection. This parser will also
900 * merge and remove any redundant items that browsers might have added. It will also convert non hex
901 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
904 * @param {String} st Style value to parse for example: border:1px solid red;.
905 * @return {Object} Object representation of that style like {border : '1px solid red'}
907 parseStyle : function(st) {
908 return this.styles.parse(st);
912 * Serializes the specified style object into a string.
914 * @method serializeStyle
915 * @param {Object} o Object to serialize as string for example: {border : '1px solid red'}
916 * @param {String} name Optional element name.
917 * @return {String} String representation of the style object for example: border: 1px solid red.
919 serializeStyle : function(o, name) {
920 return this.styles.serialize(o, name);
924 * Imports/loads the specified CSS file into the document bound to the class.
927 * @param {String} u URL to CSS file to load.
929 * // Loads a CSS file dynamically into the current document
930 * tinymce.DOM.loadCSS('somepath/some.css');
932 * // Loads a CSS file into the currently active editor instance
933 * tinyMCE.activeEditor.dom.loadCSS('somepath/some.css');
935 * // Loads a CSS file into an editor instance by id
936 * tinyMCE.get('someid').dom.loadCSS('somepath/some.css');
938 * // Loads multiple CSS files into the current document
939 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
941 loadCSS : function(u) {
942 var t = this, d = t.doc, head;
947 head = t.select('head')[0];
949 each(u.split(','), function(u) {
956 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
958 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
959 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
960 // It's ugly but it seems to work fine.
961 if (isIE && d.documentMode && d.recalc) {
962 link.onload = function() {
970 head.appendChild(link);
975 * Adds a class to the specified element or elements.
978 * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
979 * @param {String} c Class name to add to each element.
980 * @return {String/Array} String with new class value or array with new class values for all elements.
982 * // Adds a class to all paragraphs in the active editor
983 * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
985 * // Adds a class to a specific element in the current page
986 * tinyMCE.DOM.addClass('mydiv', 'myclass');
988 addClass : function(e, c) {
989 return this.run(e, function(e) {
995 if (this.hasClass(e, c))
998 o = this.removeClass(e, c);
1000 return e.className = (o != '' ? (o + ' ') : '') + c;
1005 * Removes a class from the specified element or elements.
1007 * @method removeClass
1008 * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
1009 * @param {String} c Class name to remove to each element.
1010 * @return {String/Array} String with new class value or array with new class values for all elements.
1012 * // Removes a class from all paragraphs in the active editor
1013 * tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
1015 * // Removes a class from a specific element in the current page
1016 * tinyMCE.DOM.removeClass('mydiv', 'myclass');
1018 removeClass : function(e, c) {
1021 return t.run(e, function(e) {
1024 if (t.hasClass(e, c)) {
1026 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
1028 v = e.className.replace(re, ' ');
1029 v = tinymce.trim(v != ' ' ? v : '');
1035 e.removeAttribute('class');
1036 e.removeAttribute('className');
1047 * Returns true if the specified element has the specified class.
1050 * @param {String/Element} n HTML element or element id string to check CSS class on.
1051 * @param {String} c CSS class to check for.
1052 * @return {Boolean} true/false if the specified element has the specified class.
1054 hasClass : function(n, c) {
1060 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
1064 * Shows the specified element(s) by ID by setting the "display" style.
1067 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show.
1069 show : function(e) {
1070 return this.setStyle(e, 'display', 'block');
1074 * Hides the specified element(s) by ID by setting the "display" style.
1077 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
1079 * // Hides a element by id in the document
1080 * tinymce.DOM.hide('myid');
1082 hide : function(e) {
1083 return this.setStyle(e, 'display', 'none');
1087 * Returns true/false if the element is hidden or not by checking the "display" style.
1090 * @param {String/Element} e Id or element to check display state on.
1091 * @return {Boolean} true/false if the element is hidden or not.
1093 isHidden : function(e) {
1096 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
1100 * Returns a unique id. This can be useful when generating elements on the fly.
1101 * This method will not check if the element allready exists.
1104 * @param {String} p Optional prefix to add infront of all ids defaults to "mce_".
1105 * @return {String} Unique id.
1107 uniqueId : function(p) {
1108 return (!p ? 'mce_' : p) + (this.counter++);
1112 * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means
1113 * URLs will get converted, hex color values fixed etc. Check processHTML for details.
1116 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside.
1117 * @param {String} h HTML content to set as inner HTML of the element.
1119 * // Sets the inner HTML of all paragraphs in the active editor
1120 * tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html');
1122 * // Sets the inner HTML of a element by id in the document
1123 * tinyMCE.DOM.setHTML('mydiv', 'some inner html');
1125 setHTML : function(element, html) {
1128 return self.run(element, function(element) {
1130 // Remove all child nodes, IE keeps empty text nodes in DOM
1131 while (element.firstChild)
1132 element.removeChild(element.firstChild);
1135 // IE will remove comments from the beginning
1136 // unless you padd the contents with something
1137 element.innerHTML = '<br />' + html;
1138 element.removeChild(element.firstChild);
1140 // 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
1141 // This seems to fix this problem
1143 // Create new div with HTML contents and a BR infront to keep comments
1144 element = self.create('div');
1145 element.innerHTML = '<br />' + html;
1147 // Add all children from div to target
1148 each (element.childNodes, function(node, i) {
1151 element.appendChild(node);
1155 element.innerHTML = html;
1162 * Returns the outer HTML of an element.
1164 * @method getOuterHTML
1165 * @param {String/Element} elm Element ID or element object to get outer HTML from.
1166 * @return {String} Outer HTML string.
1168 * tinymce.DOM.getOuterHTML(editorElement);
1169 * tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody());
1171 getOuterHTML : function(elm) {
1172 var doc, self = this;
1174 elm = self.get(elm);
1179 if (elm.nodeType === 1 && self.hasOuterHTML)
1180 return elm.outerHTML;
1182 doc = (elm.ownerDocument || self.doc).createElement("body");
1183 doc.appendChild(elm.cloneNode(true));
1185 return doc.innerHTML;
1189 * Sets the specified outer HTML on a element or elements.
1191 * @method setOuterHTML
1192 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on.
1193 * @param {Object} h HTML code to set as outer value for the element.
1194 * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class.
1196 * // Sets the outer HTML of all paragraphs in the active editor
1197 * tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '<div>some html</div>');
1199 * // Sets the outer HTML of a element by id in the document
1200 * tinyMCE.DOM.setOuterHTML('mydiv', '<div>some html</div>');
1202 setOuterHTML : function(e, h, d) {
1205 function setHTML(e, h, d) {
1208 tp = d.createElement("body");
1213 t.insertAfter(n.cloneNode(true), e);
1214 n = n.previousSibling;
1220 return this.run(e, function(e) {
1223 // Only set HTML on elements
1224 if (e.nodeType == 1) {
1225 d = d || e.ownerDocument || t.doc;
1229 // Try outerHTML for IE it sometimes produces an unknown runtime error
1230 if (isIE && e.nodeType == 1)
1235 // Fix for unknown runtime error
1245 * Entity decode a string, resolves any HTML entities like å.
1248 * @param {String} s String to decode entities on.
1249 * @return {String} Entity decoded string.
1251 decode : Entities.decode,
1254 * Entity encodes a string, encodes the most common entities <>"& into entities.
1257 * @param {String} text String to encode with entities.
1258 * @return {String} Entity encoded string.
1260 encode : Entities.encodeAllRaw,
1263 * Inserts a element after the reference element.
1265 * @method insertAfter
1266 * @param {Element} node Element to insert after the reference.
1267 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
1268 * @return {Element/Array} Element that got added or an array with elements.
1270 insertAfter : function(node, reference_node) {
1271 reference_node = this.get(reference_node);
1273 return this.run(node, function(node) {
1274 var parent, nextSibling;
1276 parent = reference_node.parentNode;
1277 nextSibling = reference_node.nextSibling;
1280 parent.insertBefore(node, nextSibling);
1282 parent.appendChild(node);
1289 * Returns true/false if the specified element is a block element or not.
1292 * @param {Node/String} node Element/Node to check.
1293 * @return {Boolean} True/False state if the node is a block element or not.
1295 isBlock : function(node) {
1296 var type = node.nodeType;
1298 // If it's a node then check the type and use the nodeName
1300 return !!(type === 1 && blockElementsMap[node.nodeName]);
1302 return !!blockElementsMap[node];
1306 * Replaces the specified element or elements with the specified element, the new element will
1307 * be cloned if multiple inputs elements are passed.
1310 * @param {Element} n New element to replace old ones with.
1311 * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace.
1312 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
1314 replace : function(n, o, k) {
1318 n = n.cloneNode(true);
1320 return t.run(o, function(o) {
1322 each(tinymce.grep(o.childNodes), function(c) {
1327 return o.parentNode.replaceChild(n, o);
1332 * Renames the specified element to a new name and keep it's attributes and children.
1335 * @param {Element} elm Element to rename.
1336 * @param {String} name Name of the new element.
1337 * @return New element or the old element if it needed renaming.
1339 rename : function(elm, name) {
1340 var t = this, newElm;
1342 if (elm.nodeName != name.toUpperCase()) {
1343 // Rename block element
1344 newElm = t.create(name);
1346 // Copy attribs to new block
1347 each(t.getAttribs(elm), function(attr_node) {
1348 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
1352 t.replace(newElm, elm, 1);
1355 return newElm || elm;
1359 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
1361 * @method findCommonAncestor
1362 * @param {Element} a Element to find common ancestor of.
1363 * @param {Element} b Element to find common ancestor of.
1364 * @return {Element} Common ancestor element of the two input elements.
1366 findCommonAncestor : function(a, b) {
1372 while (pe && ps != pe)
1381 if (!ps && a.ownerDocument)
1382 return a.ownerDocument.documentElement;
1388 * Parses the specified RGB color value and returns a hex version of that color.
1391 * @param {String} s RGB string value like rgb(1,2,3)
1392 * @return {String} Hex version of that RGB value like #FF00FF.
1394 toHex : function(s) {
1395 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
1398 s = parseInt(s).toString(16);
1400 return s.length > 1 ? s : '0' + s; // 0 -> 00
1404 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
1413 * Returns a array of all single CSS classes in the document. A single CSS class is a simple
1414 * rule like ".class" complex ones like "div td.class" will not be added to output.
1416 * @method getClasses
1417 * @return {Array} Array with class objects each object has a class field might be other fields in the future.
1419 getClasses : function() {
1420 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
1425 function addClasses(s) {
1427 each(s.imports, function(r) {
1431 each(s.cssRules || s.rules, function(r) {
1432 // Real type or fake it on IE
1433 switch (r.type || 1) {
1436 if (r.selectorText) {
1437 each(r.selectorText.split(','), function(v) {
1438 v = v.replace(/^\s*|\s*$|^\s\./g, "");
1440 // Is internal or it doesn't contain a class
1441 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
1444 // Remove everything but class name
1446 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
1449 if (f && !(v = f(v, ov)))
1453 cl.push({'class' : v});
1462 addClasses(r.styleSheet);
1469 each(t.doc.styleSheets, addClasses);
1481 * Executes the specified function on the element by id or dom element node or array of elements/id.
1484 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
1485 * @param {function} f Function to execute for each item.
1486 * @param {Object} s Optional scope to execute the function in.
1487 * @return {Object/Array} Single object or array with objects depending on multiple input or not.
1489 run : function(e, f, s) {
1492 if (t.doc && typeof(e) === 'string')
1499 if (!e.nodeType && (e.length || e.length === 0)) {
1502 each(e, function(e, i) {
1504 if (typeof(e) == 'string')
1505 e = t.doc.getElementById(e);
1507 o.push(f.call(s, e, i));
1514 return f.call(s, e);
1518 * Returns an NodeList with attributes for the element.
1520 * @method getAttribs
1521 * @param {HTMLElement/string} n Element node or string id to get attributes from.
1522 * @return {NodeList} NodeList with attributes.
1524 getAttribs : function(n) {
1535 // Object will throw exception in IE
1536 if (n.nodeName == 'OBJECT')
1537 return n.attributes;
1539 // IE doesn't keep the selected attribute if you clone option elements
1540 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
1541 o.push({specified : 1, nodeName : 'selected'});
1543 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
1544 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
1545 o.push({specified : 1, nodeName : a});
1551 return n.attributes;
1555 * Returns true/false if the specified node is to be considered empty or not.
1558 * tinymce.DOM.isEmpty(node, {img : true});
1560 * @param {Object} elements Optional name/value object with elements that are automatically treated as non empty elements.
1561 * @return {Boolean} true/false if the node is empty or not.
1563 isEmpty : function(node, elements) {
1564 var self = this, i, attributes, type, walker, name;
1566 node = node.firstChild;
1568 walker = new tinymce.dom.TreeWalker(node);
1569 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
1572 type = node.nodeType;
1575 // Ignore bogus elements
1576 if (node.getAttribute('data-mce-bogus'))
1579 // Keep empty elements like <img />
1580 if (elements && elements[node.nodeName.toLowerCase()])
1583 // Keep elements with data attributes or name attribute like <a name="1"></a>
1584 attributes = self.getAttribs(node);
1585 i = node.attributes.length;
1587 name = node.attributes[i].nodeName;
1588 if (name === "name" || name.indexOf('data-') === 0)
1593 // Keep non whitespace text nodes
1594 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
1596 } while (node = walker.next());
1603 * Destroys all internal references to the DOM to solve IE leak issues.
1607 destroy : function(s) {
1613 t.win = t.doc = t.root = t.events = null;
1615 // Manual destroy then remove unload handler
1617 tinymce.removeUnload(t.destroy);
1621 * Created a new DOM Range object. This will use the native DOM Range API if it's
1622 * available if it's not it will fallback to the custom TinyMCE implementation.
1625 * @return {DOMRange} DOM Range object.
1627 * var rng = tinymce.DOM.createRng();
1628 * alert(rng.startContainer + "," + rng.startOffset);
1630 createRng : function() {
1633 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
1637 * Returns the index of the specified node within it's parent.
1639 * @param {Node} node Node to look for.
1640 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
1641 * @return {Number} Index of the specified node.
1643 nodeIndex : function(node, normalized) {
1644 var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
1647 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
1648 nodeType = node.nodeType;
1650 // Normalize text nodes
1651 if (normalized && nodeType == 3) {
1652 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
1653 // (the nodeValue attribute will not exist, and will error here).
1654 nodeValueExists = false;
1655 try {nodeValueExists = node.nodeValue.length} catch (c) {}
1656 if (nodeType == lastNodeType || !nodeValueExists)
1660 lastNodeType = nodeType;
1668 * Splits an element into two new elements and places the specified split
1669 * element or element between the new ones. For example splitting the paragraph at the bold element in
1670 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
1673 * @param {Element} pe Parent element to split.
1674 * @param {Element} e Element to split at.
1675 * @param {Element} re Optional replacement element to replace the split element by.
1676 * @return {Element} Returns the split element or the replacement element if that is specified.
1678 split : function(pe, e, re) {
1679 var t = this, r = t.createRng(), bef, aft, pa;
1681 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
1682 // but we don't want that in our code since it serves no purpose for the end user
1683 // For example if this is chopped:
1684 // <p>text 1<span><b>CHOP</b></span>text 2</p>
1686 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
1687 // this function will then trim of empty edges and produce:
1688 // <p>text 1</p><b>CHOP</b><p>text 2</p>
1689 function trim(node) {
1690 var i, children = node.childNodes, type = node.nodeType;
1692 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
1695 for (i = children.length - 1; i >= 0; i--)
1699 // Keep non whitespace text nodes
1700 if (type == 3 && node.nodeValue.length > 0) {
1701 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
1702 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
1704 } else if (type == 1) {
1705 // If the only child is a bookmark then move it up
1706 children = node.childNodes;
1707 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
1708 node.parentNode.insertBefore(children[0], node);
1710 // Keep non empty elements or img, hr etc
1711 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
1723 r.setStart(pe.parentNode, t.nodeIndex(pe));
1724 r.setEnd(e.parentNode, t.nodeIndex(e));
1725 bef = r.extractContents();
1729 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
1730 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
1731 aft = r.extractContents();
1733 // Insert before chunk
1735 pa.insertBefore(trim(bef), pe);
1737 // Insert middle chunk
1739 pa.replaceChild(re, e);
1741 pa.insertBefore(e, pe);
1743 // Insert after chunk
1744 pa.insertBefore(trim(aft), pe);
1752 * Adds an event handler to the specified object.
1755 * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents.
1756 * @param {String} n Name of event handler to add for example: click.
1757 * @param {function} f Function to execute when the event occurs.
1758 * @param {Object} s Optional scope to execute the function in.
1759 * @return {function} Function callback handler the same as the one passed in.
1761 bind : function(target, name, func, scope) {
1765 t.events = new tinymce.dom.EventUtils();
1767 return t.events.add(target, name, func, scope || this);
1771 * Removes the specified event handler by name and function from a element or collection of elements.
1774 * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
1775 * @param {String} n Event handler name like for example: "click"
1776 * @param {function} f Function to remove.
1777 * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in.
1779 unbind : function(target, name, func) {
1783 t.events = new tinymce.dom.EventUtils();
1785 return t.events.remove(target, name, func);
1790 dumpRng : function(r) {
1791 return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset;
1796 _findSib : function(node, selector, name) {
1797 var t = this, f = selector;
1800 // If expression make a function of it using is
1801 if (is(f, 'string')) {
1802 f = function(node) {
1803 return t.is(node, selector);
1807 // Loop all siblings
1808 for (node = node[name]; node; node = node[name]) {
1817 _isRes : function(c) {
1818 // Is live resizble element
1819 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
1823 walk : function(n, f, s) {
1824 var d = this.doc, w;
1826 if (d.createTreeWalker) {
1827 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
1829 while ((n = w.nextNode()) != null)
1830 f.call(s || this, n);
1832 tinymce.walk(n, f, 'childNodes', s);
1837 toRGB : function(s) {
1838 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
1845 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
1854 * Instance of DOMUtils for the current document.
1858 * @type tinymce.dom.DOMUtils
1860 * // Example of how to add a class to some element by id
1861 * tinymce.DOM.addClass('someid', 'someclass');
1863 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});