babydom
Version:
Little DOM manipulations
418 lines (333 loc) • 11.4 kB
JavaScript
/*!
* babydom v0.0.7, https://github.com/hoho/babydom
* (c) 2014 Marat Abdullin, MIT license
*/
var $B = (function(document, encodeURIComponent, undefined) {
'use strict';
function select(selector, context) {
context = context || document;
return /:first$/.test(selector) ?
(context = context.querySelector(selector.slice(0, -6))) ?
[context]
:
[]
:
Array.prototype.slice.call(context.querySelectorAll(selector));
}
function BabyDOM(nodeOrSelector, context) {
var i,
nodes = (nodeOrSelector instanceof Node) || (nodeOrSelector === window) ?
[nodeOrSelector]
:
(nodeOrSelector ? select(nodeOrSelector, context) : []);
for (i = 0; i < nodes.length; i++) {
this[i] = nodes[i];
}
this.length = nodes.length;
}
var proto = BabyDOM.prototype,
properties = {disabled: false, checked: false, style: null, value: ''},
captureEvents = {focus: 1, blur: 1},
emitByMethodCall = {focus: 1, blur: 1, reset: 1},
eventHandlers = {},
whitespace = /[\x20\t\r\n\f]+/,
classStr = 'class',
styleStr = 'style';
function eventHandler(e) {
var node,
$b,
h,
i,
next = 0,
origStopPropagation = e.stopPropagation,
origStopImmediatePropagation = e.stopImmediatePropagation;
node = e.target;
e.stopPropagation = function() {
next |= 1;
origStopPropagation.call(e);
};
e.stopImmediatePropagation = function() {
next |= 2;
origStopImmediatePropagation.call(e);
};
while (node) {
if ((($b = node.$b)) && ((h = $b[e.type]))) {
// Make a copy of current handlers (because the original list
// might change during callbacks execution).
h = h.slice(0);
for (i = 0; i < h.length; i++) {
h[i].call(node, e);
if (next & 2) { return; }
}
}
if (next & 1) { return; }
node = node.parentNode;
}
}
function classToObject(val) {
var ret = {},
i,
cls;
val = (val || '').split(whitespace);
for (i = 0; i < val.length; i++) {
if ((cls = val[i])) {
ret[cls] = 1; // `1` is shorter than `true` and we need just keys.
}
}
return ret;
}
function __attr(node, name, val) {
var key,
ret;
if (val === undefined) {
return name in properties ?
(name === styleStr ? node.style.cssText : node[name])
:
node.getAttribute(name);
} else {
if (name in properties) {
if (name === styleStr) {
if (typeof val === 'object') {
ret = [];
for (key in val) {
ret.push(key + ': ' + val[key]);
}
val = ret.join('; ');
}
if (val !== undefined) {
node.style.cssText = val || null;
}
} else {
node[name] = val === null ? properties[name] : val;
}
} else {
if (val === null) {
node.removeAttribute(name);
} else {
node.setAttribute(name, val);
}
}
}
}
function modifyClass(self, val, addOrRemove, force) {
var i,
node,
obj1,
obj2 = classToObject(val),
cls;
if (force !== undefined) { force = !!force; }
for (i = 0; i < self.length; i++) {
node = self[i];
obj1 = classToObject(__attr(node, classStr));
for (cls in obj2) {
if (addOrRemove === true || force === true) {
// Add class or toggle true.
obj1[cls] = 1; // `1` is shorter than `true` and we need just keys.
} else if (addOrRemove === false || force === false) {
// Remove class or toggle false.
delete obj1[cls];
} else {
// Toggle class.
if (cls in obj1) {
delete obj1[cls];
} else {
obj1[cls] = 1; // `1` is shorter than `true` and we need just keys.
}
}
}
__attr(node, classStr, Object.keys(obj1).join(' ') || null);
}
return self;
}
proto.attr = function attr(name, val) {
var self = this,
i,
ret;
for (i = 0; i < self.length; i++) {
ret = __attr(self[i], name, val);
if (val === undefined) { return ret; }
}
if (val !== undefined) {
return self;
}
};
proto.emit = function emit(event, detail) {
var self = this,
i,
node,
e;
for (i = 0; i < self.length; i++) {
node = self[i];
if ((event in emitByMethodCall) && node[event]) {
node[event]();
} else {
// TODO: It would probably be needed to distinguish event types.
// For now we're not trying to follow standards much.
e = document.createEvent('HTMLEvents');
e.initEvent(event, true, true);
if (detail) {
e.detail = detail;
}
node.dispatchEvent(e);
}
}
return self;
};
proto.on = function on(event, handler) {
var $b,
h,
self = this,
i,
node;
if (event) {
event = event.split(whitespace);
if (event.length === 1) {
event += '';
if (!(event in eventHandlers)) {
document.body.addEventListener(event, eventHandler, event in captureEvents);
eventHandlers[event] = true;
}
for (i = 0; i < self.length; i++) {
node = self[i];
if (!(($b = node.$b))) {
$b = node.$b = {};
}
if (!((h = $b[event]))) {
h = $b[event] = [];
}
h.push(handler);
}
} else {
for (i = 0; i < event.length; i++) {
self.on(event[i], handler);
}
}
}
return self;
};
proto.off = function(event, handler) {
var i,
self = this,
$b,
handlers;
for (i = 0; i < self.length; i++) {
$b = self[i].$b;
if (event && $b) {
event = event.split(whitespace);
if (event.length === 1) {
event += '';
if (((handlers = $b[event])) && handler) {
i = 0;
while (i < handlers.length) {
if (handlers[i] === handler) {
handlers.splice(i, 1);
} else {
i++;
}
}
}
if (handlers && (!handlers.length || !handler)) {
delete $b[event];
}
} else {
for (i = 0; i < event.length; i++) {
self.off(event[i], handler);
}
}
}
}
return self;
};
proto.text = function text(val) {
var self = this,
i,
node;
for (i = 0; i < self.length; i++) {
node = self[i];
if (val === undefined) {
return node.textContent;
} else {
node.textContent = val;
}
}
if (val !== undefined) {
return self;
}
};
proto.addClass = function addClass(val) {
return modifyClass(this, val, true);
};
proto.removeClass = function removeClass(val) {
return modifyClass(this, val, false);
};
proto.toggleClass = function toggleClass(val, force) {
return modifyClass(this, val, undefined, force);
};
proto.hasClass = function hasClass(val) {
return val in classToObject(this.attr(classStr));
};
proto.serialize = function serialize(type) {
var self = this,
i,
j,
k,
node,
elems,
elem,
name,
opts,
ret = [];
for (i = 0; i < self.length; i++) {
node = self[i];
if (node instanceof HTMLFormElement) {
elems = node.elements;
for (j = 0; j < elems.length; j++) {
elem = elems[j];
if ((name = elem.name)) {
switch (elem.type) {
case 'submit':
case 'reset':
case 'button':
case 'file':
break;
case 'checkbox':
case 'radio':
if (elem.checked) {
ret.push({name: name, value: elem.value});
}
break;
case 'select-multiple':
opts = elem.options;
for (k = 0; k < opts.length; k++) {
if (opts[k].selected) {
ret.push({name: name, value: opts[k].value});
}
}
break;
default:
ret.push({name: name, value: elem.value});
}
}
}
}
}
if (type === 'map') {
opts = {};
for (i = ret.length; i--;) {
opts[ret[i].name] = ret[i].value;
}
ret = opts;
}
if (type === 'qs') {
opts = [];
for (i = 0; i < ret.length; i++) {
opts.push(encodeURIComponent(ret[i].name) + '=' + encodeURIComponent(ret[i].value));
}
ret = opts.join('&');
}
return ret;
};
return function(node, context) {
return new BabyDOM(node, context);
};
})(document, encodeURIComponent);