Created
September 7, 2012 07:47
-
-
Save weepy/3664168 to your computer and use it in GitHub Desktop.
o_O version 4.2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function() { | |
/* HTML binding for Lulz | |
,ad8888ba, | |
d8"' `"8b Power of KnockoutJS, with the agility of Backbone | |
d8' `8b | |
,adPPYba, 88 88 Elegantly binds objects to HTML | |
a8" "8a 88 88 | |
8b d8 Y8, ,8P Proxies through jQuery (or whatever $ is) | |
"8a, ,a8" Y8a. .a8P | |
`"YbbdP"' `"Y8888Y"' Automatic dependency resolution | |
888888888888 Plays well with others | |
(c) 2012 by Jonah Fox (weepy), MIT Licensed */ | |
var VERSION = "0.4.2" | |
, slice = [].slice | |
, Events = { | |
/* | |
* Create an immutable callback list, allowing traversal during modification. The tail is an empty object that will always be used as the next node. | |
* */ | |
on: function(events, callback, context) { | |
var ev; | |
events = events.split(/\s+/); | |
var calls = this._callbacks || (this._callbacks = {}); | |
while (ev = events.shift()) { | |
var list = calls[ev] || (calls[ev] = {}); | |
var tail = list.tail || (list.tail = list.next = {}); | |
tail.callback = callback; | |
tail.context = context; | |
list.tail = tail.next = {}; | |
} | |
return this; | |
}, | |
once: function(events, callback, context) { | |
var self = this | |
function run() { | |
self.off(events, run, context) | |
callback() | |
} | |
this.on(events, run, context) | |
}, | |
/* | |
* Remove one or many callbacks. If context is null, removes all callbacks with that function. | |
* If callback is null, removes all callbacks for the event. | |
* If ev is null, removes all bound callbacks for all events. | |
* */ | |
off: function(events, callback, context) { | |
var ev, calls, node; | |
if (!events) { | |
delete this._callbacks; | |
} else if (calls = this._callbacks) { | |
events = events.split(/\s+/); | |
while (ev = events.shift()) { | |
node = calls[ev]; | |
var lastnode = node | |
//delete calls[ev]; | |
if (!callback || !node) continue; | |
// Create a new list, omitting the indicated event/context pairs. | |
while ((node = node.next) && node.next) { | |
if (node.callback === callback && | |
(!context || node.context === context)) { | |
//if(ev == 'all') console.log('removing event', ev, node.context, node.callback) | |
lastnode.next = node.next | |
continue | |
}; | |
lastnode = node | |
//this.on(ev, node.callback, node.context); | |
} | |
} | |
} | |
return this; | |
}, | |
/* | |
* Trigger an event, firing all bound callbacks. Callbacks are passed the same arguments as emit is, apart from the event name. | |
* Listening for "*" passes the true event name as the first argument. | |
* */ | |
emit: function(events) { | |
var event, node, calls, tail, args, all, rest; | |
if (!(calls = this._callbacks)) return this; | |
all = calls['all']; | |
(events = events.split(/\s+/)).push(null); | |
// Save references to the current heads & tails. | |
while (event = events.shift()) { | |
if (all) events.push({next: all.next, tail: all.tail, event: event}); | |
if (!(node = calls[event])) continue; | |
events.push({next: node.next, tail: node.tail}); | |
} | |
//Traverse each list, stopping when the saved tail is reached. | |
rest = slice.call(arguments, 1); | |
while (node = events.pop()) { | |
tail = node.tail; | |
args = node.event ? [node.event].concat(rest) : rest; | |
while ((node = node.next) !== tail && node.callback) { | |
node.callback.apply(node.context || this, args); | |
} | |
} | |
return this; | |
} | |
} | |
/* | |
* Public function to return an observable property | |
* sync: whether to emit changes immediately, or in the next event loop | |
*/ | |
var propertyMethods = { | |
incr: function (val) { return this(this.value + (val || 1)) }, | |
scale: function (val) { return this(this.value * (val || 1)) }, | |
toggle: function (val) { return this(!this.value) }, | |
change: function(fn) { | |
fn | |
? this.on('set', fn) // setup observer | |
: this.emit('set', this(), this.old_val) | |
return this | |
}, | |
mirror: function(other, both) { | |
var self = this | |
other.change(function(val) { | |
if(val != self.value) self(val) | |
}).change() | |
both && other.mirror(this) | |
return this | |
}, | |
toString: function() { | |
return "<"+JSON.stringify(this.value)+">" | |
}, | |
bindTo: function(el) { | |
o_O.bind(this, el) | |
return this | |
}, | |
emitset: function() { | |
if(this._emitting) return // property is already emitting to avoid circular problems | |
this._emitting = true | |
this.emit('set', this(), this.old_value) // force a read | |
delete this._emitting | |
}, | |
// timeout: 0, | |
constructor: o_O // fake this - useful for checking | |
} | |
function o_O(arg, type) { | |
var simple = typeof arg != 'function' | |
function prop(v) { | |
if(arguments.length) { | |
prop.old_value = prop.value | |
prop.value = simple ? v : arg(v) | |
prop.emitset() | |
} else { | |
if(dependencies.checking) | |
dependencies.emit('get', prop) // emit to dependency checker | |
if(!simple) | |
prop.value = arg() | |
} | |
return prop.value | |
} | |
if(simple) | |
prop.value = arg | |
else { | |
prop.dependencies = [] | |
each(dependencies(prop), function(dep) { | |
prop.dependencies.push(dep) | |
dep.change(function() { | |
prop.emitset() | |
}) | |
}) | |
} | |
extend(prop, Events, propertyMethods) | |
if(type) prop.type = type | |
return prop | |
} | |
/* | |
* Calculate dependencies | |
*/ | |
function dependencies(func) { | |
var deps = [] | |
function add(dep) { | |
if(indexOf(deps, dep) < 0 && dep != func) | |
deps.push(dep) | |
} | |
dependencies.checking = true // we're checking dependencies | |
dependencies.on('get', add) // setup listener | |
dependencies.lastResult = func() // run the function | |
dependencies.off('get', add) // remove listener | |
dependencies.checking = false // no longer checking dependencies | |
return deps | |
} | |
extend(dependencies, Events) | |
o_O.dependencies = dependencies | |
// returns a function from some text | |
o_O.expression = function(text) { | |
o_O.expression.last = text // remember the last case useful for debugging syntax errors | |
return new Function('__p', 'with(__p || this) { return (' + text + '); } ') | |
} | |
o_O.nextFrame = (function() { | |
var fns = [] | |
, timeout; | |
function run() { | |
while(fns.length) fns.shift()() | |
timeout = null | |
} | |
var self = this | |
// shim layer with setTimeout fallback | |
var onNextFrame = self.requestAnimationFrame || | |
self.webkitRequestAnimationFrame || | |
self.mozRequestAnimationFrame || | |
self.oRequestAnimationFrame || | |
self.msRequestAnimationFrame || | |
function( callback ) { | |
self.setTimeout(callback, 1000 / 60); | |
}; | |
return function(fn) { | |
fns.push(fn) | |
timeout = timeout || onNextFrame(run) | |
} | |
})(); | |
/* | |
* Contains information about current binding | |
*/ | |
o_O.current = {} | |
/* | |
* el: dom element | |
* binding: name of the binding rule | |
* expr: text containing binding specification | |
* context: the object that we're binding | |
*/ | |
var bindingid = 0 | |
allbindings = {} | |
o_O.bindRuleToElement = function(method, expressionString, context, $el, source, root) { | |
var id = ++bindingid | |
allbindings[id] = [method, expressionString, context, $el] | |
var expression = o_O.expression(expressionString) | |
, binding = o_O.createBinding(method) | |
, value // contains the current value of the attribute that emit will use | |
, el = $el[0] | |
o_O.el = root // the original el that o_O.bind was called with | |
o_O.node = el | |
// if it's an event event - just emit immediately and we're done | |
el.bindings = el.bindings || [] | |
if(binding.type == 'event') { | |
evaluateExpression() | |
emit() | |
el.bindings.push(function() { | |
delete allbindings[id] | |
}) | |
return | |
} | |
// otherwise we need to calculate our dependencies | |
var deps = dependencies(evaluateExpression) | |
// if we're not two way and it's a function - then really it's a short hand for missing brackets - recalculate | |
if(typeof value == 'function' && binding.type != 'twoway') { | |
var callString = '.call(this)' // value.constructor == o_O ? '()' : '.call(this)' | |
expression = o_O.expression('(' + expressionString + ')' + callString) | |
deps = dependencies(evaluateExpression) | |
} | |
// we should emit immediately | |
emit() | |
// and also everytime a dependency changes - but only once per binding per frame - even if > 1 dependency changes | |
var disabled | |
, first = true | |
function runbinding() { | |
// if(emitting) return // - had to remove this | |
evaluateExpression(); | |
(first || binding.immediate) | |
? emit() | |
: o_O.nextFrame(emit) | |
first = false | |
} | |
runbinding.method = method | |
runbinding.id = id | |
each(deps, function(dep) { | |
dep.on('set', runbinding) | |
}) | |
el.bindings.push(function() { | |
each(deps, function(dep) { | |
dep.off('set', runbinding) | |
}) | |
disabled = true | |
delete allbindings[id] | |
}) | |
function evaluateExpression() { | |
// console.log("evaluateExpression", method, expressionString) | |
value = expression.call(context, source || context) //, o_O.helpers); | |
if(value instanceof String) value = value.toString(); // strange problem | |
} | |
// emit is the function that actually performs the work | |
function emit() { | |
if(disabled) | |
console.error("skipping deleted binding", id, method, expressionString, context) | |
// SHOULD DO SOME REFACTORING HERE !! | |
o_O.el = root // the original el that o_O.bind was called with | |
o_O.node = el // the current node child that we're working with | |
return binding.call(context, value, $el, root) | |
} | |
} | |
/* | |
* Helper function to extract rules from a css like string | |
*/ | |
function parseBindingAttribute(str) { | |
if(!str) return [] | |
var rules = trim(str).split(";"), ret = [], i, bits, binding, param, rule | |
for(var i=0; i <rules.length; i++) { | |
rule = trim(rules[i]) | |
if(!rule) continue // for trailing ; | |
bits = map(trim(rule).split(":"), trim) | |
binding = trim(bits.shift()) | |
param = trim(bits.join(":")) || binding | |
ret.push([binding, param]) | |
} | |
return ret | |
} | |
/* | |
* Public function to bind an object to an element or selector | |
* context: the object to bind | |
* dom: the element or selector | |
* recursing: internal flag to indicate wether it is an internal call | |
*/ | |
o_O.bind = function(context, el, source, recursing, root) { | |
if(!(el instanceof HTMLElement)) | |
el = $(el)[0] | |
var $el = $(el) | |
if(el.bindings) { | |
if(el != root) console.warn('nested bindings. unbinding child first') | |
o_O.unbind(el) | |
} | |
if(!root) { | |
root = el | |
context.el = el | |
$el.data('o_O', context) | |
} | |
var recurse = true | |
, pairs = parseBindingAttribute($el.attr(o_O.bindingAttribute)) | |
, onbindings = [] | |
for(var i=0; i <pairs.length; i++) { | |
var method = pairs[i][0] | |
, expression = pairs[i][1] | |
if(method == 'with' || method == 'foreach' || method == 'if' || method == 'unless') | |
recurse = false | |
if(method == '"class"') | |
method = "class" | |
if(method == 'onbind') | |
onbindings.push([ method, expression, context, $el, source, root ]) | |
else if(method == 'source') | |
source = o_O.expression(expression).call(context) | |
else | |
o_O.bindRuleToElement(method, expression, context, $el, source, root) | |
} | |
if(o_O.removeBindingAttribute) | |
$el.attr(o_O.bindingAttribute,null) | |
if(recurse) | |
$el.children().each(function(i, el) { | |
o_O.bind(context, el, source, true, root) | |
}) | |
for(var i=0; i< onbindings.length; i++) | |
o_O.bindRuleToElement.apply(o_O, onbindings[i]) | |
} | |
/* | |
* Unbinds all dependencies | |
*/ | |
o_O.unbind = function(dom, onlychildren) { | |
if(!dom) return | |
if(!onlychildren && dom.bindings) { | |
var fn | |
while(fn = dom.bindings.shift()) | |
fn() | |
delete dom.bindings | |
} | |
$(dom).children().each(function(i, el) { | |
o_O.unbind(el) | |
}) | |
} | |
/* | |
* Retrieves HTML string from a dom node (that may have been changed due to binding) | |
*/ | |
function getTemplate($el) { | |
var template = $el.data('o_O:template') | |
if(template == null) { | |
template = $el.html() | |
$el.html('') | |
// $el.attr(o_O.bindingAttribute, null) | |
$el.data('o_O:template', template) | |
} | |
return template | |
} | |
// o_O.helpers = { | |
// // converts a mouse event callback to a callback with the mouse position relative to the target | |
// position: function(fn) { | |
// return function(e) { | |
// var el = e.currentTarget | |
// var o = $(el).offset() | |
// fn.call(this, e.pageX - o.left, e.pageY - o.top, e) | |
// } | |
// } | |
// } | |
/* | |
|_ o._ _|o._ _ _ | |
|_)|| |(_||| |(_|_> | |
_| */ | |
o_O.bindings = { | |
noop: function() {}, | |
call: function(func, $el) { | |
// func.call(this, $el) | |
}, | |
/* | |
* set visibility depenent on val | |
*/ | |
visible: function(val, $el) { | |
val ? $el.show() : $el.hide() | |
}, | |
'if': function(context, $el) { | |
var template = getTemplate($el) | |
var old_context = this | |
o_O.unbind($el, true) | |
if(context) { | |
$el.html(template) | |
$el.children().each(function(i, el) { | |
o_O.bind(old_context, el, null, true) | |
}) | |
} | |
else { | |
$el.html('') | |
} | |
}, | |
unless: function(val, $el) { | |
return o_O.bindings['if'].call(this, !val, $el) | |
}, | |
'with': function(context, $el) { | |
var template = getTemplate($el) | |
o_O.unbind($el, true) | |
if(context) { | |
$el.html(template) | |
$el.children().each(function(i, el) { | |
o_O.bind(context, el, null, true) | |
}) | |
} | |
else { | |
$el.html('') | |
} | |
}, | |
options: function(options, $el) { | |
var isArray = options instanceof Array | |
$.each(options, function(key, value) { | |
var text = isArray ? value : key | |
$el.append($("<option>").attr("value", value).html(text)) | |
}) | |
}, | |
/* | |
* Allows binding of a list of items | |
* list is expected to respond to forEach | |
*/ | |
foreach: function(list, $el) { | |
var template = getTemplate($el) | |
// default renderer if list doesn't specify one | |
function defaultRenderer(item) { | |
$(template).each(function(i, elem) { | |
var $$ = $(elem) | |
$$.appendTo($el) | |
o_O.bind(item, $$) | |
}) | |
} | |
var renderItem = list.renderItem || defaultRenderer | |
, visible = $el.is(":visible") | |
o_O.unbind($el, true) | |
$el.html('') | |
// an experiment to avoid reflows from multiple inserts | |
if(visible) $el.hide() | |
list.forEach(function(item, index) { | |
renderItem.call(list, item, $el, index) | |
}) | |
if(visible) $el.show() | |
list.onbind && list.onbind($el) | |
}, | |
log: function(context, $el) { | |
console.log('o_O', context, $el, this) | |
} | |
} | |
o_O.bindings['if'].immediate = true | |
o_O.bindings['with'].immediate = true | |
o_O.bindings['unless'].immediate = true | |
o_O.bindings['foreach'].immediate = true | |
/* general purpose | |
* `call: fn` will run once - useful for intialization | |
*/ | |
o_O.bindings.onbind = function(func, $el) { | |
func.call(this, $el) | |
} | |
o_O.bindings.onbind.type = 'event' | |
/* Two-way binding to a form element to a property | |
* usage: bind='value: myProperty' | |
* special cases for checkbox | |
*/ | |
function ValueBinding(transform) { | |
function binding(property, $el) { | |
var self = this | |
, changing = false | |
, in_set_handler = false | |
, checkbox = $el.attr('type') == 'checkbox' | |
$el.change(function(e) { | |
changing = true | |
if (!in_set_handler) { | |
var val = checkbox ? !!$(this).attr('checked') : $(this).val() | |
val = transform(val) | |
property.call(self, val, e) | |
} | |
changing = false | |
}) | |
if(property.constructor == o_O) { | |
property.on('set', function(val) { | |
in_set_handler = true | |
val = transform(val) | |
checkbox | |
? $el.attr('checked', val ? 'checked' : null) | |
: $el.val(val) | |
if(!changing) $el.change() | |
in_set_handler = false | |
}) | |
// set without forcing an update | |
var val = property() | |
val = transform(val) | |
checkbox | |
? $el.attr('checked', val ? 'checked' : null) | |
: $el.val(val) | |
} | |
} | |
binding.type = 'twoway' | |
return binding | |
} | |
o_O.bindings.value = ValueBinding(function(x) { return x }) | |
o_O.bindings['numeric-value'] = ValueBinding(function(x) { return Number(x) }) | |
/* | |
event bindings - i.e. user events | |
*/ | |
o_O.bindingTypes = { focus:'event', blur:'event', change:'event', submit:'event', | |
keypress:'event', keydown:'event', keyup:'event', click:'event', | |
mouseover:'event', mouseout:'event', mousedown:'event', mousemove:'event', | |
mouseup:'event', dblclick:'event', load:'event' } | |
function __bind(func, context) { | |
return function() { | |
return func.apply(context, arguments) | |
} | |
} | |
o_O.createBinding = function(method) { | |
var binding | |
if( o_O.bindings[method] ) { | |
binding = o_O.bindings[method] | |
} | |
else if( method in $.prototype ) { | |
binding = function(value, $el) { | |
typeof value == 'function' && (value = __bind(value, this)) | |
return $el[method](value) | |
} | |
} | |
else { | |
binding = function(value, $el) { | |
return $el.attr(method, value); // set DOM attribute | |
} | |
} | |
binding.type = binding.type || o_O.bindingTypes[method] | |
return binding | |
} | |
/* ___ __ __ _ _ | |
___ / _ \ | \/ | ___ __| | ___| | | |
/ _ \ | | | || |\/| |/ _ \ / _` |/ _ \ | | |
| (_) | | |_| || | | | (_) | (_| | __/ | | |
\___/___\___(_)_| |_|\___/ \__,_|\___|_| | |
|_____| | |
Model with observable properties, subclasses, evented | |
*/ | |
function model(o, proto) { | |
if(!(this instanceof model)) return new model(o, proto) | |
o = o || {} | |
this.properties = [] | |
for(var name in o) { | |
model.addProperty(this, name, o[name]) | |
model.observeProperty(this, name) | |
} | |
var defaults = this.constructor.defaults | |
for(var name in defaults) { | |
if(name in o) continue | |
var val = defaults[name] | |
model.addProperty(this, name, val) | |
model.observeProperty(this, name) | |
} | |
proto && extend(this, proto) | |
this.initialize.call(this, o, proto) | |
this.constructor.emit('create', this, o) | |
} | |
extend(model, Events, { | |
observeProperty: function(model, name) { | |
model[name].on('set', function(val, old) { | |
model.emit('set:' + name, model, val, old) | |
if(val === old) return | |
var x = {} | |
, y = {} | |
x[name] = val | |
y[name] = old | |
model.emit('update', model, x, y) | |
}) | |
}, | |
addProperty: function(model, name, val) { | |
model[name] = o_O(val) | |
model.properties.push(name) | |
}, | |
defaults: {}, | |
extend: function(defaults, protoProps, classProps) { | |
defaults = defaults || {} | |
classProps = classProps || {} | |
classProps.extend = classProps.extend || this.extend | |
var child = inherits(this, protoProps, classProps) | |
child.defaults = defaults | |
child._callbacks = {} | |
return child | |
} | |
}) | |
extend(model.prototype, Events, { | |
toString: function() { | |
return '<'+(this.type ? this.type() : 'model')+'>' | |
}, | |
bindTo: function(el) { | |
o_O.bind(this, el); | |
return this; | |
}, | |
initialize: function(o) {}, | |
update: function(o) { | |
var old = {} | |
for(var key in o) { | |
if (!(key in this)) { | |
model.addProperty(this,key) | |
model.observeProperty(this,key) | |
} | |
old[key] = this[key].value | |
this[key](o[key]) | |
} | |
this.emit('update', this, o, old) | |
}, | |
destroy: function() { | |
this.emit('destroy', this) | |
}, | |
each: function(fn) { | |
for(var i=0; i< this.properties.length; i++) { | |
var prop = this.properties[i] | |
fn.call(this, prop, this[prop]()) | |
} | |
}, | |
toJSON: function() { | |
var json = {} | |
this.each(function(prop, value) { | |
json[prop] = value | |
}) | |
return json | |
}, | |
clone: function() { | |
return model.create(this.toJSON()) | |
} | |
}) | |
o_O.model = model | |
/* ___ | |
___ / _ \ __ _ _ __ _ __ __ _ _ _ | |
/ _ \ | | | |/ _` | '__| '__/ _` | | | | | |
| (_) | | |_| | (_| | | | | | (_| | |_| | | |
\___/___\___(_)__,_|_| |_| \__,_|\__, | | |
|_____| |___/ */ | |
function array( list ) { | |
if(!(this instanceof array)) return new array( list ) | |
var self = this | |
this.items = [] | |
this.count = o_O(0) | |
this.length = 0 | |
this.count.change(function(count) { | |
self.length = count | |
}) | |
if( list ) this.concat( list ) | |
this.initialize() | |
} | |
extend(array, { | |
insert: function (arr, o, index) { | |
arr.count.incr() | |
if(o.on && o.emit) { | |
o.on('all', arr._onevent, arr) | |
o.emit('add', o, arr, index) | |
} else{ | |
arr.emit('add', o, arr, index) | |
} | |
return arr.items.length | |
}, | |
remove: function(arr, o, index) { | |
arr.count.incr(-1) //force re-binding | |
if(o.off && o.emit) { | |
o.emit('remove', o, arr, index) | |
o.off('all', arr._onevent, arr) | |
} else { | |
arr.emit('remove', o, arr, index) | |
} | |
return o | |
}, | |
extend: function(protoProps, classProps) { | |
var child = inherits(this, protoProps, classProps); | |
child.extend = this.extend; | |
return child; | |
} | |
}) | |
extend(array.prototype, Events, { | |
type: 'o_O.array', | |
initialize: function() {}, | |
_onevent : function(ev, o, arr) { | |
if ((ev == 'add' || ev == 'remove') && arr != this) return | |
if (ev == 'destroy') { | |
this.remove(o) | |
} | |
this.emit.apply(this, arguments) | |
}, | |
bindTo: function(el) { | |
o_O.bind(this, el) | |
return this | |
}, | |
indexOf: function(o){ | |
return this.items.indexOf(o) | |
}, | |
filter: function(fn){ | |
return this.items.filter(fn) | |
}, | |
detect: function(fn){ | |
for(var i=0;i<this.items.length; i++) { | |
var it = this.items[i] | |
if(fn(it, i)) return it | |
} | |
}, | |
map: function(fn) { | |
this.count(); // force the dependency | |
var ret = [] | |
, items = [] | |
// lets get all the items first | |
for(var i = 0, len = this.length; i < len; i++) | |
items.push(this.items[i]) | |
for(var i = 0, len = items.length; i < len; i++) { | |
var result = fn.call(this, items[i], i) | |
ret.push(result) | |
} | |
return ret | |
}, | |
concat: function(list) { // different from the a normal concat for [] since it doens't return a new object | |
for(var i=0; i < list.length; i++ ) | |
this.push( list[i] ) | |
return this | |
}, | |
push: function(o) { | |
return this.insert(o, this.length) | |
}, | |
add: function(o) { // only adds if not already in | |
return this.indexOf(o) < 0 && this.push(o) | |
}, | |
unshift: function(o) { | |
return this.insert(o, 0) | |
}, | |
pop: function(){ | |
return this.removeAt(this.length-1) //remove(this, this.items.pop()) | |
}, | |
shift: function(){ | |
return this.removeAt(0) //remove(this, this.items.shift()) | |
}, | |
get: function(index) { | |
return this.items[index] | |
}, | |
first: function() { | |
return this.items[0] | |
}, | |
last: function() { | |
return this.items[this.items.length - 1] | |
}, | |
insert: function(o, index) { | |
if(index < 0 || index > this.count()) return false | |
this.items.splice(index, 0, o) | |
array.insert(this, o, index) | |
return o | |
}, | |
removeAt: function(index) { | |
if(index < 0 || index >= this.count()) return false | |
var o = this.items[index] | |
this.items.splice(index, 1) | |
array.remove(this, o, index) | |
return o | |
}, | |
remove: function(o) { | |
var index = this.indexOf(o) | |
return index < 0 ? false : this.removeAt(index) | |
}, | |
empty: function() { | |
var item | |
, items = [] | |
while(item = this.shift()) | |
items.push(item) | |
return items | |
}, | |
sort: function(fn) { | |
this.concat( this.empty().sort(fn) ) | |
return this | |
}, | |
renderItem: function(item, $el, index) { | |
var $$ = $(getTemplate($el)) | |
, children = $el.children() | |
if(children[0] && children[0].nodeName == 'TBODY') children = $(children[0]).children() | |
var nextElem = children[index] | |
nextElem | |
? $$.insertBefore(nextElem) | |
: $el.append($$) | |
o_O.bind(item, $$) | |
}, | |
onbind: function($el) { | |
var self = this | |
this.on('add', function(item, arr, index) { | |
self.renderItem(item, $el, index) | |
}) | |
this.on('remove', function(item, arr, index) { | |
self.removeElement(item, $el, index) | |
}) | |
this.el = $el[0] | |
}, | |
removeElement: function(item, $el, index) { | |
var el = /*item.el || */$el.children()[index] | |
o_O.unbind(el) | |
$(el).remove() | |
}, | |
toString: function() { | |
return '<' + this.type + ':' + this.length + '>' | |
}, | |
toJSON: function() { | |
return this.map(function(o) { | |
return o.toJSON ? o.toJSON() : o | |
}) | |
}, | |
toArray: function() { | |
return this.map(function(o) { | |
return o | |
}) | |
} | |
}) | |
array.prototype.each = array.prototype.forEach = array.prototype.map | |
o_O.array = array | |
/* * * * * * * * * * | |
* HELPER FUNCTIONS | |
*/ | |
function map(array, fn) { | |
var ret = [] | |
for(var i=0; i<array.length;i++) | |
ret[i] = fn(array[i], i) | |
return ret | |
} | |
function extend(obj) { | |
var args = slice.call(arguments, 1) | |
for(var i=0; i<args.length;i++) { | |
var source = args[i] | |
for (var prop in source) | |
obj[prop] = source[prop] | |
} | |
return obj | |
} | |
function each(array, action) { | |
if(array.forEach) return array.forEach(action) | |
for (var i= 0, n= array.length; i<n; i++) | |
if (i in array) | |
action.call(null, array[i], i, array); | |
} | |
function trim(s) { | |
return s.replace(/^\s+|\s+$/g, '') | |
} | |
function indexOf(array, obj, start) { | |
if(array.indexOf) return array.indexOf(obj, start) | |
for (var i = (start || 0), j = array.length; i < j; i++) { | |
if (array[i] === obj) { return i; } | |
} | |
return -1; | |
} | |
function ctor(){}; | |
function inherits(parent, protoProps, staticProps) { | |
var model = function(a, b) { | |
if(this instanceof model) | |
parent.apply(this, arguments) | |
else | |
return new model(a, b) | |
}; | |
if(protoProps && protoProps.hasOwnProperty('constructor')) | |
model = protoProps.constructor | |
extend(model, parent) | |
ctor.prototype = parent.prototype | |
model.prototype = new ctor() | |
if (protoProps) extend(model.prototype, protoProps); | |
if (staticProps) extend(model, staticProps); | |
model.prototype.constructor = model; | |
model.__super__ = parent.prototype; | |
return model; | |
}; | |
// export and options | |
extend(o_O, { | |
bindingAttribute: 'data-bind', | |
removeBindingAttribute: false, | |
inherits: inherits, | |
extend: extend, | |
Events: Events, | |
VERSION: VERSION | |
}) | |
if(typeof module == 'undefined') | |
window.o_O = o_O | |
else | |
module.exports = o_O | |
}(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment