Skip to content

Instantly share code, notes, and snippets.

@tmarshall
Created June 16, 2016 22:44
Show Gist options
  • Select an option

  • Save tmarshall/fc17d6a638a683d2422fb0c2f423584d to your computer and use it in GitHub Desktop.

Select an option

Save tmarshall/fc17d6a638a683d2422fb0c2f423584d to your computer and use it in GitHub Desktop.
Mootools-ish clone I wrote in '09
(function() {
var
window = this,
Denizen = window.Denizen = function(name, options) {
options = options || {};
var object = options.init || options.extend || new Function;
object.constructor = Denizen;
object.$type = name.toLowerCase();
object.prototype.constructor = options.extend || object;
//object.prototype.$type = 'denizen';
object.prototype.implemented = ['implement', 'isImplemented', 'unImplement', '$type', '$key'];
object.prototype.isImplemented = function(key) {
if($chk(Array.indexOf) === true)
return (this.implemented.indexOf(key) === -1) ? false : true;
else {
for(var i = 0; i < this.implemented.length; i++)
if(this.implemented[i] === key) return true;
return false;
}
};
object.prototype.unImplement = function(key) {
delete this.prototype[key];
this.implemented.erase(key);
return this;
};
object.prototype.implement = function(funcs) {
for(var fn in funcs) {
this.prototype[fn] = funcs[fn];
this.implemented.push(fn);
}
return this;
};
window[name] = object;
return object;
},
Deft = window.Deft = {
version: '0.1.5',
author: {
company: 'Duzo Design, LLC',
person: 'Timothy Marshall'
}
},
_bank = {
$: {},
events: {}
},
$_key = 0,
$_genKey = function(el) {
return el.$key || (el.$key = ++$_key);
};
/*
utility functions
*/
var
$type = window.$type = function(obj) {
return (obj === undefined || obj === null) ? false :
(obj.$type) ? obj.$type :
(obj.nodeName) ? (function() {
switch(obj.nodeType) {
case 1: return 'element';
case 3: return 'text';
}
})() :
(typeof obj == 'object') ? (function() {
return (typeof obj.length == 'number') ? 'array' : 'object';
})() :
typeof obj;
},
$chk = window.$chk = function(obj) {
return (obj === undefined || obj === null) ? false : true;
},
$random = window.$random = function(min, max) {
if(!$chk(max)) {
max = min;
min = 0;
}
return Math.floor(Math.random() * (max - min + 1) + min);
},
$func = window.$func = function() { return this; },
$args = window.$args = function(args) {
var returns = [];
for (var i = 0, l = args.length; i < l; i++)
returns.push(args[i]);
return returns;
},
$implement = window.$implement = function(Denizens, funcs) {
Denizens = $args(Denizens);
$H(funcs).each(function(name, func) {
if(Denizens.any(function(den) {
return den.isImplemented(name);
}))
return;
Denizens.each(function(den) {
den.prototype[name] = func;
den.implemented.push(name);
});
});
return Denizens;
},
$fetch = window.$fetch = function(key) {
if(!key)
return _bank.$;
return _bank.$[key];
},
$store = window.$store = function() {
if(arguments.length === 2) {
_bank.$[arguments[0]] = arguments[1];
return true;
}
arguments[0].each(function(k, v) {
_bank.$[k] = v;
});
};
/*
implementing denizens
*/
(function() {
var inits = {
'Array': Array,
'String': String,
'Function': Function,
'Number': Number,
'Date': Date,
'Document': window.Document,
'Window': window.Document.body
};
for (var i in inits)
new Denizen(i, {
extend: inits[i]
});
})();
/*
FUNCTION DENIZEN
*/
Function.implement({
bind: function() {
var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift();
return function() {
return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
};
}
});
/*
Hash DENIZEN
*/
new Denizen('Hash', {
init: function(obj) {
this.prototype.constructor = obj || {};
}
});
Hash.implement({
add: function(obj) {
var returns = this.clone();
obj.keys().each(function(o) {
if(!$chk(this[o]))
this[o] = obj[o];
}.bind(returns));
return returns;
},
any: function(fn) {
var ya = false;
this.keys().each(function(o, i) {
if(fn.call(this, o, this[o], i) === true)
ya = true;
}.bind(this));
return ya;
},
clean: function() {
var returns = {};
this.each(function(k, v) {
returns[k] = v;
});
return returns;
},
clone: function() {
var c = {};
this.each(function(k, v) {
c[k] = (['object', 'array'].has($type(v))) ? v.clone() : v;
});
return c;
},
each: function(fn) {
this.keys().each(function(o, i) {
fn.call(this, o, this[o], i);
}.bind(this));
return this;
},
erase: function(keys) {
var returns = this.clone();
keys.each(function(o) {
delete this[o];
}.bind(returns));
return returns;
},
every: function(fn) {
var ya = true;
this.keys().each(function(o, i) {
if(fn.call(this, o, this[o], i) === false)
ya = false;
}.bind(this));
return ya;
},
extend: function(obj) {
obj.keys().each(function(o) {
this[o] = obj[o];
}.bind(this));
return this;
},
filter: function(fn) {
var i = 0, result = {};
for(var o in this)
if(!Object.isImplemented(o)) {
if(fn.call(this, o, this[o], i) === true)
result[o] = this[o];
i++;
}
return $H(result);
},
firstKey: function() {
return this.keys()[0];
},
firstValue: function() {
return this[this.firstKey()];
},
hasKey: function(key) {
return this.hasOwnProperty(key);
},
hasValue: function(val) {
return this.values().has(val);
},
keyOf: function(val) {
var is = false;
this.keys().each(function(o) {
if(this[o] === val)
is = o;
}.bind(this));
return is;
},
keys: function() {
var result = [];
for(var o in this)
if(!Object.isImplemented(o))
result.push(o);
return result;
},
lastKey: function() {
return this.keys().last();
},
lastValue: function() {
return this.values().last();
},
none: function(fn) {
return !this.any(fn);
},
random: function(fn) {
var keys = this.keys();
var key = keys[$random(keys.length - 1)];
fn.call(this, key, this[key]);
return this;
},
values: function() {
var result = [];
for(var o in this)
if(!Object.isImplemented(o))
result.push(this[o]);
return result;
}
});
var $H = window.$H = function(obj) {
/*
return ($type(obj) == 'object') ? obj : (function() {
var returns = {};
for (var i = 0, l = this.length; i < l; i++)
returns[i] = this[i];
return returns;
}.bind(arguments))();
*/
return new Hash(obj);
};
/*
ARRAY DENIZEN
*/
Array.implement({
add: function(objs) {
objs = $A(objs);
var c = this.copy();
objs.each(function(o) {
c.push(o);
});
return c;
},
any: function(fn) {
var ya = false;
this.each(function(o, i) {
if(fn.call(this, o, i) === true)
ya = true;
}.bind(this));
return ya;
},
clone: function() {
var c = [];
this.each(function(o) {
c.push((['object', 'array'].has($type(o))) ? o.clone() : o);
});
return c;
},
count: function(obj) {
return this.filter(function(o) {
return o === obj;
}).length;
},
each: function(fn) {
for (var i = 0, l = this.length; i < l; i++)
fn.call(this, this[i], i);
return this;
},
erase: function(objs) {
var c = this.clone(), objs = $A(objs);
for (var i = c.length - 1, l = 0; l <= i; i--)
if(objs.has(c[i]))
c.splice(i, 1);
return c;
},
every: function(fn) {
var ya = true;
this.each(function(o, i) {
if(fn.call(this, o, i) === false)
ya = false;
}.bind(this));
return ya;
},
filter: function(fn) {
var c = this.clone();
for (var i = c.length - 1, l = 0; l <= i; i--)
if(fn.call(c, c[i], i) === false)
c.splice(i, 1);
return c;
},
has: function() {
var objs = $args(arguments);
if(objs.length === 1)
return ($chk(Array.indexOf) === true) ?
(this.indexOf(objs[0]) === -1) ? false : true :
(function() {
for(var i = 0; i < this.length; i++)
if(this[i] === objs[0])
return true;
return false;
}.bind(this))();
var ya = true;
objs.each(function(o) {
if(!this.has(o))
ya = false;
}.bind(this));
return ya;
},
include: function(objs) {
objs = $A(objs);
var c = this.clone();
objs.each(function(o) {
if(!this.has(o))
this.push(o);
}.bind(c));
return c;
},
indexOf: function(val) {
try {
return this.indexOf(val);
} catch(e) {
for (var i = 0, l = this.length; i < l; i++)
if(this[i] === val)
return i;
return -1;
}
},
none: function(fn) {
return !this.any(fn);
},
unique: function() {
var c = this.clone().filter(function(o) {
return this.count(o) === 1;
}.bind(this));
this.filter(function(o) {
return this.count(o) !== 1;
}.bind(this)).each(function(o) {
c = c.include(o);
});
return c;
}
});
var $A = window.$A = function() {
if(arguments.length === 1 && $type(arguments[0]) == 'array')
return arguments[0];
var returns = [];
for (var i = 0, l = arguments.length; i < l; i++)
returns.push(arguments[i]);
return returns;
};
/*
NUMBER DENIZEN
*/
Number.implement({
abs: function() {
return Math.abs(this);
},
negative: function() {
return this < 0;
},
positive: function() {
return this > 0;
},
random: function() {
return $random(this.round());
},
round: function() {
return (Math.ceil(this) - this < 0.5) ?
((this >= 0) ? Math.ceil(this) : Math.floor(this)) :
((this >= 0) ? Math.floor(this) : Math.ceil(this));
},
times: function(fn) {
for (var i = 0, l = parseInt(this); i < l; i++)
fn.call(this, i);
return this;
}
});
/*
STRING DENIZEN
*/
String.implement({
blank: function() {
return this.trim().length === 0;
},
empty: function() {
return this.length === 0;
},
escapeRegExp: function(){
return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
},
stripScripts: function() {
return this.replace(/<script[^>]*>.*<\/script>/gi, '');
},
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
substitute: function(obj, options) {
options = options || {
left: '{',
right: '}'
};
var c = this;
obj.keys().each(function(o) {
c = c.replace(new RegExp(options.left.escapeRegExp() + o + options.right.escapeRegExp(), 'g'), obj[o]);
});
return c;
},
test: function(reg) {
return reg.test(this);
},
trim: function() {
return this.replace(/^\s*|\s*$/g, '');
}
});
/*
ELEMENT DENIZEN
*/
new Denizen('Element', {
extend: window.Element,
init: function(obj, butes) {
if($type(obj) == 'element' && $chk(obj.$type) !== false)
return obj;
var el = ($type(obj) == 'string') ? document.createElement(obj) : obj;
Element.prototype.keys().each(function(o) {
this[o] = Element.prototype[o];
}.bind(el));
return ($type(obj) == 'string') ? el.set(butes || {}) : el;
}
});
Element.implement({
$: function(sel) {
return $(sel, this);
},
$$: function(sel) {
return $$(sel, this);
},
fetch: function(key) {
if(!key)
return _bank[this.$key] || {};
return (_bank[this.$key] || {})[key];
},
filter: function(sel) {
return $.filter(sel, this);
},
find: function(sel) {
return $.find(sel, this);
},
get: function(str) {
return str === 'tag' ? this.tagName.toLowerCase() || null :
$type(this.innerHTML) === false ? null :
str === 'text' ? this.innerHTML.stripScripts().stripTags() :
str === 'html' ? this.innerHTML :
null;
},
getStyle: function(what) {
return this.style[what] || false;
},
inject: function(parent) {
parent.appendChild(this);
return this;
},
match: function(sel) {
return $.match(sel, this);
},
parse: function(sel) {
return $.parse(sel, this);
},
purge: function() {
this.innerHTML = '';
},
search: function(sel) {
return $.search(sel, this);
},
set: function(butes) {
butes.each(function(k, v) {
switch(k) {
case 'text':
this.innerHTML = v.stripScripts().stripTags()
break;
case 'html':
this.innerHTML = v;
break;
default:
this.setAttribute(k, v);
}
}.bind(this));
return this;
},
store: function() {
if(!$chk(_bank[this.$key]))
_bank[this.$key] = {};
if(arguments.length === 2) {
_bank[this.$key][arguments[0]] = arguments[1];
return this;
}
$H(arguments[0]).each(function(k, v) {
_bank[this.$key][k] = v;
}.bind(this));
}
});
/*
AJAX DENIZEN
*/
new Denizen('Ajax', {
init: function(settings) {
var onComplete = settings.onComplete || $func;
var onFailure = settings.onFailure || $func;
this.req = (function() {
return (window.XMLHttpRequest) ? new XMLHttpRequest() :
(window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') :
$func;
})();
this.req.onComplete = onComplete;
this.req.onFailure = onFailure;
this.req.method = settings.method;
this.req.url = settings.url;
this.req.data = settings.data || {};
this.req.onreadystatechange = function() {
if(this.readyState === 4)
this.onComplete(this.responseText);
}.bind(this.req);
return this;
}
});
Ajax.implement({
request: function() {
try {
this.req.open(this.req.method, this.req.url, true);
this.req.send(this.req.data);
}
catch(err) {
this.req.onFailure(err);
}
},
requestJSON: function() {
this.req.onComplete = function(r) {
try {
this.onComplete(r.decodeJSON());
}
catch(err) {
this.onFailure(err);
}
}.bind(this.req);
this.request();
}
});
/*
Event Denizen
*/
new Denizen('Event', {
init: function(ev) {
ev = ev || win.event;
var target = ev.target || ev.srcElement;
while(target && target.nodeType == 3)
target = target.parentNode;
return ev;
}
});
Event.implement({
/* stop: function() {
this.returnValue = false;
} */
stop: function(){
return this.stopPropagation().preventDefault();
},
stopPropagation: function(){
this.cancelBubble = true;
return this;
},
preventDefault: function(){
this.returnValue = false;
return this;
}
});
/*
a:
onblur
onclick
ondblclick
onfocus
onmousedown
onmousemove
onmouseout
onmouseover
onmouseup
onkeydown
onkeypress
onkeyup
*/
$implement([Element, Document, Window], {
listen: function(ev, func) {
$_genKey(this);
if(!_bank.events[this.$key])
_bank.events[this.$key] = [];
_bank.events[this.$key].push(func);
this['on' + ev.replace(/^on/, '')] = function(e) {
this.each(function(o) {
o(e);
});
}.bind($H(_bank.events[this.$key]));
}
});
/*
JSON support
inspired by JSON.js
http://www.JSON.org/json2.js
*/
(function() {
var
f = function(n) {
return n < 10 ? '0' + n : n;
},
cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep,
JSON = {};
var toJSON = function(obj) {
if($type(obj) === 'date')
return obj.getUTCFullYear() + '-' +
f(obj.getUTCMonth() + 1) + '-' +
f(obj.getUTCDate()) + 'T' +
f(obj.getUTCHours()) + ':' +
f(obj.getUTCMinutes()) + ':' +
f(obj.getUTCSeconds()) + 'Z';
return obj.valueOf();
};
var quote = function(str) {
escapable.lastIndex = 0;
return escapable.test(str) ?
'"' + str.replace(escapable, function (a) {
var c = meta[a];
return $type(c) === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
};
var str = function(key, holder) {
var i, k, v, length, mind = gap, partial, value = holder[key];
if(value && $type(value) === 'object' && $type(toJSON(value)) === 'function')
value = toJSON(key);
if($type(rep) === 'function')
value = rep.call(holder, key, value);
switch($type(value)) {
case 'string':
return quote(value);
case 'number':
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
return String(value);
case 'object':
if (!value)
return 'null';
gap += indent;
partial = [];
if(Object.toString.apply(value) === '[object Array]') {
length = value.length;
for(i = 0; i < length; i += 1)
partial[i] = str(i, value) || 'null';
v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
if(rep && $type(rep) === 'object') {
length = rep.length;
for(i = 0; i < length; i += 1) {
k = rep[i];
if($type(k) === 'string') {
v = str(k, value);
if(v)
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
else {
value.each(function(k, val) {
if(Object.hasKey.call(val, k)) {
v = str(k, val);
if(v)
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
});
}
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
};
JSON.stringify = function (value, replacer, space) {
var i;
gap = '';
indent = '';
if($type(space) === 'number')
for (i = 0; i < space; i += 1)
indent += ' ';
else if($type(space) === 'string')
indent = space;
rep = replacer;
if(replacer && $type(replacer) !== 'function' && ($type(replacer) !== 'object' || $type(replacer.length) !== 'number'))
throw new Error('JSON stringify failed');
return str('', {'': value});
};
JSON.parse = function (text, reviver) {
var walk = function(holder, key) {
var k, v, value = holder[key];
if(value && $type(value) === 'object') {
value.each(function(k, val) {
if(Object.hasOwnProperty.call(val, k))
v = walk(value, k);
if(v !== undefined)
value[k] = v;
else
delete value[k];
});
}
return reviver.call(holder, key, value);
};
cx.lastIndex = 0;
if(cx.test(text))
text = text.replace(cx, function (a) {
return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
if(/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
j = eval('(' + text + ')');
return $type(reviver) === 'function' ? walk({'': j}, '') : j;
}
throw new Error('JSON parsing failed');
};
String.implement({
decodeJSON: function() {
return JSON.parse(this);
}
});
})();
/*
Sly v1.0rc0 <http://sly.digitarald.com>
(C) 2009 Harald Kirschner <http://digitarald.de>
Open source under MIT License
----
Modified by Timothy Marshall
*/
(function() {
var cache = {};
/**
* Sly::constructor
*/
var Sly = function(text) {
return cache[text] || (cache[text] = new Sly.initialize(text));
};
Sly.initialize = function(text) {
// normalise
this.text = (typeof(text) == 'string') ? text.replace(/^\s+/, '') : '';
};
var proto = Sly.initialize.prototype = Sly.prototype;
/**
* Sly.implement
*/
Sly.implement = function(key, properties) {
for (var prop in properties) Sly[key][prop] = properties[prop];
};
/**
* Sly.features
*
* @todo Check proper support with more tests
*/
var features = Sly.features = {
querySelector: !!(document.querySelectorAll),
elementsByClass: !!(document.getElementsByClassName)
};
var locateFast = function() {
return true;
};
/**
* Sly::search
*/
proto.search = function(context) {
var iterate;
if (!context) context = document;
else iterate = (context instanceof Array);
var results; // overall result
if (features.querySelector && !iterate && context.nodeType == 9) {
try {
results = context.querySelectorAll(this.text);
} catch(e) {}
if (results) return Sly.toArray(results);
}
var parsed = this.parse();
var current = {}, // unique ids for one iteration process
combined, // found nodes from one iteration process
nodes, // context nodes from one iteration process
all = {}, // unique ids for overall result
state = {}; // matchers temporary state
// unifiers
var getUid = Sly.getUid;
var locateCurrent = function(node) {
var uid = getUid(node);
return (current[uid]) ? null : (current[uid] = true);
};
for (var i = 0, selector; (selector = parsed[i]); i++) {
var locate = locateCurrent;
if (selector.first) {
if (!results) locate = locateFast;
if (iterate) nodes = context;
else if (selector.combinator) nodes = [context]; // allows combinators before selectors
}
if (selector.last && results) {
current = all;
combined = results;
} else {
// default stack
current = {};
combined = [];
}
if (!selector.combinator) {
// without prepended combinator
combined = selector.combine(combined, context, selector, state, locate, !(combined.length));
} else {
// with prepended combinators
for (var k = 0, l = nodes.length; k < l; k++) {
combined = selector.combine(combined, nodes[k], selector, state, locate);
}
}
if (selector.last) {
if (combined.length) results = combined;
} else {
nodes = combined;
}
}
return results || [];
};
/**
* Sly::find
*/
proto.find = function(context) {
return this.search(context)[0];
};
/**
* Sly::match
*/
proto.match = function(node) {
return !!(this.parse()[0].match(node, {}));
};
/**
* Sly::filter
*/
proto.filter = function(nodes) {
var results = [], match = this.parse()[0].match;
for (var i = 0, node; (node = nodes[i]); i++) {
if (match(node)) results.push(node);
}
return results;
};
/**
* Sly.recompile()
*/
var pattern;
Sly.recompile = function() {
var key, combList = [','], operList = ['!'];
for (key in combinators) {
if (key != ' ') {
combList[(key.length > 1) ? 'unshift' : 'push'](Sly.escapeRegExp(key));
}
}
for (key in operators) operList.push(key);
/**
The regexp is a group of every possible selector part including combinators.
"|" separates the possible selectors.
Capturing parentheses:
1 - Combinator (only requires to allow multiple-character combinators)
2 - Attribute name
3 - Attribute operator
4, 5, 6 - The value
7 - Pseudo name
8, 9, 10 - The value
*/
pattern = new RegExp(
// A tagname
'[\\w\\u00c0-\\uFFFF][\\w\\u00c0-\\uFFFF-]*|' +
// An id or the classname
'[#.][\\w\\u00c0-\\uFFFF-]+|' +
// Whitespace (descendant combinator)
'[ \\t\\r\\n\\f](?=[\\w\\u00c0-\\uFFFF*#.[:])|' +
// Other combinators and the comma
'(' + combList.join('|') + ')[ \\t\\r\\n\\f]*|' +
// An attribute, with the various and optional value formats ([name], [name=value], [name="value"], [name='value']
'\\[([\\w\\u00c0-\\uFFFF-]+)(?:([' + operList.join('') + ']?=)(?:"([^"]*)"|\'([^\']*)\'|([^\\]]*)))?]|' +
// A pseudo-class, with various formats
':([-\\w\\u00c0-\\uFFFF]+)(?:\\((?:"([^"]*)"|\'([^\']*)\'|([^)]*))\\))?|' +
// The universial selector, not process
'\\*', 'g'
);
};
// I prefer it outside, not sure if this is faster
var create = function(combinator) {
return {
ident: [],
classes: [],
attributes: [],
pseudos: [],
combinator: combinator
};
};
var blank = function($0) {
return $0;
};
/**
* Sly::parse
*
* Returns an array with one object for every selector:
*
* {
* tag: (String) Tagname (defaults to null for universal *)
* id: (String) Id
* classes: (Array) Classnames
* attributes: (Array) Attribute objects with "name", "operator" and "value"
* pseudos: (Array) Pseudo objects with "name" and "value"
* operator: (Char) The prepended operator (not comma)
* first: (Boolean) true if it is the first selector or the first after a comma
* last: (Boolean) true if it is the last selector or the last before a comma
* ident: (Array) All parsed matches, can be used as cache identifier.
* }
*/
proto.parse = function(plain) {
var save = (plain) ? 'plain' : 'parsed';
if (this[save]) return this[save];
var compute = (plain) ? blank : this.compute;
var parsed = [], current = create(null);
current.first = true;
var refresh = function(combinator) {
parsed.push(compute(current));
current = create(combinator);
};
var match, $0;
while ((match = pattern.exec(this.text))) {
$0 = match[0];
switch ($0.charAt(0)) {
case '.':
current.classes.push($0.slice(1));
break;
case '#':
current.id = $0.slice(1);
break;
case '[':
current.attributes.push({
name: match[2],
operator: match[3] || null,
value: match[4] || match[5] || match[6] || null
});
break;
case ':':
current.pseudos.push({
name: match[7],
value: match[8] || match[9] || match[10] || null
});
break;
case ',':
current.last = true;
refresh(null);
current.first = true;
continue;
case ' ': case '\t': case '\r': case '\n': case '\f':
match[1] = ' ';
default:
var combinator = match[1];
if (combinator) {
if (current.first && !current.ident.length) current.combinator = combinator;
else refresh(combinator);
} else {
if ($0 != '*') current.tag = $0;
}
}
current.ident.push($0);
}
current.last = true;
parsed.push(compute(current));
return (this[save] = parsed);
};
// chains two given functions
function chain(prepend, append, aux, unshift) {
var fn = (prepend) ? ((unshift) ? function(node, state) {
return append(node, aux, state) && prepend(node, state);
} : function(node, state) {
return prepend(node, state) && append(node, aux, state);
}) : function(node, state) {
return append(node, aux, state);
};
fn.$slyIndex = (prepend) ? (prepend.$slyIndex + 1) : 0;
return fn;
};
// prepared match comperators, probably needs namespacing
var empty = function() {
return true;
};
var matchId = function(node, id) {
return (node.id == id);
};
var matchTag = function(node, tag) {
return (node.nodeName == tag);
};
var prepareClass = function(name) {
return (new RegExp('(?:^|[ \\t\\r\\n\\f])' + name + '(?:$|[ \\t\\r\\n\\f])'));
};
var matchClass = function(node, expr) {
return node.className && expr.test(node.className);
};
var prepareAttribute = function(attr) {
if (!attr.operator || !attr.value) return attr;
var parser = operators[attr.operator];
if (parser) { // @todo: Allow functions, not only regex
attr.escaped = Sly.escapeRegExp(attr.value);
attr.pattern = new RegExp(parser(attr.value, attr.escaped, attr));
}
return attr;
};
var matchAttribute = function(node, attr) {
var read = Sly.getAttribute(node, attr.name);
switch (attr.operator) {
case null: return read;
case '=': return (read == attr.value);
case '!=': return (read != attr.value);
}
if (!read && attr.value) return false;
return attr.pattern.test(read);
};
/**
* Sly::compute
*
* Attaches the following methods to the selector object:
*
* {
* search: Uses the most convinient properties (id, tag and/or class) of the selector as search.
* matchAux: If search does not contain all selector properties, this method matches an element against the rest.
* match: Matches an element against all properties.
* simple: Set when matchAux is not needed.
* combine: The callback for the combinator
* }
*/
proto.compute = function(selector) {
var i, item, match, search, matchSearch, tagged,
tag = selector.tag,
id = selector.id,
classes = selector.classes;
var nodeName = (tag) ? tag.toUpperCase() : null;
if (id) {
tagged = true;
matchSearch = chain(matchSearch, matchId, id);
search = function(context) {
if (context.getElementById) {
var el = context.getElementById(id);
return (el && (!nodeName || el.nodeName == nodeName)) ? [el] : [];
}
var query = context.getElementsByTagName(tag || '*');
for (var j = 0, node; (node = query[j]); j++) {
if (node.id == id) return [node];
}
return [];
};
}
if (classes.length > 0) {
if (!search && Sly.features.elementsByClass) {
for (i = 0; (item = classes[i]); i++) {
matchSearch = chain(matchSearch, matchClass, prepareClass(item));
}
var joined = classes.join(' ');
search = function(context) {
return context.getElementsByClassName(joined);
};
} else if (!search && classes.length == 1) { // optimised for typical .one-class-only
tagged = true;
var expr = prepareClass(classes[0]);
matchSearch = chain(matchSearch, matchClass, expr);
search = function(context) {
var query = context.getElementsByTagName(tag || '*');
var found = [];
for (var i = 0, node; (node = query[i]); i++) {
if (node.className && expr.test(node.className)) found.push(node);
}
return found;
};
} else {
for (i = 0; (item = classes[i]); i++) {
match = chain(match, matchClass, prepareClass(item));
}
}
}
if (tag) {
if (!search) {
matchSearch = chain(matchSearch, matchTag, nodeName);
search = function(context) {
return context.getElementsByTagName(tag);
};
} else if (!tagged) { // search does not filter by tag yet
match = chain(match, matchTag, nodeName);
}
} else if (!search) { // default engine
search = function(context) {
return context.getElementsByTagName('*');
};
}
for (i = 0; (item = selector.pseudos[i]); i++) {
if (item.name == 'not') { // optimised :not(), fast as possible
match = chain(match, function(node, not) {
return !not(node);
}, Sly(item.value).parse()[0].match);
} else {
var parser = pseudos[item.name];
// chain(match, matchAttribute, prepareAttribute(item))
if (parser) match = chain(match, parser, item.value);
}
}
for (i = 0; (item = selector.attributes[i]); i++) {
match = chain(match, matchAttribute, prepareAttribute(item));
}
if ((selector.simple = !(match))) {
selector.matchAux = empty;
} else {
selector.matchAux = match;
matchSearch = chain(matchSearch, match);
}
selector.match = matchSearch || empty;
selector.combine = Sly.combinators[selector.combinator || ' '];
selector.search = search;
return selector;
};
/**
* Combinators
*/
var combinators = Sly.combinators = {
' ': function(combined, context, selector, state, locate, fast) {
var nodes = selector.search(context);
if (fast && selector.simple) return Sly.toArray(nodes);
for (var i = 0, node, aux = selector.matchAux; (node = nodes[i]); i++) {
if (locate(node) && aux(node, state)) combined.push(node);
}
return combined;
},
'>': function(combined, context, selector, state, locate) {
var nodes = selector.search(context);
for (var i = 0, node; (node = nodes[i]); i++) {
if (node.parentNode == context && locate(node) && selector.matchAux(node, state)) combined.push(node);
}
return combined;
},
'+': function(combined, context, selector, state, locate) {
while ((context = context.nextSibling)) {
if (context.nodeType == 1) {
if (locate(context) && selector.match(context, state)) combined.push(context);
break;
}
}
return combined;
},
'~': function(combined, context, selector, state, locate) {
while ((context = context.nextSibling)) {
if (context.nodeType == 1) {
if (!locate(context)) break;
if (selector.match(context, state)) combined.push(context);
}
}
return combined;
}
};
/**
* Pseudo-Classes
*/
var pseudos = Sly.pseudos = {
// w3c pseudo classes
'first-child': function(node) {
return pseudos.index(node, 0);
},
'last-child': function(node) {
while ((node = node.nextSibling)) {
if (node.nodeType === 1) return false;
}
return true;
},
'only-child': function(node) {
var prev = node;
while ((prev = prev.previousSibling)) {
if (prev.nodeType === 1) return false;
}
var next = node;
while ((next = next.nextSibling)) {
if (next.nodeType === 1) return false;
}
return true;
},
'nth-child': function(node, value, state) {
var parsed = Sly.parseNth(value || 'n');
if (parsed.special != 'n') return pseudos[parsed.special](node, parsed.a, state);
state = state || {}; // just to be sure
state.positions = state.positions || {};
var uid = Sly.getUid(node) ;
if (!state.positions[uid]) {
var count = 0;
while ((node = node.previousSibling)) {
if (node.nodeType != 1) continue;
count++;
var position = state.positions[Sly.getUid(node)];
if (position != undefined) {
count = position + count;
break;
}
}
state.positions[uid] = count;
}
return (state.positions[uid] % parsed.a == parsed.b);
},
'empty': function(node) {
return !(node.innerText || node.textContent || '').length;
},
'contains': function(node, text) {
return (node.innerText || node.textContent || '').indexOf(text) != -1;
},
'index': function(node, index) {
var count = 0;
while ((node = node.previousSibling)) {
if (node.nodeType == 1 && ++count > index) return false;
}
return (count == index);
},
'even': function(node, value, state) {
return pseudos['nth-child'](node, '2n', state);
},
'odd': function(node, value, state) {
return pseudos['nth-child'](node, '2n+1', state);
}
};
/**
* Attribute operators
*/
var operators = Sly.operators = {
'*=': function(value, escaped) {
return escaped;
},
'^=': function(value, escaped) {
return '^' + escaped;
},
'$=': function(value, escaped) {
return value + '$';
},
'~=': function(value, escaped) {
return '(?:^|[ \\t\\r\\n\\f])' + escaped + '(?:$|[ \\t\\r\\n\\f])';
},
'|=': function(value, escaped) {
return '(?:^|\\|)' + escaped + '(?:$|\\|)';
}
};
// public, overridable
Sly.getAttribute = function(node, name) {
if (name == 'class') return node.className;
return node.getAttribute(name, 2); // 2 for IE, others ignore it
};
var toArray = function(nodes) {
return Array.prototype.slice.call(nodes);
};
try {
toArray(document.documentElement.childNodes);
} catch (e) {
toArray = function(nodes) {
if (nodes instanceof Array) return nodes;
var i = nodes.length, results = new Array(i);
while (i--) results[i] = nodes[i];
return results;
};
}
Sly.toArray = toArray;
var nextUid = 1;
Sly.getUid = (window.ActiveXObject) ? function(node) {
return (node.$slyUid || (node.$slyUid = {id: nextUid++})).id;
} : function(node) {
return node.$slyUid || (node.$slyUid = nextUid++);
};
var nthCache = {};
Sly.parseNth = function(value) {
if (nthCache[value]) return nthCache[value];
var parsed = value.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
if (!parsed) return false;
var a = parseInt(parsed[1], 10), b = (parseInt(parsed[3], 10) || 0) - 1;
if ((a = (isNaN(a)) ? 1 : a)) {
while (b < 1) b += a;
while (b >= a) b -= a;
}
switch (parsed[2]) {
case 'n': parsed = {a: a, b: b, special: 'n'}; break;
case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
case 'first': parsed = {a: 0, special: 'index'}; break;
case 'last': parsed = {special: 'last-child'}; break;
case 'only': parsed = {special: 'only-child'}; break;
default: parsed = {a: (a) ? (a - 1) : b, special: 'index'};
}
return (nthCache[value] = parsed);
};
Sly.escapeRegExp = function(text) {
return text.replace(/[-.*+?^${}()|[\]\/\\]/g, '\\$&');
};
// generic accessors
Sly.generise = function(name) {
Sly[name] = function(text) {
var cls = Sly(text);
return cls[name].apply(cls, Array.prototype.slice.call(arguments, 1));
}
};
var generics = ['parse', 'search', 'find', 'match', 'filter'];
for (var i = 0; generics[i]; i++) Sly.generise(generics[i]);
// compile pattern for the first time
Sly.recompile();
// FIN
window.$ = document.$ = function(sel, par) {
switch($type(sel)) {
case 'string':
return $(Sly.find('#' + sel.replace(/^#?/, ''), par));
break;
case 'array':
sel.each(function(o) {
sel = $(sel, par);
});
return sel;
break;
}
return new Element(sel);
};
window.$$ = document.$$ = function(sel, par) {
var returns = Sly.search(sel, par);
returns.each(function(o) {
o = new Element(o);
});
return returns;
};
['find', 'search', 'match', 'filter', 'parse'].each(function(o) {
window.$[o] = function(sel, par) {
return $(Sly[o](sel, par));
};
});
})();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment