oxjs/source/Ox/js/DOM.js
2014-09-22 15:55:29 +02:00

857 lines
28 KiB
JavaScript

'use strict';
/*@
Ox.$ <f> Generic HTML element, mimics jQuery
value <s|h|w|?> tagname, selector, html element, `window`, or `document`
Passing a tagname ('<tagname>') creates an element, passing a selector
('tagname', '.classname' or '#id') selects an element.
(value) -> <o> Element object
> Ox.$('<div>').addClass('red').hasClass('red')
true
> Ox.$('<div>').addClass('red').removeClass('red').hasClass('red')
false
> Ox.$('<div>').addClass('red').addClass('red')[0].className
'red'
> Ox.$('<a>').append(Ox.$('<b>')).children('b').length
1
> Ox.$('<b>').appendTo(Ox.$('<a>')).parents('a').length
1
> Ox.$('<div>').attr({id: 'red'}).attr('id')
'red'
> Ox.$('<div>').attr({id: 'red'}).removeAttr('id').attr('id')
void 0
> Ox.$('<div>').css({color: 'red'}).css('color')
'red'
> Ox.$('<div>').html('red').html()
'red'
> Ox.$('<div>').html('red').empty().html()
''
> !!Ox.$('<div>').on({click: function(e) { Ox.test(e.type, 'click'); }}).trigger('click')
true
> Ox.$('<input>').val('red').val()
'red'
@*/
Ox.$ = Ox.element = function $(value) {
var elements = Ox.isArray(value) ? value // array of elements
: Ox.isNodeList(value) ? Ox.slice(value) // nodelist
: !Ox.isString(value) ? [value] // window, document or element
: value[0] == '<' ? [document.createElement(value.slice(1, -1))]
: Ox.slice(document.querySelectorAll(value)),
mousewheelEvents = ['wheel', 'mousewheel'],
originalMousewheelEvents = 'onwheel' in document ? ['wheel']
: ['mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
previousDisplay;
function getElements($other) {
return $other.forEach ? $other
: Ox.range($other.length).map(function(index) {
return $other[index];
});
}
function normalizeEvents(args) {
var ret = {};
Ox.forEach(Ox.makeObject(args), function(callback, event) {
if (Ox.contains(mousewheelEvents, event)) {
originalMousewheelEvents.forEach(function(event) {
ret[event] = callback;
});
} else {
ret[event] = callback;
}
});
return ret;
}
function removeOxElements(parent, includeParent) {
return;
if (includeParent) {
removeOxElement(parent);
} else {
Ox.$(parent).find('.OxElement').forEach(removeOxElement);
}
function removeOxElement(element) {
var $element = Ox.getOxElement(element);
$element && $element.removeElement();
}
}
return elements.length ? Ox.extend(
Ox.zipObject(Ox.range(elements.length), elements
), {
/*@
add <f> Adds another DOM object to this DOM object
(other) -> This DOM object
other <o> Other DOM object
@*/
add: function add($other) {
elements = Ox.unique(elements.concat(other.elements()));
this.length = elements.length;
return this;
},
/*@
addClass <f> Adds a class name to all elements
(className) -> <o> This DOM object
className <s> Class name
@*/
addClass: function addClass(string) {
string = Ox.clean(string);
elements.forEach(function(element) {
element.className = Ox.unique(((
element.className ? element.className + ' ' : ''
) + string).split(' ')).join(' ');
});
return this;
},
/*@
append <f> Appends one or more DOM objects to this DOM object
(object[, object[, ...]]) -> <o> This DOM object
element <o> Another DOM object
@*/
append: function append() {
var $others = Ox.slice(arguments);
elements.forEach(function(element) {
$others.forEach(function($other) {
getElements($other).forEach(function(otherElement) {
element.appendChild(otherElement);
});
});
});
return this;
},
/*@
appendTo <f> Appends this DOM object to another DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
appendTo: function appendTo($other) {
getElements($other).forEach(function(otherElement) {
elements.forEach(function(element) {
otherElement.appendChild(element);
});
});
return this;
},
/*@
attr <f> Gets or sets an attribute for all elements
(key) -> <s> Value
(key, value) -> <o> This DOM object
({key0: value0, key1: value1, ...}) -> <o> This DOM object
key <s> Attribute name
value <s> Attribute value
@*/
attr: function attr() {
var args = arguments, ret, that = this;
if (args.length == 1 && Ox.isString(args[0])) {
ret = this[0].getAttribute
? this[0].getAttribute(args[0])
: void 0;
// fixme: why exactly is this needed?
return ret === null ? void 0 : ret;
} else {
args = Ox.makeObject(args);
elements.forEach(function(element) {
Ox.forEach(args, function(value, key) {
if (
element.setAttribute
&& !Ox.contains([false, null, void 0], value)
) {
element.setAttribute(key, value);
}
});
});
return this;
}
},
/*@
children <f> Returns the unique list of children of all elements
([selector]) -> <[h]> Children
selector <s|'*'> CSS selector
@*/
children: function children(selector) {
var children = Ox.unique(Ox.flatten(elements.map(function(element) {
return Ox.slice(element.childNodes);
})));
return Ox.$(selector ? children.filter(function(child) {
return Ox.$(child).is(selector);
}) : children);
},
/*@
css <f> Gets or sets a CSS attribute for all elements
(key) -> <s> Value
(key, value) -> <o> This DOM object
({key0: value0, key1: value1, ...}) -> <o> This DOM object
key <s> Attribute name
value <s> Attribute value
@*/
css: function css() {
var args = arguments;
if (args.length == 1 && Ox.isString(args[0])) {
return elements[0].style[args[0]];
} else {
elements.forEach(function(element) {
Ox.forEach(Ox.makeObject(args), function(value, key) {
element.style[key] = value;
});
});
return this;
}
},
/*@
data <f> Gets or sets data
() -> <o> All data
(key) -> <s> Value
(key, value) -> <o> This DOM object
({key0: value0, key1: value1, ...}) -> <o> This DOM object
key <s> Property
value <*> Value
@*/
data: function data() {
var args;
if (arguments.length == 1 && Ox.isString(arguments[0])) {
return element.getAttribute('data-' + arguments[0]);
} else {
args = Ox.makeObject(arguments);
elements.forEach(function(element) {
Ox.forEach(args, function(value, key) {
element.setAttribute('data-' + key, value);
});
});
return this;
}
},
/*@
elements <a> All elements
@*/
elements: elements,
/*@
eq <f> Reduces the list of elements to the one at the given index
() -> <o> This DOM object
@*/
eq: function eq() {
var that = this;
Ox.loop(1, this.length, function(index) {
delete that[index];
});
this.elements = [this.elements[index]];
this.length = 1;
return this;
},
/*@
empty <f> Empties the inner HTML of all elements
() -> <o> This DOM object
@*/
empty: function empty() {
return this.html('');
},
/*@
every <f> Tests if every element satisfies a given condition
(test) -> True if every element passes the test
test <f> Test function
@*/
every: function every() {
return Array.prototype.every.apply(elements, arguments);
},
/*@
filter <f> Filters all elements by a given condition
(test) -> Array of matching elements
test <f> Test function
@*/
filter: function filter() {
return Array.prototype.filter.apply(elements, arguments);
},
/*@
find <f> Find all descendant elements matching a CSS selector
([selector]) -> <[h]> Elements
selector <s|'*'> CSS selector
@*/
find: function find(selector) {
return Ox.$(Ox.unique(Ox.flatten(elements.map(function(element) {
return Ox.slice(element.querySelectorAll(selector || '*'));
}))));
},
/*@
forEach <f> Loops over all elements
(iterator) -> This DOM object
iterator <f> Iterator function
@*/
forEach: function forEach() {
Array.prototype.forEach.apply(elements, arguments);
return this;
},
/*@
hasClass <f> Returns true if this element has a given class
(className) -> <b> True if this element has the class
className <s> Class name
@*/
hasClass: function hasClass(string) {
return elements.some(function(element) {
return Ox.contains(element.className.split(' '), string);
});
},
/*@
height <f> Returns the height of the first element
() -> <n> Height in px
@*/
height: function height() {
return elements[0].offsetHeight;
},
/*@
hide <f> Hides all elements
() -> <o> This DOM object
@*/
hide: function hide() {
previousDisplay = this.css('display');
return this.css({display: 'none'});
},
/*@
html <f> Gets or sets the innerHTML of all elements
() -> <s> The inner HTML
(html) -> <o> This DOM object
html <s> The inner HTML
@*/
html: function html(string) {
var html = '';
if (arguments.length == 0) {
elements.forEach(function(element) {
html += element.innerHTML;
})
return html;
} else {
elements.forEach(function(element) {
removeOxElements(element);
element.innerHTML = string;
});
return this;
}
},
/*@
insertAfter <f> Inserts this DOM object after another DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
insertAfter: function insertAfter($other) {
var nextSibling = $other[0].nextSibling;
elements.forEach(function(element) {
$other[0].parentNode.insertBefore(element, nextSibling);
})
return this;
},
/*@
insertBefore <f> Inserts this DOM object before another DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
insertBefore: function insertBefore($other) {
elements.forEach(function(element) {
$other[0].parentNode.insertBefore(element, $other[0]);
});
return this;
},
/*@
is <f> Tests if any element matches a CSS selector
(selector) -> <b> True if the element matches the selector
selector <s> CSS selector
@*/
is: function is(selector) {
return elements.some(function(element) {
var parent = element.parentNode;
if (!parent) {
parent = document.createElement('div');
parent.appendChild(element);
}
return Ox.contains(parent.querySelectorAll(selector), element);
});
},
/*@
length <n> Number of elements
@*/
length: elements.length,
/*@
map <f> Transforms all elements
(iterator) -> [] Transformed elements
iterator <f> Iterator function
@*/
map: function map() {
return Array.prototype.filter.map(elements, arguments);
},
/*@
next <f> Returns the unique list of siblings directly after all elements
() -> <[h]> Siblings
@*/
next: function next() {
return Ox.$(Ox.unique(Ox.filter(elements.map(function(element) {
return element.nextSibling;
}))));
},
/*@
nextAll <f> Returns the unique list of siblings after all elements
() -> <[h]> Siblings
@*/
nextAll: function nextAll() {
var siblings = [];
elements.forEach(function(element) {
var sibling = element;
while (true) {
sibling = sibling.nextSibling;
if (!sibling) {
break;
}
siblings.push(sibling);
}
});
return Ox.$(Ox.unique(siblings));
},
/*@
off <f> Unbinds a callback from an event
(event) -> <o> This DOM object (unbinds all callbacks)
(event, callback) -> <o> This DOM object
({event0: callback0, event1: callback1, ...}) -> <o> This DOM object
event <s> Event name
callback <f> Callback function
@*/
off: function off(event, callback) {
var args = normalizeEvents(arguments);
elements.forEach(function(element) {
Ox.forEach(args, function(callback, event) {
if (callback) {
element.removeEventListener(event, callback, false);
} else {
element['on' + event] = null;
}
});
});
return this;
},
/*@
on <f> Binds a callback to an event
(event, callback) -> <o> This DOM object
({event0: callback0, event1: callback1, ...}) -> <o> This DOM object
event <s> Event name
callback <f> Callback function
e <o> Event properties
@*/
on: function on() {
var args = normalizeEvents(arguments);
elements.forEach(function(element) {
Ox.forEach(args, function(callback, event) {
element.addEventListener(event, callback, false);
});
});
return this;
},
/*@
one <f> Binds a callback to an event and unbinds it on first invocation
(event, callback) -> <o> This DOM object
({event0: callback0, event1: callback1, ...}) -> <o> This DOM object
event <s> Event name
callback <f> Callback function
e <o> Event properties
@*/
one: function one(events) {
var args = Ox.slice(arguments), that = this;
Ox.forEach(normalizeEvents(arguments), function(callback, event) {
that.on(event, function fn() {
that.off(event, fn);
return callback.apply(that, args);
});
});
return this;
},
/*@
parent <f> Returns the unique list of parents of all elements
() -> <[h]> Parent elements
@*/
parent: function parent() {
return Ox.$(Ox.unique(Ox.compact(elements.map(function(element) {
return element.parentNode;
}))));
},
/*@
parents <f> Returns the unique list of all ancestors of all elements
([selector]) -> <[h]> Ancestor elements
selector <s|'*'> CSS selector
@*/
parents: function parents(selector) {
var parents = [];
Ox.reverse(elements).forEach(function(element) {
var parent = element;
while (true) {
parent = parent.parentNode;
if (!parent || parent == document) {
break;
}
parents.unshift(parent);
}
});
parents = Ox.unique(parents);
return Ox.$(selector ? parents.filter(function(parent) {
return Ox.$(parent).is(selector);
}) : parents);
},
/*@
prepend <f> Prepends one or more DOM objects to this DOM object
(object[, object[, ...]]) -> <o> DOM object
object <o> Another DOM objectt
@*/
prepend: function prepend() {
var $others = Ox.slice(arguments).reverse();
elements.forEach(function(element) {
var parent = element.parentNode;
$others.forEach(function($other) {
getElements($other).forEach(function(otherElement) {
parent.insertBefore(otherElement, parent.firstChild);
});
});
});
return this;
},
/*@
prependTo <f> Prepends this DOM object to another DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
prependTo: function prependTo($other) {
getElements($other).forEach(function(otherElement) {
var firstChild = otherElement.firstChild
elements.forEach(function(element) {
otherElement.insertBefore(element, firstChild);
});
});
return this;
},
/*@
prev <f> Returns the unique list of siblings directly before all elements
() -> <[h]> Siblings
@*/
prev: function prev() {
return Ox.$(Ox.unique(Ox.filter(elements.map(function(element) {
return element.previousSibling;
}))));
},
/*@
prevAll <f> Returns the unique list of siblings before all elements
() -> <[h]> Siblings
@*/
prevAll: function prevAll() {
var siblings = [];
Ox.reverse(elements).forEach(function(element) {
var sibling = element;
while (true) {
sibling = sibling.previousSibling;
if (!sibling) {
break;
}
siblings.unshift(sibling);
}
});
return Ox.$(Ox.unique(siblings));
},
/*@
reduce <f> Applies `reduce` to all elements
@*/
reduce: function reduce() {
return Array.prototype.reduce.apply(elements, arguments);
},
/*@
remove <f> Removes all element from the DOM
() -> <o> This DOM object
@*/
remove: function remove() {
elements.forEach(function(element) {
if (element.parentNode) {
removeOxElements(element, true);
element.parentNode.removeChild(element);
}
});
return this;
},
/*@
removeAttr <f> Removes an attribute from all elements
(key) -> <o> This DOM object
([key0, key1, ...]) -> <o> This DOM object
key <s> The attribute
@*/
removeAttr: function removeAttr() {
var keys = Ox.makeArray(arguments);
elements.forEach(function(element) {
keys.forEach(function(key) {
element.removeAttribute(key);
});
});
return this;
},
/*@
removeClass <f> Removes a class name from all elements
(className) -> <o> This DOM object
className <s> Class name
@*/
removeClass: function removeClass(string) {
var classNames = Ox.clean(string).split(' ');
elements.forEach(function(element) {
element.className = element.className.split(' ')
.filter(function(className) {
return !Ox.contains(classNames, className)
})
.join(' ');
});
return this;
},
/*@
replace <f> Replaces another DOM object with this DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
replace: function replace($other) {
getElements($other).forEach(function(otherElement) {
var parent = otherElement.parentNode,
sibling = otherElement.nextSibling;
if (parent) {
removeOxElements(otherElement, true);
parent.removeChild(otherElement);
elements.forEach(function(element) {
parent.insertBefore(element, sibling)
});
}
});
return this;
},
/*@
replaceWith <f> Replaces this DOM object with another DOM object
(object) -> <o> This DOM object
object <o> Another DOM object
@*/
replaceWith: function replaceWith($other) {
elements.forEach(function(element) {
var parent = element.parentNode,
sibling = element.nextSibling;
if (parent) {
removeOxElements(element, true);
parent.removeChild(element);
getElements($other).forEach(function(otherElement) {
parent.insertBefore(otherElement, sibling);
});
}
});
return this;
},
/*@
show <f> Shows all elements
() -> This DOM object
@*/
show: function show() {
return this.css({display: previousDisplay || 'block'});
},
/*@
siblings <f> Returns all siblings of all elements
([selector]) -> <[h]> Siblings
selector <s|'*'> CSS selector
@*/
siblings: function siblings(selector) {
var siblings = Ox.unique(elements.map(function(element) {
return Ox.filter(
element.parentNode.childNodes,
function(sibling) {
return sibling !== element;
}
);
}));
return Ox.$(selector ? siblings.filter(function(sibling) {
return Ox.$(sibling).is(selector);
}) : siblings);
},
/*@
some <f> Tests if some elements satisfy a given condition
(test) -> True if some elements pass the test
test <f> Test function
@*/
some: function some() {
return Array.prototype.some.apply(elements, arguments);
},
/*@
text <f> Gets or sets the text contents of all elements
() -> <s> The text contents
(text) -> <o> This DOM object
text <s> The text contents
@*/
text: function text(string) {
var text = '';
if (arguments.length == 0) {
elements.forEach(function(element) {
text += Ox.isString(element.textContent)
? element.textContent : element.innerText;
});
return text;
} else {
elements.forEach(function(element) {
element.empty();
element.appendChild(document.createTextNode(string));
});
return this;
}
},
/*@
toggle <f> Toggle visibility of all elements
() -> This DOM object
@*/
toggle: function toggle() {
return this[
Ox.$(element).css('display') == 'none' ? 'show' : 'hide'
]();
},
/*@
toggleClass <f> Toggles a class name for all elements
(className) -> <o> This DOM object
className <s> Class name
@*/
toggleClass: function toggleClass(string) {
elements.forEach(function(element) {
var $element = Ox.$(element);
$element[
$element.hasClass(string) ? 'removeClass' : 'addClass'
](string);
})
return this;
},
/*@
trigger <f> Triggers an event
(event) -> <o> This DOM object
@*/
trigger: function trigger(event) {
elements.forEach(function(element) {
var e = document.createEvent('MouseEvents');
e.initEvent(event, true, true);
element.dispatchEvent(e);
});
return this;
},
/*@
val <f> Gets the value of the first or sets the value of all elements
() -> <s> Value
(value) -> <o> This DOM object
value <s> Value
@*/
val: function val(value) {
var ret;
if (arguments.length == 0) {
return elements[0].value;
} else {
elements.forEach(function(element) {
element.value = value;
});
return this;
}
},
/*@
width <f> Returns the width of the first element
() -> <n> Width in px
@*/
width: function width() {
return elements[0].offsetWidth;
}
}) : null;
};
/*@
Ox.canvas <function> Generic canvas object
Returns an object with the properties: `canvas`, `context`, `data` and
`imageData`.
(width, height) -> <o> canvas
(image) -> <o> canvas
width <n> Width in px
height <n> Height in px
image <e> Image object
@*/
Ox.canvas = function() {
var c = {}, isImage = arguments.length == 1,
image = isImage ? arguments[0] : {
width: arguments[0], height: arguments[1]
};
c.context = (c.canvas = Ox.$('<canvas>').attr({
width: image.width, height: image.height
})[0]).getContext('2d');
isImage && c.context.drawImage(image, 0, 0);
c.data = (c.imageData = c.context.getImageData(
0, 0, image.width, image.height
)).data;
return c;
};
/*@
Ox.documentReady <function> Calls a callback function once the DOM is ready
(callback) -> <b> If true, the document was ready
callback <f> Callback function
@*/
Ox.documentReady = (function() {
var callbacks = [];
document.onreadystatechange = window.onload = function() {
if (document.readyState == 'complete') {
callbacks.forEach(function(callback) {
callback();
});
document.onreadystatechange = window.onload = null;
}
};
return function(callback) {
if (document.readyState == 'complete') {
callback();
return true;
} else {
callbacks.push(callback);
return false;
}
};
}());