724 lines
14 KiB
JavaScript
Executable File
724 lines
14 KiB
JavaScript
Executable File
/**
|
|
* DomQuery.js
|
|
*
|
|
* Copyright, Moxiecode Systems AB
|
|
* Released under LGPL License.
|
|
*
|
|
* License: http://www.tinymce.com/license
|
|
* Contributing: http://www.tinymce.com/contributing
|
|
*
|
|
* Some of this logic is based on jQuery code that is released under
|
|
* MIT license that grants us to sublicense it under LGPL.
|
|
*
|
|
* @ignore-file
|
|
*/
|
|
|
|
/**
|
|
* @class tinymce.dom.DomQuery
|
|
*/
|
|
define("tinymce/dom/DomQuery", [
|
|
"tinymce/dom/EventUtils",
|
|
"tinymce/dom/Sizzle"
|
|
], function(EventUtils, Sizzle) {
|
|
var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
|
|
var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
|
|
var Event = EventUtils.Event;
|
|
|
|
function isDefined(obj) {
|
|
return typeof obj !== "undefined";
|
|
}
|
|
|
|
function isString(obj) {
|
|
return typeof obj === "string";
|
|
}
|
|
|
|
function createFragment(html) {
|
|
var frag, node, container;
|
|
|
|
container = doc.createElement("div");
|
|
frag = doc.createDocumentFragment();
|
|
container.innerHTML = html;
|
|
|
|
while ((node = container.firstChild)) {
|
|
frag.appendChild(node);
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
function domManipulate(targetNodes, sourceItem, callback) {
|
|
var i;
|
|
|
|
if (typeof sourceItem === "string") {
|
|
sourceItem = createFragment(sourceItem);
|
|
} else if (sourceItem.length) {
|
|
for (i = 0; i < sourceItem.length; i++) {
|
|
domManipulate(targetNodes, sourceItem[i], callback);
|
|
}
|
|
|
|
return targetNodes;
|
|
}
|
|
|
|
i = targetNodes.length;
|
|
while (i--) {
|
|
callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem);
|
|
}
|
|
|
|
return targetNodes;
|
|
}
|
|
|
|
function hasClass(node, className) {
|
|
return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
|
|
}
|
|
|
|
/**
|
|
* Makes a map object out of a string that gets separated by a delimiter.
|
|
*
|
|
* @method makeMap
|
|
* @param {String} items Item string to split.
|
|
* @param {Object} map Optional object to add items to.
|
|
* @return {Object} name/value object with items as keys.
|
|
*/
|
|
function makeMap(items, map) {
|
|
var i;
|
|
|
|
items = items || [];
|
|
|
|
if (typeof(items) == "string") {
|
|
items = items.split(' ');
|
|
}
|
|
|
|
map = map || {};
|
|
|
|
i = items.length;
|
|
while (i--) {
|
|
map[items[i]] = {};
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
var numericCssMap = makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom');
|
|
|
|
function DomQuery(selector, context) {
|
|
return new DomQuery.fn.init(selector, context);
|
|
}
|
|
|
|
/**
|
|
* Extends the specified object with another object.
|
|
*
|
|
* @method extend
|
|
* @param {Object} target Object to extend.
|
|
* @param {Object..} obj Multiple objects to extend with.
|
|
* @return {Object} Same as target, the extended object.
|
|
*/
|
|
function extend(target) {
|
|
var args = arguments, arg, i, key;
|
|
|
|
for (i = 1; i < args.length; i++) {
|
|
arg = args[i];
|
|
|
|
for (key in arg) {
|
|
target[key] = arg[key];
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* Converts the specified object into a real JavaScript array.
|
|
*
|
|
* @method toArray
|
|
* @param {Object} obj Object to convert into array.
|
|
* @return {Array} Array object based in input.
|
|
*/
|
|
function toArray(obj) {
|
|
var array = [], i, l;
|
|
|
|
for (i = 0, l = obj.length; i < l; i++) {
|
|
array[i] = obj[i];
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Returns the index of the specified item inside the array.
|
|
*
|
|
* @method inArray
|
|
* @param {Object} item Item to look for.
|
|
* @param {Array} array Array to look for item in.
|
|
* @return {Number} Index of the item or -1.
|
|
*/
|
|
function inArray(item, array) {
|
|
var i;
|
|
|
|
if (array.indexOf) {
|
|
return array.indexOf(item);
|
|
}
|
|
|
|
i = array.length;
|
|
while (i--) {
|
|
if (array[i] === item) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns true/false if the specified object is an array.
|
|
*
|
|
* @method isArray
|
|
* @param {Object} obj Object to check if it's an array.
|
|
* @return {Boolean} true/false if the input object is array or not.
|
|
*/
|
|
var isArray = Array.isArray || function(obj) {
|
|
return Object.prototype.toString.call(obj) === "[object Array]";
|
|
};
|
|
|
|
var whiteSpaceRegExp = /^\s*|\s*$/g;
|
|
var trim = function(str) {
|
|
return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
|
|
};
|
|
|
|
/**
|
|
* Executes the callback function for each item in array/object. If you return false in the
|
|
* callback it will break the loop.
|
|
*
|
|
* @method each
|
|
* @param {Object} obj Object to iterate.
|
|
* @param {function} callback Callback function to execute for each item.
|
|
*/
|
|
function each(obj, callback) {
|
|
var length, key, i, undef, value;
|
|
|
|
if (obj) {
|
|
length = obj.length;
|
|
|
|
if (length === undef) {
|
|
// Loop object items
|
|
for (key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
value = obj[key];
|
|
if (callback.call(value, value, key) === false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Loop array items
|
|
for (i = 0; i < length; i++) {
|
|
value = obj[i];
|
|
if (callback.call(value, value, key) === false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
DomQuery.fn = DomQuery.prototype = {
|
|
constructor: DomQuery,
|
|
selector: "",
|
|
length: 0,
|
|
|
|
init: function(selector, context) {
|
|
var self = this, match, node;
|
|
|
|
if (!selector) {
|
|
return self;
|
|
}
|
|
|
|
if (selector.nodeType) {
|
|
self.context = self[0] = selector;
|
|
self.length = 1;
|
|
|
|
return self;
|
|
}
|
|
|
|
if (isString(selector)) {
|
|
if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
|
|
match = [null, selector, null];
|
|
} else {
|
|
match = rquickExpr.exec(selector);
|
|
}
|
|
|
|
if (match) {
|
|
if (match[1]) {
|
|
node = createFragment(selector).firstChild;
|
|
while (node) {
|
|
this.add(node);
|
|
node = node.nextSibling;
|
|
}
|
|
} else {
|
|
node = doc.getElementById(match[2]);
|
|
|
|
if (node.id !== match[2]) {
|
|
return self.find(selector);
|
|
}
|
|
|
|
self.length = 1;
|
|
self[0] = node;
|
|
}
|
|
} else {
|
|
return DomQuery(context || document).find(selector);
|
|
}
|
|
} else {
|
|
this.add(selector);
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
toArray: function() {
|
|
return toArray(this);
|
|
},
|
|
|
|
add: function(items) {
|
|
var self = this;
|
|
|
|
// Force single item into array
|
|
if (!isArray(items)) {
|
|
if (items instanceof DomQuery) {
|
|
self.add(items.toArray());
|
|
} else {
|
|
push.call(self, items);
|
|
}
|
|
} else {
|
|
push.apply(self, items);
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
attr: function(name, value) {
|
|
var self = this;
|
|
|
|
if (typeof name === "object") {
|
|
each(name, function(value, name) {
|
|
self.attr(name, value);
|
|
});
|
|
} else if (isDefined(value)) {
|
|
this.each(function() {
|
|
if (this.nodeType === 1) {
|
|
this.setAttribute(name, value);
|
|
}
|
|
});
|
|
} else {
|
|
return self[0] && self[0].nodeType === 1 ? self[0].getAttribute(name) : undefined;
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
css: function(name, value) {
|
|
var self = this;
|
|
|
|
if (typeof name === "object") {
|
|
each(name, function(value, name) {
|
|
self.css(name, value);
|
|
});
|
|
} else {
|
|
// Camelcase it, if needed
|
|
name = name.replace(/-(\D)/g, function(a, b) {
|
|
return b.toUpperCase();
|
|
});
|
|
|
|
if (isDefined(value)) {
|
|
// Default px suffix on these
|
|
if (typeof(value) === 'number' && !numericCssMap[name]) {
|
|
value += 'px';
|
|
}
|
|
|
|
self.each(function() {
|
|
var style = this.style;
|
|
|
|
// IE specific opacity
|
|
if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") {
|
|
style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
|
|
}
|
|
|
|
try {
|
|
style[name] = value;
|
|
} catch (ex) {
|
|
// Ignore
|
|
}
|
|
});
|
|
} else {
|
|
return self[0] ? self[0].style[name] : undefined;
|
|
}
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
remove: function() {
|
|
var self = this, node, i = this.length;
|
|
|
|
while (i--) {
|
|
node = self[i];
|
|
Event.clean(node);
|
|
|
|
if (node.parentNode) {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
empty: function() {
|
|
var self = this, node, i = this.length;
|
|
|
|
while (i--) {
|
|
node = self[i];
|
|
while (node.firstChild) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
html: function(value) {
|
|
var self = this, i;
|
|
|
|
if (isDefined(value)) {
|
|
i = self.length;
|
|
while (i--) {
|
|
self[i].innerHTML = value;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
return self[0] ? self[0].innerHTML : '';
|
|
},
|
|
|
|
text: function(value) {
|
|
var self = this, i;
|
|
|
|
if (isDefined(value)) {
|
|
i = self.length;
|
|
while (i--) {
|
|
self[i].innerText = self[0].textContent = value;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
return self[0] ? self[0].innerText || self[0].textContent : '';
|
|
},
|
|
|
|
append: function() {
|
|
return domManipulate(this, arguments, function(node) {
|
|
if (this.nodeType === 1) {
|
|
this.appendChild(node);
|
|
}
|
|
});
|
|
},
|
|
|
|
prepend: function() {
|
|
return domManipulate(this, arguments, function(node) {
|
|
if (this.nodeType === 1) {
|
|
this.insertBefore(node, this.firstChild);
|
|
}
|
|
});
|
|
},
|
|
|
|
before: function() {
|
|
var self = this;
|
|
|
|
if (self[0] && self[0].parentNode) {
|
|
return domManipulate(self, arguments, function(node) {
|
|
this.parentNode.insertBefore(node, this.nextSibling);
|
|
});
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
after: function() {
|
|
var self = this;
|
|
|
|
if (self[0] && self[0].parentNode) {
|
|
return domManipulate(self, arguments, function(node) {
|
|
this.parentNode.insertBefore(node, this);
|
|
});
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
appendTo: function(val) {
|
|
DomQuery(val).append(this);
|
|
|
|
return this;
|
|
},
|
|
|
|
addClass: function(className) {
|
|
return this.toggleClass(className, true);
|
|
},
|
|
|
|
removeClass: function(className) {
|
|
return this.toggleClass(className, false);
|
|
},
|
|
|
|
toggleClass: function(className, state) {
|
|
var self = this;
|
|
|
|
if (className.indexOf(' ') !== -1) {
|
|
each(className.split(' '), function() {
|
|
self.toggleClass(this, state);
|
|
});
|
|
} else {
|
|
self.each(function() {
|
|
var node = this, existingClassName;
|
|
|
|
if (hasClass(node, className) !== state) {
|
|
existingClassName = node.className;
|
|
|
|
if (state) {
|
|
node.className += existingClassName ? ' ' + className : className;
|
|
} else {
|
|
node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
hasClass: function(className) {
|
|
return hasClass(this[0], className);
|
|
},
|
|
|
|
each: function(callback) {
|
|
return each(this, callback);
|
|
},
|
|
|
|
on: function(name, callback) {
|
|
return this.each(function() {
|
|
Event.bind(this, name, callback);
|
|
});
|
|
},
|
|
|
|
off: function(name, callback) {
|
|
return this.each(function() {
|
|
Event.unbind(this, name, callback);
|
|
});
|
|
},
|
|
|
|
show: function() {
|
|
return this.css('display', '');
|
|
},
|
|
|
|
hide: function() {
|
|
return this.css('display', 'none');
|
|
},
|
|
|
|
slice: function() {
|
|
return new DomQuery(slice.apply(this, arguments));
|
|
},
|
|
|
|
eq: function(index) {
|
|
return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
|
|
},
|
|
|
|
first: function() {
|
|
return this.eq(0);
|
|
},
|
|
|
|
last: function() {
|
|
return this.eq(-1);
|
|
},
|
|
|
|
replaceWith: function(content) {
|
|
var self = this;
|
|
|
|
if (self[0]) {
|
|
self[0].parentNode.replaceChild(DomQuery(content)[0], self[0]);
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
wrap: function(wrapper) {
|
|
wrapper = DomQuery(wrapper)[0];
|
|
|
|
return this.each(function() {
|
|
var self = this, newWrapper = wrapper.cloneNode(false);
|
|
self.parentNode.insertBefore(newWrapper, self);
|
|
newWrapper.appendChild(self);
|
|
});
|
|
},
|
|
|
|
unwrap: function() {
|
|
return this.each(function() {
|
|
var self = this, node = self.firstChild, currentNode;
|
|
|
|
while (node) {
|
|
currentNode = node;
|
|
node = node.nextSibling;
|
|
self.parentNode.insertBefore(currentNode, self);
|
|
}
|
|
});
|
|
},
|
|
|
|
clone: function() {
|
|
var result = [];
|
|
|
|
this.each(function() {
|
|
result.push(this.cloneNode(true));
|
|
});
|
|
|
|
return DomQuery(result);
|
|
},
|
|
|
|
find: function(selector) {
|
|
var i, l, ret = [];
|
|
|
|
for (i = 0, l = this.length; i < l; i++) {
|
|
DomQuery.find(selector, this[i], ret);
|
|
}
|
|
|
|
return DomQuery(ret);
|
|
},
|
|
|
|
push: push,
|
|
sort: [].sort,
|
|
splice: [].splice
|
|
};
|
|
|
|
// Static members
|
|
extend(DomQuery, {
|
|
extend: extend,
|
|
toArray: toArray,
|
|
inArray: inArray,
|
|
isArray: isArray,
|
|
each: each,
|
|
trim: trim,
|
|
makeMap: makeMap,
|
|
|
|
// Sizzle
|
|
find: Sizzle,
|
|
expr: Sizzle.selectors,
|
|
unique: Sizzle.uniqueSort,
|
|
text: Sizzle.getText,
|
|
isXMLDoc: Sizzle.isXML,
|
|
contains: Sizzle.contains,
|
|
filter: function(expr, elems, not) {
|
|
if (not) {
|
|
expr = ":not(" + expr + ")";
|
|
}
|
|
|
|
return elems.length === 1 ?
|
|
DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] :
|
|
DomQuery.find.matches(expr, elems);
|
|
}
|
|
});
|
|
|
|
function dir(el, prop, until) {
|
|
var matched = [], cur = el[prop];
|
|
|
|
while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) {
|
|
if (cur.nodeType === 1) {
|
|
matched.push(cur);
|
|
}
|
|
|
|
cur = cur[prop];
|
|
}
|
|
|
|
return matched;
|
|
}
|
|
|
|
function sibling(n, el, siblingName, nodeType) {
|
|
var r = [];
|
|
|
|
for(; n; n = n[siblingName]) {
|
|
if ((!nodeType || n.nodeType === nodeType) && n !== el) {
|
|
r.push(n);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
each({
|
|
parent: function(node) {
|
|
var parent = node.parentNode;
|
|
|
|
return parent && parent.nodeType !== 11 ? parent : null;
|
|
},
|
|
|
|
parents: function(node) {
|
|
return dir(node, "parentNode");
|
|
},
|
|
|
|
parentsUntil: function(node, until) {
|
|
return dir(node, "parentNode", until);
|
|
},
|
|
|
|
next: function(node) {
|
|
return sibling(node, 'nextSibling', 1);
|
|
},
|
|
|
|
prev: function(node) {
|
|
return sibling(node, 'previousSibling', 1);
|
|
},
|
|
|
|
nextNodes: function(node) {
|
|
return sibling(node, 'nextSibling');
|
|
},
|
|
|
|
prevNodes: function(node) {
|
|
return sibling(node, 'previousSibling');
|
|
},
|
|
|
|
children: function(node) {
|
|
return sibling(node.firstChild, 'nextSibling', 1);
|
|
},
|
|
|
|
contents: function(node) {
|
|
return toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
|
|
}
|
|
}, function(name, fn){
|
|
DomQuery.fn[name] = function(selector) {
|
|
var self = this, result;
|
|
|
|
if (self.length > 1) {
|
|
throw new Error("DomQuery only supports traverse functions on a single node.");
|
|
}
|
|
|
|
if (self[0]) {
|
|
result = fn(self[0], selector);
|
|
}
|
|
|
|
result = DomQuery(result);
|
|
|
|
if (selector && name !== "parentsUntil") {
|
|
return result.filter(selector);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
});
|
|
|
|
DomQuery.fn.filter = function(selector) {
|
|
return DomQuery.filter(selector);
|
|
};
|
|
|
|
DomQuery.fn.is = function(selector) {
|
|
return !!selector && this.filter(selector).length > 0;
|
|
};
|
|
|
|
DomQuery.fn.init.prototype = DomQuery.fn;
|
|
|
|
return DomQuery;
|
|
}); |