/**
* DOMUtils.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/
(function(tinymce) {
// Shorten names
var each = tinymce.each,
is = tinymce.is,
isWebKit = tinymce.isWebKit,
isIE = tinymce.isIE,
Entities = tinymce.html.Entities,
simpleSelectorRe = /^([a-z0-9],?)+$/i,
blockElementsMap = tinymce.html.Schema.blockElementsMap,
whiteSpaceRegExp = /^[ \t\r\n]*$/;
/**
* Utility class for various DOM manipulation and retrival functions.
*
* @class tinymce.dom.DOMUtils
* @example
* // Add a class to an element by id in the page
* tinymce.DOM.addClass('someid', 'someclass');
*
* // Add a class to an element by id inside the editor
* tinyMCE.activeEditor.dom.addClass('someid', 'someclass');
*/
tinymce.create('tinymce.dom.DOMUtils', {
doc : null,
root : null,
files : null,
pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
props : {
"for" : "htmlFor",
"class" : "className",
className : "className",
checked : "checked",
disabled : "disabled",
maxlength : "maxLength",
readonly : "readOnly",
selected : "selected",
value : "value",
id : "id",
name : "name",
type : "type"
},
/**
* Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
*
* @constructor
* @method DOMUtils
* @param {Document} d Document reference to bind the utility class to.
* @param {settings} s Optional settings collection.
*/
DOMUtils : function(d, s) {
var t = this, globalStyle, name;
t.doc = d;
t.win = window;
t.files = {};
t.cssFlicker = false;
t.counter = 0;
t.stdMode = !tinymce.isIE || d.documentMode >= 8;
t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
t.hasOuterHTML = "outerHTML" in d.createElement("a");
t.settings = s = tinymce.extend({
keep_values : false,
hex_colors : 1
}, s);
t.schema = s.schema;
t.styles = new tinymce.html.Styles({
url_converter : s.url_converter,
url_converter_scope : s.url_converter_scope
}, s.schema);
// Fix IE6SP2 flicker and check it failed for pre SP2
if (tinymce.isIE6) {
try {
d.execCommand('BackgroundImageCache', false, true);
} catch (e) {
t.cssFlicker = true;
}
}
if (isIE && s.schema) {
// Add missing HTML 4/5 elements to IE
('abbr article aside audio canvas ' +
'details figcaption figure footer ' +
'header hgroup mark menu meter nav ' +
'output progress section summary ' +
'time video').replace(/\w+/g, function(name) {
d.createElement(name);
});
// Create all custom elements
for (name in s.schema.getCustomElements()) {
d.createElement(name);
}
}
tinymce.addUnload(t.destroy, t);
},
/**
* Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not
* go above the point of this root node.
*
* @method getRoot
* @return {Element} Root element for the utility class.
*/
getRoot : function() {
var t = this, s = t.settings;
return (s && t.get(s.root_element)) || t.doc.body;
},
/**
* Returns the viewport of the window.
*
* @method getViewPort
* @param {Window} w Optional window to get viewport of.
* @return {Object} Viewport object with fields x, y, w and h.
*/
getViewPort : function(w) {
var d, b;
w = !w ? this.win : w;
d = w.document;
b = this.boxModel ? d.documentElement : d.body;
// Returns viewport size excluding scrollbars
return {
x : w.pageXOffset || b.scrollLeft,
y : w.pageYOffset || b.scrollTop,
w : w.innerWidth || b.clientWidth,
h : w.innerHeight || b.clientHeight
};
},
/**
* Returns the rectangle for a specific element.
*
* @method getRect
* @param {Element/String} e Element object or element ID to get rectange from.
* @return {object} Rectange for specified element object with x, y, w, h fields.
*/
getRect : function(e) {
var p, t = this, sr;
e = t.get(e);
p = t.getPos(e);
sr = t.getSize(e);
return {
x : p.x,
y : p.y,
w : sr.w,
h : sr.h
};
},
/**
* Returns the size dimensions of the specified element.
*
* @method getSize
* @param {Element/String} e Element object or element ID to get rectange from.
* @return {object} Rectange for specified element object with w, h fields.
*/
getSize : function(e) {
var t = this, w, h;
e = t.get(e);
w = t.getStyle(e, 'width');
h = t.getStyle(e, 'height');
// Non pixel value, then force offset/clientWidth
if (w.indexOf('px') === -1)
w = 0;
// Non pixel value, then force offset/clientWidth
if (h.indexOf('px') === -1)
h = 0;
return {
w : parseInt(w) || e.offsetWidth || e.clientWidth,
h : parseInt(h) || e.offsetHeight || e.clientHeight
};
},
/**
* Returns a node by the specified selector function. This function will
* loop through all parent nodes and call the specified function for each node.
* If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
* and the node it found will be returned.
*
* @method getParent
* @param {Node/String} n DOM node to search parents on or ID string.
* @param {function} f Selection function to execute on each node or CSS pattern.
* @param {Node} r Optional root element, never go below this point.
* @return {Node} DOM Node or null if it wasn't found.
*/
getParent : function(n, f, r) {
return this.getParents(n, f, r, false);
},
/**
* Returns a node list of all parents matching the specified selector function or pattern.
* If the function then returns true indicating that it has found what it was looking for and that node will be collected.
*
* @method getParents
* @param {Node/String} n DOM node to search parents on or ID string.
* @param {function} f Selection function to execute on each node or CSS pattern.
* @param {Node} r Optional root element, never go below this point.
* @return {Array} Array of nodes or null if it wasn't found.
*/
getParents : function(n, f, r, c) {
var t = this, na, se = t.settings, o = [];
n = t.get(n);
c = c === undefined;
if (se.strict_root)
r = r || t.getRoot();
// Wrap node name as func
if (is(f, 'string')) {
na = f;
if (f === '*') {
f = function(n) {return n.nodeType == 1;};
} else {
f = function(n) {
return t.is(n, na);
};
}
}
while (n) {
if (n == r || !n.nodeType || n.nodeType === 9)
break;
if (!f || f(n)) {
if (c)
o.push(n);
else
return n;
}
n = n.parentNode;
}
return c ? o : null;
},
/**
* Returns the specified element by ID or the input element if it isn't a string.
*
* @method get
* @param {String/Element} n Element id to look for or element to just pass though.
* @return {Element} Element matching the specified id or null if it wasn't found.
*/
get : function(e) {
var n;
if (e && this.doc && typeof(e) == 'string') {
n = e;
e = this.doc.getElementById(e);
// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
if (e && e.id !== n)
return this.doc.getElementsByName(n)[1];
}
return e;
},
/**
* Returns the next node that matches selector or function
*
* @method getNext
* @param {Node} node Node to find siblings from.
* @param {String/function} selector Selector CSS expression or function.
* @return {Node} Next node item matching the selector or null if it wasn't found.
*/
getNext : function(node, selector) {
return this._findSib(node, selector, 'nextSibling');
},
/**
* Returns the previous node that matches selector or function
*
* @method getPrev
* @param {Node} node Node to find siblings from.
* @param {String/function} selector Selector CSS expression or function.
* @return {Node} Previous node item matching the selector or null if it wasn't found.
*/
getPrev : function(node, selector) {
return this._findSib(node, selector, 'previousSibling');
},
// #ifndef jquery
/**
* Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
* This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough
* on more complex patterns.
*
* @method select
* @param {String} p CSS level 1 pattern to select/find elements by.
* @param {Object} s Optional root element/scope element to search in.
* @return {Array} Array with all matched elements.
* @example
* // Adds a class to all paragraphs in the currently active editor
* tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
*
* // Adds a class to all spans that has the test class in the currently active editor
* tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass')
*/
select : function(pa, s) {
var t = this;
return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
},
/**
* Returns true/false if the specified element matches the specified css pattern.
*
* @method is
* @param {Node/NodeList} n DOM node to match or an array of nodes to match.
* @param {String} selector CSS pattern to match the element agains.
*/
is : function(n, selector) {
var i;
// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
if (n.length === undefined) {
// Simple all selector
if (selector === '*')
return n.nodeType == 1;
// Simple selector just elements
if (simpleSelectorRe.test(selector)) {
selector = selector.toLowerCase().split(/,/);
n = n.nodeName.toLowerCase();
for (i = selector.length - 1; i >= 0; i--) {
if (selector[i] == n)
return true;
}
return false;
}
}
return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
},
// #endif
/**
* Adds the specified element to another element or elements.
*
* @method add
* @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to.
* @param {String/Element} n Name of new element to add or existing element to add.
* @param {Object} a Optional object collection with arguments to add to the new element(s).
* @param {String} h Optional inner HTML contents to add for each element.
* @param {Boolean} c Optional internal state to indicate if it should create or add.
* @return {Element/Array} Element that got created or array with elements if multiple elements where passed.
* @example
* // Adds a new paragraph to the end of the active editor
* tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content');
*/
add : function(p, n, a, h, c) {
var t = this;
return this.run(p, function(p) {
var e, k;
e = is(n, 'string') ? t.doc.createElement(n) : n;
t.setAttribs(e, a);
if (h) {
if (h.nodeType)
e.appendChild(h);
else
t.setHTML(e, h);
}
return !c ? p.appendChild(e) : e;
});
},
/**
* Creates a new element.
*
* @method create
* @param {String} n Name of new element.
* @param {Object} a Optional object name/value collection with element attributes.
* @param {String} h Optional HTML string to set as inner HTML of the element.
* @return {Element} HTML DOM node element that got created.
* @example
* // Adds an element where the caret/selection is in the active editor
* var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content');
* tinyMCE.activeEditor.selection.setNode(el);
*/
create : function(n, a, h) {
return this.add(this.doc.createElement(n), n, a, h, 1);
},
/**
* Create HTML string for element. The element will be closed unless an empty inner HTML string is passed.
*
* @method createHTML
* @param {String} n Name of new element.
* @param {Object} a Optional object name/value collection with element attributes.
* @param {String} h Optional HTML string to set as inner HTML of the element.
* @return {String} String with new HTML element like for example: test.
* @example
* // Creates a html chunk and inserts it at the current selection/caret location
* tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line'));
*/
createHTML : function(n, a, h) {
var o = '', t = this, k;
o += '<' + n;
for (k in a) {
if (a.hasOwnProperty(k))
o += ' ' + k + '="' + t.encode(a[k]) + '"';
}
// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
if (typeof(h) != "undefined")
return o + '>' + h + '' + n + '>';
return o + ' />';
},
/**
* Removes/deletes the specified element(s) from the DOM.
*
* @method remove
* @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
* @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.
* @return {Element/Array} HTML DOM element that got removed or array of elements depending on input.
* @example
* // Removes all paragraphs in the active editor
* tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p'));
*
* // Removes a element by id in the document
* tinyMCE.DOM.remove('mydiv');
*/
remove : function(node, keep_children) {
return this.run(node, function(node) {
var child, parent = node.parentNode;
if (!parent)
return null;
if (keep_children) {
while (child = node.firstChild) {
// IE 8 will crash if you don't remove completely empty text nodes
if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
parent.insertBefore(child, node);
else
node.removeChild(child);
}
}
return parent.removeChild(node);
});
},
/**
* Sets the CSS style value on a HTML element. The name can be a camelcase string
* or the CSS style name like background-color.
*
* @method setStyle
* @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
* @param {String} na Name of the style value to set.
* @param {String} v Value to set on the style.
* @example
* // Sets a style value on all paragraphs in the currently active editor
* tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red');
*
* // Sets a style value to an element by id in the current document
* tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red');
*/
setStyle : function(n, na, v) {
var t = this;
return t.run(n, function(e) {
var s, i;
s = e.style;
// Camelcase it, if needed
na = na.replace(/-(\D)/g, function(a, b){
return b.toUpperCase();
});
// Default px suffix on these
if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
v += 'px';
switch (na) {
case 'opacity':
// IE specific opacity
if (isIE) {
s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
if (!n.currentStyle || !n.currentStyle.hasLayout)
s.display = 'inline-block';
}
// Fix for older browsers
s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
break;
case 'float':
isIE ? s.styleFloat = v : s.cssFloat = v;
break;
default:
s[na] = v || '';
}
// Force update of the style data
if (t.settings.update_styles)
t.setAttrib(e, 'data-mce-style');
});
},
/**
* Returns the current style or runtime/computed value of a element.
*
* @method getStyle
* @param {String/Element} n HTML element or element id string to get style from.
* @param {String} na Style name to return.
* @param {Boolean} c Computed style.
* @return {String} Current style or computed style value of a element.
*/
getStyle : function(n, na, c) {
n = this.get(n);
if (!n)
return;
// Gecko
if (this.doc.defaultView && c) {
// Remove camelcase
na = na.replace(/[A-Z]/g, function(a){
return '-' + a;
});
try {
return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
} catch (ex) {
// Old safari might fail
return null;
}
}
// Camelcase it, if needed
na = na.replace(/-(\D)/g, function(a, b){
return b.toUpperCase();
});
if (na == 'float')
na = isIE ? 'styleFloat' : 'cssFloat';
// IE & Opera
if (n.currentStyle && c)
return n.currentStyle[na];
return n.style ? n.style[na] : undefined;
},
/**
* Sets multiple styles on the specified element(s).
*
* @method setStyles
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
* @param {Object} o Name/Value collection of style items to add to the element(s).
* @example
* // Sets styles on all paragraphs in the currently active editor
* tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'});
*
* // Sets styles to an element by id in the current document
* tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'});
*/
setStyles : function(e, o) {
var t = this, s = t.settings, ol;
ol = s.update_styles;
s.update_styles = 0;
each(o, function(v, n) {
t.setStyle(e, n, v);
});
// Update style info
s.update_styles = ol;
if (s.update_styles)
t.setAttrib(e, s.cssText);
},
/**
* Removes all attributes from an element or elements.
*
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
*/
removeAllAttribs: function(e) {
return this.run(e, function(e) {
var i, attrs = e.attributes;
for (i = attrs.length - 1; i >= 0; i--) {
e.removeAttributeNode(attrs.item(i));
}
});
},
/**
* Sets the specified attributes value of a element or elements.
*
* @method setAttrib
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
* @param {String} n Name of attribute to set.
* @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead.
* @example
* // Sets an attribute to all paragraphs in the active editor
* tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass');
*
* // Sets an attribute to a specific element in the current page
* tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass');
*/
setAttrib : function(e, n, v) {
var t = this;
// Whats the point
if (!e || !n)
return;
// Strict XML mode
if (t.settings.strict)
n = n.toLowerCase();
return this.run(e, function(e) {
var s = t.settings;
if (v !== null) {
switch (n) {
case "style":
if (!is(v, 'string')) {
each(v, function(v, n) {
t.setStyle(e, n, v);
});
return;
}
// No mce_style for elements with these since they might get resized by the user
if (s.keep_values) {
if (v && !t._isRes(v))
e.setAttribute('data-mce-style', v, 2);
else
e.removeAttribute('data-mce-style', 2);
}
e.style.cssText = v;
break;
case "class":
e.className = v || ''; // Fix IE null bug
break;
case "src":
case "href":
if (s.keep_values) {
if (s.url_converter)
v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
t.setAttrib(e, 'data-mce-' + n, v, 2);
}
break;
case "shape":
e.setAttribute('data-mce-style', v);
break;
}
}
if (is(v) && v !== null && v.length !== 0)
e.setAttribute(n, '' + v, 2);
else
e.removeAttribute(n, 2);
});
},
/**
* Sets the specified attributes of a element or elements.
*
* @method setAttribs
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on.
* @param {Object} o Name/Value collection of attribute items to add to the element(s).
* @example
* // Sets some attributes to all paragraphs in the active editor
* tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'});
*
* // Sets some attributes to a specific element in the current page
* tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'});
*/
setAttribs : function(e, o) {
var t = this;
return this.run(e, function(e) {
each(o, function(v, n) {
t.setAttrib(e, n, v);
});
});
},
/**
* Returns the specified attribute by name.
*
* @method getAttrib
* @param {String/Element} e Element string id or DOM element to get attribute from.
* @param {String} n Name of attribute to get.
* @param {String} dv Optional default value to return if the attribute didn't exist.
* @return {String} Attribute value string, default value or null if the attribute wasn't found.
*/
getAttrib : function(e, n, dv) {
var v, t = this, undef;
e = t.get(e);
if (!e || e.nodeType !== 1)
return dv === undef ? false : dv;
if (!is(dv))
dv = '';
// Try the mce variant for these
if (/^(src|href|style|coords|shape)$/.test(n)) {
v = e.getAttribute("data-mce-" + n);
if (v)
return v;
}
if (isIE && t.props[n]) {
v = e[t.props[n]];
v = v && v.nodeValue ? v.nodeValue : v;
}
if (!v)
v = e.getAttribute(n, 2);
// Check boolean attribs
if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
if (e[t.props[n]] === true && v === '')
return n;
return v ? n : '';
}
// Inner input elements will override attributes on form elements
if (e.nodeName === "FORM" && e.getAttributeNode(n))
return e.getAttributeNode(n).nodeValue;
if (n === 'style') {
v = v || e.style.cssText;
if (v) {
v = t.serializeStyle(t.parseStyle(v), e.nodeName);
if (t.settings.keep_values && !t._isRes(v))
e.setAttribute('data-mce-style', v);
}
}
// Remove Apple and WebKit stuff
if (isWebKit && n === "class" && v)
v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
// Handle IE issues
if (isIE) {
switch (n) {
case 'rowspan':
case 'colspan':
// IE returns 1 as default value
if (v === 1)
v = '';
break;
case 'size':
// IE returns +0 as default value for size
if (v === '+0' || v === 20 || v === 0)
v = '';
break;
case 'width':
case 'height':
case 'vspace':
case 'checked':
case 'disabled':
case 'readonly':
if (v === 0)
v = '';
break;
case 'hspace':
// IE returns -1 as default value
if (v === -1)
v = '';
break;
case 'maxlength':
case 'tabindex':
// IE returns default value
if (v === 32768 || v === 2147483647 || v === '32768')
v = '';
break;
case 'multiple':
case 'compact':
case 'noshade':
case 'nowrap':
if (v === 65535)
return n;
return dv;
case 'shape':
v = v.toLowerCase();
break;
default:
// IE has odd anonymous function for event attributes
if (n.indexOf('on') === 0 && v)
v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
}
}
return (v !== undef && v !== null && v !== '') ? '' + v : dv;
},
/**
* Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields.
*
* @method getPos
* @param {Element/String} n HTML element or element id to get x, y position from.
* @param {Element} ro Optional root element to stop calculations at.
* @return {object} Absolute position of the specified element object with x, y fields.
*/
getPos : function(n, ro) {
var t = this, x = 0, y = 0, e, d = t.doc, r;
n = t.get(n);
ro = ro || d.body;
if (n) {
// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
if (n.getBoundingClientRect) {
n = n.getBoundingClientRect();
e = t.boxModel ? d.documentElement : d.body;
// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
return {x : x, y : y};
}
r = n;
while (r && r != ro && r.nodeType) {
x += r.offsetLeft || 0;
y += r.offsetTop || 0;
r = r.offsetParent;
}
r = n.parentNode;
while (r && r != ro && r.nodeType) {
x -= r.scrollLeft || 0;
y -= r.scrollTop || 0;
r = r.parentNode;
}
}
return {x : x, y : y};
},
/**
* Parses the specified style value into an object collection. This parser will also
* merge and remove any redundant items that browsers might have added. It will also convert non hex
* colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
*
* @method parseStyle
* @param {String} st Style value to parse for example: border:1px solid red;.
* @return {Object} Object representation of that style like {border : '1px solid red'}
*/
parseStyle : function(st) {
return this.styles.parse(st);
},
/**
* Serializes the specified style object into a string.
*
* @method serializeStyle
* @param {Object} o Object to serialize as string for example: {border : '1px solid red'}
* @param {String} name Optional element name.
* @return {String} String representation of the style object for example: border: 1px solid red.
*/
serializeStyle : function(o, name) {
return this.styles.serialize(o, name);
},
/**
* Imports/loads the specified CSS file into the document bound to the class.
*
* @method loadCSS
* @param {String} u URL to CSS file to load.
* @example
* // Loads a CSS file dynamically into the current document
* tinymce.DOM.loadCSS('somepath/some.css');
*
* // Loads a CSS file into the currently active editor instance
* tinyMCE.activeEditor.dom.loadCSS('somepath/some.css');
*
* // Loads a CSS file into an editor instance by id
* tinyMCE.get('someid').dom.loadCSS('somepath/some.css');
*
* // Loads multiple CSS files into the current document
* tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
*/
loadCSS : function(u) {
var t = this, d = t.doc, head;
if (!u)
u = '';
head = t.select('head')[0];
each(u.split(','), function(u) {
var link;
if (t.files[u])
return;
t.files[u] = true;
link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
// It's ugly but it seems to work fine.
if (isIE && d.documentMode && d.recalc) {
link.onload = function() {
if (d.recalc)
d.recalc();
link.onload = null;
};
}
head.appendChild(link);
});
},
/**
* Adds a class to the specified element or elements.
*
* @method addClass
* @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
* @param {String} c Class name to add to each element.
* @return {String/Array} String with new class value or array with new class values for all elements.
* @example
* // Adds a class to all paragraphs in the active editor
* tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
*
* // Adds a class to a specific element in the current page
* tinyMCE.DOM.addClass('mydiv', 'myclass');
*/
addClass : function(e, c) {
return this.run(e, function(e) {
var o;
if (!c)
return 0;
if (this.hasClass(e, c))
return e.className;
o = this.removeClass(e, c);
return e.className = (o != '' ? (o + ' ') : '') + c;
});
},
/**
* Removes a class from the specified element or elements.
*
* @method removeClass
* @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
* @param {String} c Class name to remove to each element.
* @return {String/Array} String with new class value or array with new class values for all elements.
* @example
* // Removes a class from all paragraphs in the active editor
* tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
*
* // Removes a class from a specific element in the current page
* tinyMCE.DOM.removeClass('mydiv', 'myclass');
*/
removeClass : function(e, c) {
var t = this, re;
return t.run(e, function(e) {
var v;
if (t.hasClass(e, c)) {
if (!re)
re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
v = e.className.replace(re, ' ');
v = tinymce.trim(v != ' ' ? v : '');
e.className = v;
// Empty class attr
if (!v) {
e.removeAttribute('class');
e.removeAttribute('className');
}
return v;
}
return e.className;
});
},
/**
* Returns true if the specified element has the specified class.
*
* @method hasClass
* @param {String/Element} n HTML element or element id string to check CSS class on.
* @param {String} c CSS class to check for.
* @return {Boolean} true/false if the specified element has the specified class.
*/
hasClass : function(n, c) {
n = this.get(n);
if (!n || !c)
return false;
return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
},
/**
* Shows the specified element(s) by ID by setting the "display" style.
*
* @method show
* @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show.
*/
show : function(e) {
return this.setStyle(e, 'display', 'block');
},
/**
* Hides the specified element(s) by ID by setting the "display" style.
*
* @method hide
* @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
* @example
* // Hides a element by id in the document
* tinymce.DOM.hide('myid');
*/
hide : function(e) {
return this.setStyle(e, 'display', 'none');
},
/**
* Returns true/false if the element is hidden or not by checking the "display" style.
*
* @method isHidden
* @param {String/Element} e Id or element to check display state on.
* @return {Boolean} true/false if the element is hidden or not.
*/
isHidden : function(e) {
e = this.get(e);
return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
},
/**
* Returns a unique id. This can be useful when generating elements on the fly.
* This method will not check if the element allready exists.
*
* @method uniqueId
* @param {String} p Optional prefix to add infront of all ids defaults to "mce_".
* @return {String} Unique id.
*/
uniqueId : function(p) {
return (!p ? 'mce_' : p) + (this.counter++);
},
/**
* Sets the specified HTML content inside the element or elements. The HTML will first be processed this means
* URLs will get converted, hex color values fixed etc. Check processHTML for details.
*
* @method setHTML
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside.
* @param {String} h HTML content to set as inner HTML of the element.
* @example
* // Sets the inner HTML of all paragraphs in the active editor
* tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html');
*
* // Sets the inner HTML of a element by id in the document
* tinyMCE.DOM.setHTML('mydiv', 'some inner html');
*/
setHTML : function(element, html) {
var self = this;
return self.run(element, function(element) {
if (isIE) {
// Remove all child nodes, IE keeps empty text nodes in DOM
while (element.firstChild)
element.removeChild(element.firstChild);
try {
// IE will remove comments from the beginning
// unless you padd the contents with something
element.innerHTML = '
' + html;
element.removeChild(element.firstChild);
} catch (ex) {
// 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
// This seems to fix this problem
// Create new div with HTML contents and a BR infront to keep comments
element = self.create('div');
element.innerHTML = '
' + html;
// Add all children from div to target
each (element.childNodes, function(node, i) {
// Skip br element
if (i)
element.appendChild(node);
});
}
} else
element.innerHTML = html;
return html;
});
},
/**
* Returns the outer HTML of an element.
*
* @method getOuterHTML
* @param {String/Element} elm Element ID or element object to get outer HTML from.
* @return {String} Outer HTML string.
* @example
* tinymce.DOM.getOuterHTML(editorElement);
* tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody());
*/
getOuterHTML : function(elm) {
var doc, self = this;
elm = self.get(elm);
if (!elm)
return null;
if (elm.nodeType === 1 && self.hasOuterHTML)
return elm.outerHTML;
doc = (elm.ownerDocument || self.doc).createElement("body");
doc.appendChild(elm.cloneNode(true));
return doc.innerHTML;
},
/**
* Sets the specified outer HTML on a element or elements.
*
* @method setOuterHTML
* @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on.
* @param {Object} h HTML code to set as outer value for the element.
* @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class.
* @example
* // Sets the outer HTML of all paragraphs in the active editor
* tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '
abcabc123
would produceabc
abc123
. * * @method split * @param {Element} pe Parent element to split. * @param {Element} e Element to split at. * @param {Element} re Optional replacement element to replace the split element by. * @return {Element} Returns the split element or the replacement element if that is specified. */ split : function(pe, e, re) { var t = this, r = t.createRng(), bef, aft, pa; // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense // but we don't want that in our code since it serves no purpose for the end user // For example if this is chopped: //text 1CHOPtext 2
// would produce: //text 1
CHOPtext 2
// this function will then trim of empty edges and produce: //text 1
CHOPtext 2
function trim(node) { var i, children = node.childNodes, type = node.nodeType; if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') return; for (i = children.length - 1; i >= 0; i--) trim(children[i]); if (type != 9) { // Keep non whitespace text nodes if (type == 3 && node.nodeValue.length > 0) { // If parent element isn't a block or there isn't any useful contents for example "" if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) return; } else if (type == 1) { // If the only child is a bookmark then move it up children = node.childNodes; if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') node.parentNode.insertBefore(children[0], node); // Keep non empty elements or img, hr etc if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) return; } t.remove(node); } return node; }; if (pe && e) { // Get before chunk r.setStart(pe.parentNode, t.nodeIndex(pe)); r.setEnd(e.parentNode, t.nodeIndex(e)); bef = r.extractContents(); // Get after chunk r = t.createRng(); r.setStart(e.parentNode, t.nodeIndex(e) + 1); r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); aft = r.extractContents(); // Insert before chunk pa = pe.parentNode; pa.insertBefore(trim(bef), pe); // Insert middle chunk if (re) pa.replaceChild(re, e); else pa.insertBefore(e, pe); // Insert after chunk pa.insertBefore(trim(aft), pe); t.remove(pe); return re || e; } }, /** * Adds an event handler to the specified object. * * @method bind * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents. * @param {String} n Name of event handler to add for example: click. * @param {function} f Function to execute when the event occurs. * @param {Object} s Optional scope to execute the function in. * @return {function} Function callback handler the same as the one passed in. */ bind : function(target, name, func, scope) { var t = this; if (!t.events) t.events = new tinymce.dom.EventUtils(); return t.events.add(target, name, func, scope || this); }, /** * Removes the specified event handler by name and function from a element or collection of elements. * * @method unbind * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from. * @param {String} n Event handler name like for example: "click" * @param {function} f Function to remove. * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. */ unbind : function(target, name, func) { var t = this; if (!t.events) t.events = new tinymce.dom.EventUtils(); return t.events.remove(target, name, func); }, // #ifdef debug dumpRng : function(r) { return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset; }, // #endif _findSib : function(node, selector, name) { var t = this, f = selector; if (node) { // If expression make a function of it using is if (is(f, 'string')) { f = function(node) { return t.is(node, selector); }; } // Loop all siblings for (node = node[name]; node; node = node[name]) { if (f(node)) return node; } } return null; }, _isRes : function(c) { // Is live resizble element return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); } /* walk : function(n, f, s) { var d = this.doc, w; if (d.createTreeWalker) { w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); while ((n = w.nextNode()) != null) f.call(s || this, n); } else tinymce.walk(n, f, 'childNodes', s); } */ /* toRGB : function(s) { var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); if (c) { // #FFF -> #FFFFFF if (!is(c[3])) c[3] = c[2] = c[1]; return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; } return s; } */ }); /** * Instance of DOMUtils for the current document. * * @property DOM * @member tinymce * @type tinymce.dom.DOMUtils * @example * // Example of how to add a class to some element by id * tinymce.DOM.addClass('someid', 'someclass'); */ tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); })(tinymce);