/**
* $Id$
*
* @author Moxiecode
* @copyright Copyright � 2004-2007, Moxiecode Systems AB, All rights reserved.
*
* The contents of this file will be wrapped in a class later on.
*/
/**#@+
* @member TinyMCE_Engine
* @method
*/
tinyMCE.add(TinyMCE_Engine, {
/**
* Creates a HTML tag by name and attributes array. This will XML encode all attribute values.
*
* @param {string} tn Tag name to create.
* @param {Array} a Optional name/Value array of attributes.
* @param {string} h Optional inner HTML of new tag, raw HTML code.
*/
createTagHTML : function(tn, a, h) {
var o = '', f = tinyMCE.xmlEncode, n;
o = '<' + tn;
if (a) {
for (n in a) {
if (typeof(a[n]) != 'function' && a[n] != null)
o += ' ' + f(n) + '="' + f('' + a[n]) + '"';
}
}
o += !h ? ' />' : '>' + h + '</' + tn + '>';
return o;
},
/**
* Creates a tag by name and attributes array. This will create a DOM node out of the specified
* data.
*
* @param {string} d Document to create DOM node in.
* @param {string} tn Tag name to create.
* @param {Array} a Optional name/Value array of attributes.
* @param {string} h Optional inner HTML of new tag, raw HTML code.
*/
createTag : function(d, tn, a, h) {
var o = d.createElement(tn), n;
if (a) {
for (n in a) {
if (typeof(a[n]) != 'function' && a[n] != null)
tinyMCE.setAttrib(o, n, a[n]);
}
}
if (h)
o.innerHTML = h;
return o;
},
/**
* Returns a element by a specific attribute and it's value.
*
* @param {HTMLElement} n Element to search in.
* @param {string} e Element name to search for.
* @param {string} a Attribute name to search for.
* @param {string} v Attribute value to search for.
* @return HTML element that matched the criterias or null on failure.
* @type HTMLElement
*/
getElementByAttributeValue : function(n, e, a, v) {
return (n = this.getElementsByAttributeValue(n, e, a, v)).length == 0 ? null : n[0];
},
/**
* Returns a element array by a specific attribute and it's value.
*
* @param {HTMLElement} n Element to search in.
* @param {string} e Element name to search for.
* @param {string} a Attribute name to search for.
* @param {string} v Attribute value to search for.
* @return HTML element array that matched the criterias or null on failure.
* @type Array
*/
getElementsByAttributeValue : function(n, e, a, v) {
var i, nl = n.getElementsByTagName(e), o = [];
for (i=0; i<nl.length; i++) {
if (tinyMCE.getAttrib(nl[i], a).indexOf(v) != -1)
o[o.length] = nl[i];
}
return o;
},
/**
* Returns true/false if the specified node is a block element or not.
*
* @param {HTMLNode} n Node to verify.
* @return true/false if the specified node is a block element or not.
* @type boolean
*/
isBlockElement : function(n) {
return n != null && n.nodeType == 1 && this.blockRegExp.test(n.nodeName);
},
/**
* Returns the first block element parent of the specified node.
*
* @param {HTMLNode} n Node get parent block element for.
* @param {HTMLNode} r Optional root element, never go below this point.
* @return First block element parent of the specified node or null if it wasn't found.
* @type HTMLElement
*/
getParentBlockElement : function(n, r) {
return this.getParentNode(n, function(n) {
return tinyMCE.isBlockElement(n);
}, r);
return null;
},
/**
* Inserts a node after the specific node.
*
* @param {HTMLNode} n New node to insert.
* @param {HTMLNode} r Reference node to insert after.
*/
insertAfter : function(n, r){
if (r.nextSibling)
r.parentNode.insertBefore(n, r.nextSibling);
else
r.parentNode.appendChild(n);
},
/**
* Sets the innerHTML property of a element, this function also
* fixes a MSIE bug where the first comment is removed.
*
* @param {HTMLElement} e Element to insert HTML in.
* @param {string} h HTML code to insert into innerHTML.
*/
setInnerHTML : function(e, h) {
var i, nl, n;
// Convert all strong/em to b/i in Gecko
if (tinyMCE.isGecko) {
h = h.replace(/<embed([^>]*)>/gi, '<tmpembed$1>');
h = h.replace(/<em([^>]*)>/gi, '<i$1>');
h = h.replace(/<tmpembed([^>]*)>/gi, '<embed$1>');
h = h.replace(/<strong([^>]*)>/gi, '<b$1>');
h = h.replace(/<\/strong>/gi, '</b>');
h = h.replace(/<\/em>/gi, '</i>');
}
if (tinyMCE.isRealIE) {
// Since MSIE handles invalid HTML better that valid XHTML we
// need to make some things invalid. <hr /> gets converted to <hr>.
h = h.replace(/\s\/>/g, '>');
// Since MSIE auto generated emtpy P tags some times we must tell it to keep the real ones
h = h.replace(/<p([^>]*)>\u00A0?<\/p>/gi, '<p$1 mce_keep="true"> </p>'); // Keep empty paragraphs
h = h.replace(/<p([^>]*)>\s* \s*<\/p>/gi, '<p$1 mce_keep="true"> </p>'); // Keep empty paragraphs
h = h.replace(/<p([^>]*)>\s+<\/p>/gi, '<p$1 mce_keep="true"> </p>'); // Keep empty paragraphs
// Remove first comment
e.innerHTML = tinyMCE.uniqueTag + h;
e.firstChild.removeNode(true);
// Remove weird auto generated empty paragraphs unless it's supposed to be there
nl = e.getElementsByTagName("p");
for (i=nl.length-1; i>=0; i--) {
n = nl[i];
if (n.nodeName == 'P' && !n.hasChildNodes() && !n.mce_keep)
n.parentNode.removeChild(n);
}
} else {
h = this.fixGeckoBaseHREFBug(1, e, h);
e.innerHTML = h;
this.fixGeckoBaseHREFBug(2, e, h);
}
},
/**
* Returns the outer HTML of a element, this uses the outerHTML
* property in MSIE and Opera and a workaround for Gecko.
*
* @param {HTMLElement} e HTML element to get outerHTML from.
* @return HTML content string.
* @type string
*/
getOuterHTML : function(e) {
var d;
if (tinyMCE.isIE)
return e.outerHTML;
d = e.ownerDocument.createElement("body");
d.appendChild(e.cloneNode(true));
return d.innerHTML;
},
/**
* Sets the outer HTML of a element, this uses the outerHTML
* property in MSIE and Opera and a workaround for Gecko.
*
* @param {HTMLElement} e HTML element to set outerHTML on.
* @param {string} h HTML string to set in property.
* @param {DOMDocument} d Optional document instance (Required in old IE versions).
*/
setOuterHTML : function(e, h, d) {
var d = typeof(d) == "undefined" ? e.ownerDocument : d, i, nl, t;
if (tinyMCE.isIE && e.nodeType == 1)
e.outerHTML = h;
else {
t = d.createElement("body");
t.innerHTML = h;
for (i=0, nl=t.childNodes; i<nl.length; i++)
e.parentNode.insertBefore(nl[i].cloneNode(true), e);
e.parentNode.removeChild(e);
}
},
/**
* Returns a element by id, this will also search the form names to match the id.
*
* @param {string} id Id of element.
* @param {DOMDocument} d Optional document.
* @return HTML element that matches the id.
* @type HTMLElement
*/
_getElementById : function(id, d) {
var e, i, j, f;
if (typeof(d) == "undefined")
d = document;
e = d.getElementById(id);
if (!e) {
f = d.forms;
for (i=0; i<f.length; i++) {
for (j=0; j<f[i].elements.length; j++) {
if (f[i].elements[j].name == id) {
e = f[i].elements[j];
break;
}
}
}
}
return e;
},
/**
* Returns a array of nodes selected retrived from the child nodes of the specified node.
*
* @param {HTMLNode} n Node to get children from.
* @param {Array} na Array to fill with children.
* @param {int} t Node type to get.
* @param {string} nn Node name of items to retrive.
* @return Node array.
* @type Array
* @deprecated
*/
getNodeTree : function(n, na, t, nn) {
return this.selectNodes(n, function(n) {
return (!t || n.nodeType == t) && (!nn || n.nodeName == nn);
}, na ? na : []);
},
/**
* Returns the parent element of the specified node based on the search criteria.
*
* @param {HTMLNode} node Node to get parent element of.
* @param {string} na Comma separated list of element names to get.
* @param {function} f Optional function to call for each node, if it returns true the node is valid.
* @param {HTMLNode} r Optional root element, never go below this point.
* @return HTMLElement or null based on search criteras.
* @type HTMLElement
*/
getParentElement : function(n, na, f, r) {
var re = na ? new RegExp('^(' + na.toUpperCase().replace(/,/g, '|') + ')$') : 0, v;
// Compatiblity with old scripts where f param was a attribute string
if (f && typeof(f) == 'string')
return this.getParentElement(n, na, function(no) {return tinyMCE.getAttrib(no, f) !== '';});
return this.getParentNode(n, function(n) {
return ((n.nodeType == 1 && !re) || (re && re.test(n.nodeName))) && (!f || f(n));
}, r);
},
/**
* 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 it will stop the execution and return that node.
*
* @param {DOMNode} n HTML node to search parents on.
* @param {function} f Selection function to execute on each node.
* @param {HTMLNode} r Optional root element, never go below this point.
* @return DOMNode or null if it wasn't found.
* @type DOMNode
*/
getParentNode : function(n, f, r) {
while (n) {
if (n == r)
return null;
if (f(n))
return n;
n = n.parentNode;
}
return null;
},
/**
* Returns the attribute value of a element or the default value if it wasn't found.
*
* @param {HTMLElement} elm HTML element to get attribute from.
* @param {string} name Attribute name to retrive.
* @param {string} dv Optional default value to return, this value defaults to a empty string.
* @return Attribute value or default value if it wasn't found in element.
* @type string
*/
getAttrib : function(elm, name, dv) {
var v;
if (typeof(dv) == "undefined")
dv = "";
// Not a element
if (!elm || elm.nodeType != 1)
return dv;
try {
v = elm.getAttribute(name, 0);
} catch (ex) {
// IE 7 may cast exception on invalid attributes
v = elm.getAttribute(name, 2);
}
// Try className for class attrib
if (name == "class" && !v)
v = elm.className;
// Workaround for a issue with Firefox 1.5rc2+
if (tinyMCE.isGecko) {
if (name == "src" && elm.src != null && elm.src !== '')
v = elm.src;
// Workaround for a issue with Firefox 1.5rc2+
if (name == "href" && elm.href != null && elm.href !== '')
v = elm.href;
} else if (tinyMCE.isIE) {
switch (name) {
case "http-equiv":
v = elm.httpEquiv;
break;
case "width":
case "height":
v = elm.getAttribute(name, 2);
break;
}
}
if (name == "style" && !tinyMCE.isOpera)
v = elm.style.cssText;
return (v && v !== '') ? v : dv;
},
/**
* Sets the attribute value for a specific attribute.
*
* @param {HTMLElement} el HTML element to set attribute on.
* @param {string} name Attribute name to set.
* @param {string} va Attribute value to set.
* @param {boolean} fix Optional fix value state, if true only number data will be accepted.
*/
setAttrib : function(el, name, va, fix) {
if (typeof(va) == "number" && va != null)
va = "" + va;
if (fix) {
if (va == null)
va = "";
va = va.replace(/[^0-9%]/g, '');
}
if (name == "style")
el.style.cssText = va;
if (name == "class")
el.className = va;
if (va != null && va !== '' && va != -1)
el.setAttribute(name, va);
else
el.removeAttribute(name);
},
/**
* Sets a style attribute item value.
*
* @param {HTMLElement} e HTML element to set style attribute item on.
* @param {string} n Style item name to set.
* @param {string} v Style item value to set.
*/
setStyleAttrib : function(e, n, v) {
e.style[n] = v;
// Style attrib deleted in IE
if (tinyMCE.isIE && v == null || v == '') {
v = tinyMCE.serializeStyle(tinyMCE.parseStyle(e.style.cssText));
e.style.cssText = v;
e.setAttribute("style", v);
}
},
/**
* Switches the CSS class of the specified element. This method also caches the
* elements in a lookup table for performance. This should only be used for TinyMCE main UI controls
* like buttons or select elements.
*
* @param {HTMLElement} ei Element to set CSS class on.
* @param {string} c CSS class to set.
*/
switchClass : function(ei, c) {
var e;
if (tinyMCE.switchClassCache[ei])
e = tinyMCE.switchClassCache[ei];
else
e = tinyMCE.switchClassCache[ei] = document.getElementById(ei);
if (e) {
// Keep tile mode
if (tinyMCE.settings.button_tile_map && e.className && e.className.indexOf('mceTiledButton') == 0)
c = 'mceTiledButton ' + c;
e.className = c;
}
},
/**
* Returns the absolute x, y position of a node. The position will be returned in a object with
* two properties absLeft and absTop.
*
* @param {HTMLNode} n HTML element to get x, y position from.
* @param {HTMLNode} cn Optional HTML element to to stop position calcualtion by.
* @return Absolute position of the specified element.
* @type TinyMCE_ElementPosition
*/
getAbsPosition : function(n, cn) {
var l = 0, t = 0;
while (n && n != cn) {
l += n.offsetLeft;
t += n.offsetTop;
n = n.offsetParent;
}
return {absLeft : l, absTop : t};
},
/**
* Finds any previous element by name. This will loop through the siblings
* inorder to find the specified element by name. If the element wasn't found
* it will return a null value.
*
* @param {HTMLNode} e HTML node to search from.
* @param {string} n Comma separated list of element names to search for.
* @return HTML Element or null if it wasn't found.
* @type HTMLElement
*/
prevNode : function(e, n) {
var a = n.split(','), i;
while ((e = e.previousSibling) != null) {
for (i=0; i<a.length; i++) {
if (e.nodeName == a[i])
return e;
}
}
return null;
},
/**
* Finds any element after the current one by name. This will loop through the siblings
* inorder to find the specified element by name. If the element wasn't found
* it will return a null value.
*
* @param {HTMLNode} e HTML node to search from.
* @param {string} n Comma separated list of element names to search for.
* @return HTML Element or null if it wasn't found.
* @type HTMLElement
*/
nextNode : function(e, n) {
var a = n.split(','), i;
while ((e = e.nextSibling) != null) {
for (i=0; i<a.length; i++) {
if (e.nodeName == a[i])
return e;
}
}
return null;
},
/**
* Returns a array of elements when the specified function matches a node.
*
* @param {DOMNode} n Node to select children from.
* @param {string} na Element name(s) to search for separated by commas.
* @param {function} f Function that returns true/false if the node is to be added or not.
* @return Array with selected elements.
* @type Array
*/
selectElements : function(n, na, f) {
var i, a = [], nl, x;
for (x=0, na = na.split(','); x<na.length; x++)
for (i=0, nl = n.getElementsByTagName(na[x]); i<nl.length; i++)
(!f || f(nl[i])) && a.push(nl[i]);
return a;
},
/**
* Returns a array of nodes when the specified function matches a node.
*
* @param {DOMNode} n Node to select children from.
* @param {function} f Function that returns true/false if the node is to be added or not.
* @param {Array} a Optional array to fill with nodes.
* @return Array with selected nodes.
* @type Array
*/
selectNodes : function(n, f, a) {
var i;
if (!a)
a = [];
if (f(n))
a[a.length] = n;
if (n.hasChildNodes()) {
for (i=0; i<n.childNodes.length; i++)
tinyMCE.selectNodes(n.childNodes[i], f, a);
}
return a;
},
/**
* Adds a CSS class to the specified element. It will remove any previous item with the same name
* so adding a class that already exists will move it to the end.
*
* @param {HTMLElement} e HTML element to add CSS class to.
* @param {string] c CSS class to add to HTML element.
* @param {boolean] b Optional parameter, if set to true, class will be added to the beginning.
* @return Returns the new class attribute value.
* @type string
*/
addCSSClass : function(e, c, b) {
var o = this.removeCSSClass(e, c);
return e.className = b ? c + (o !== '' ? (' ' + o) : '') : (o !== '' ? (o + ' ') : '') + c;
},
/**
* Removes the specified CSS class from the element.
*
* @param {HTMLElement} e HTML element to remove CSS class to.
* @param {string] c CSS class to remove to HTML element.
* @return Returns the new class attribute value.
* @type string
*/
removeCSSClass : function(e, c) {
c = e.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' ');
return e.className = c != ' ' ? c : '';
},
/**
* Returns true if the specified element has the specified class.
*
* @param {HTMLElement} n HTML element to check CSS class on.
* @param {string] c CSS class to check for.
* @return true/false if the specified element has the specified class.
* @type bool
*/
hasCSSClass : function(n, c) {
return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
},
/**
* Renames the specified element to the specified name.
*
* @param {HTMLElement} e Element to rename.
* @param {string} n New name of the element.
* @param {DOMDocument} d Optional document reference.
*/
renameElement : function(e, n, d) {
var ne, i, ar;
d = typeof(d) == "undefined" ? tinyMCE.selectedInstance.getDoc() : d;
if (e) {
ne = d.createElement(n);
ar = e.attributes;
for (i=ar.length-1; i>-1; i--) {
if (ar[i].specified && ar[i].nodeValue)
ne.setAttribute(ar[i].nodeName.toLowerCase(), ar[i].nodeValue);
}
ar = e.childNodes;
for (i=0; i<ar.length; i++)
ne.appendChild(ar[i].cloneNode(true));
e.parentNode.replaceChild(ne, e);
}
},
/**
* Returns the viewport of the specificed window instance.
*
* @param {Window} w Window to get viewport of.
* @return Viewport object with fields top, left, width and height.
* @type Object
*/
getViewPort : function(w) {
var d = w.document, m = d.compatMode == 'CSS1Compat', b = d.body, de = d.documentElement;
return {
left : w.pageXOffset || (m ? de.scrollLeft : b.scrollLeft),
top : w.pageYOffset || (m ? de.scrollTop : b.scrollTop),
width : w.innerWidth || (m ? de.clientWidth : b.clientWidth),
height : w.innerHeight || (m ? de.clientHeight : b.clientHeight)
};
},
/**
* Returns the current runtime/computed style value of a element.
*
* @param {Element} n HTML element to get style from.
* @param {string} na Style name to return.
* @param {string} d Optional default value.
* @return {string} Current runtime/computed style value of a element.
*/
getStyle : function(n, na, d) {
if (!n)
return false;
// Gecko
if (tinyMCE.isGecko && n.ownerDocument.defaultView) {
try {
return n.ownerDocument.defaultView.getComputedStyle(n, null).getPropertyValue(na);
} catch (n) {
// Old safari might fail
return null;
}
}
// Camelcase it, if needed
na = na.replace(/-(\D)/g, function(a, b){
return b.toUpperCase();
});
// IE & Opera
if (n.currentStyle)
return n.currentStyle[na];
return false;
}
/**#@-*/
});