// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.3';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, val, context) {
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
if (a === void 0) return 1;
if (b === void 0) return -1;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(obj) {
if (!obj) return [];
if (_.isArray(obj)) return slice.call(obj);
if (_.isArguments(obj)) return slice.call(obj);
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
return _.values(obj);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head` and `take`. The **guard** check
// allows it to work with `_.map`.
_.first = _.head = _.take = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var results = [];
// The `isSorted` flag is irrelevant if the array only contains two elements.
if (array.length < 3) isSorted = true;
_.reduce(initial, function (memo, value, index) {
if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
memo.push(value);
results.push(array[index]);
}
return memo;
}, []);
return results;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1), true);
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(null, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
result = func.apply(context, args);
}
whenDone();
throttling = true;
return result;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
if (immediate && !timeout) func.apply(context, args);
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// Return a copy of the object only containing the whitelisted properties.
_.pick = function(obj) {
var result = {};
each(_.flatten(slice.call(arguments, 1)), function(key) {
if (key in obj) result[key] = obj[key];
});
return result;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (obj == null) return true;
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && _.has(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is a given object a finite number?
_.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj);
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Has own property?
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
};
// If the value of the named property is a function then invoke it;
// otherwise, return it.
_.result = function(object, property) {
if (object == null) return null;
var value = object[property];
return _.isFunction(value) ? value.call(object) : value;
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
'\\': '\\',
"'": "'",
'r': '\r',
'n': '\n',
't': '\t',
'u2028': '\u2028',
'u2029': '\u2029'
};
for (var p in escapes) escapes[escapes[p]] = p;
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// Within an interpolation, evaluation, or escaping, remove HTML escaping
// that had been previously added.
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
settings = _.defaults(settings || {}, _.templateSettings);
// Compile the template source, taking care to escape characters that
// cannot be included in a string literal and then unescape them in code
// blocks.
var source = "__p+='" + text
.replace(escaper, function(match) {
return '\\' + escapes[match];
})
.replace(settings.escape || noMatch, function(match, code) {
return "'+\n_.escape(" + unescape(code) + ")+\n'";
})
.replace(settings.interpolate || noMatch, function(match, code) {
return "'+\n(" + unescape(code) + ")+\n'";
})
.replace(settings.evaluate || noMatch, function(match, code) {
return "';\n" + unescape(code) + "\n;__p+='";
}) + "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __p='';" +
"var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" +
source + "return __p;\n";
var render = new Function(settings.variable || 'obj', '_', source);
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for build time
// precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
source + '}';
return template;
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `global`
// on the server).
var root = this;
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
// Create a local reference to slice/splice.
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
var Backbone;
if (typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
var $ = root.jQuery || root.Zepto || root.ender;
// Set the JavaScript library that will be used for DOM manipulation and
// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
// alternate JavaScript library (or a mock library for testing your views
// outside of a browser).
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
// set a `X-Http-Method-Override` header.
Backbone.emulateHTTP = false;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone.emulateJSON = false;
// Backbone.Events
// -----------------
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; trigger`-ing an event fires all callbacks in succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var Events = Backbone.Events = {
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;
},
// 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 `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
if (options && options.collection) this.collection = options.collection;
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
};
// Attach all inheritable methods to the Model prototype.
_.extend(Model.prototype, Events, {
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Return a copy of the model's `attributes` object.
toJSON: function(options) {
return _.clone(this.attributes);
},
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset: function(attr, options) {
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear: function(options) {
(options || (options = {})).unset = true;
return this.set(_.clone(this.attributes), options);
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, value, options) {
var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
options = options ? _.clone(options) : {};
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
var serverAttrs = model.parse(resp, xhr);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
if (options.wait) this.set(current, silentOptions);
return xhr;
},
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
var triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {
if (options.wait) triggerDestroy();
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
options.error = Backbone.wrapError(options.error, model, options);
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
if (!options.wait) triggerDestroy();
return xhr;
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse: function(resp, xhr) {
return resp;
},
// Create a new model with identical attributes to this one.
clone: function() {
return new this.constructor(this.attributes);
},
// A model is new if it has never been saved to the server, and lacks an id.
isNew: function() {
return this.id == null;
},
// Call this method to manually fire a `"change"` event for this model and
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
options || (options = {});
var changing = this._changing;
this._changing = true;
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
return this;
},
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
if (!arguments.length) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous: function(attr) {
if (!arguments.length || !this._previousAttributes) return null;
return this._previousAttributes[attr];
},
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
previousAttributes: function() {
return _.clone(this._previousAttributes);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function() {
return !this.validate(this.attributes);
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false;
}
});
// Backbone.Collection
// -------------------
// Provides a standard collection class for our sets of models, ordered
// or unordered. If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this.length += length;
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
if (this.comparator) this.sort({silent: true});
if (options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue;
options.index = i;
model.trigger('add', model, this, options);
}
return this;
},
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `remove` event for every model removed.
remove: function(models, options) {
var i, l, index, model;
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
for (i = 0, l = models.length; i < l; i++) {
model = this.getByCid(models[i]) || this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byCid[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);
}
return this;
},
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
getByCid: function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort: function(options) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
}
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); });
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
options.error = Backbone.wrapError(options.error, collection, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create: function(model, options) {
var coll = this;
options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
if (!options.wait) coll.add(model, options);
var success = options.success;
options.success = function(nextModel, resp, xhr) {
if (options.wait) coll.add(nextModel, options);
if (success) {
success(nextModel, resp);
} else {
nextModel.trigger('sync', model, resp, options);
}
};
model.save(null, options);
return model;
},
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
parse: function(resp, xhr) {
return resp;
},
// Proxy to _'s chain. Can't be proxied the same way the rest of the
// underscore methods are proxied because it relies on the underscore
// constructor.
chain: function () {
return _(this.models).chain();
},
// Reset all internal state. Called when the collection is reset.
_reset: function(options) {
this.length = 0;
this.models = [];
this._byId = {};
this._byCid = {};
},
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(model, options) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;
options.collection = this;
model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) model = false;
} else if (!model.collection) {
model.collection = this;
}
return model;
},
// Internal method to remove a model's ties to a collection.
_removeReference: function(model) {
if (this == model.collection) {
delete model.collection;
}
model.off('all', this._onModelEvent, this);
},
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
this._byId[model.id] = model;
}
this.trigger.apply(this, arguments);
}
});
// Underscore methods that we want to implement on the Collection.
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
};
});
// Backbone.Router
// -------------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
// ...
// });
//
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function() {
if (!this.routes) return;
var routes = [];
for (var route in this.routes) {
routes.unshift([route, this.routes[route]]);
}
for (var i = 0, l = routes.length; i < l; i++) {
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters: function(route, fragment) {
return route.exec(fragment).slice(1);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
};
// Cached regex for cleaning leading hashes and slashes .
var routeStripper = /^[#\/]/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// Has the history handling already been started?
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
_.extend(History.prototype, Events, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
var search = window.location.search;
if (search) fragment += search;
} else {
fragment = this.getHash();
}
}
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment.replace(routeStripper, '');
},
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start: function(options) {
if (History.started) throw new Error("Backbone.history has already been started");
History.started = true;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this.options = _.extend({}, {root: '/'}, this.options, options);
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
if (!this.options.silent) {
return this.loadUrl();
}
},
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
clearInterval(this._checkUrlInterval);
History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route: function(route, callback) {
this.handlers.unshift({route: route, callback: callback});
},
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(this.getHash());
},
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
return matched;
},
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) {
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag) return;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
this.fragment = frag;
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
window.location.assign(this.options.root + fragment);
}
if (options.trigger) this.loadUrl(fragment);
},
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash: function(location, fragment, replace) {
if (replace) {
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
} else {
location.hash = fragment;
}
}
});
// Backbone.View
// -------------
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// Set up all inheritable **Backbone.View** properties and methods.
_.extend(View.prototype, Events, {
// The default `tagName` of a View's element is `"div"`.
tagName: 'div',
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be prefered to global lookups where possible.
$: function(selector) {
return this.$el.find(selector);
},
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
render: function() {
return this;
},
// Remove this view from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove: function() {
this.$el.remove();
return this;
},
// For small amounts of DOM Elements, where a full-blown template isn't
// needed, use **make** to manufacture elements, one at a time.
//
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
//
make: function(tagName, attributes, content) {
var el = document.createElement(tagName);
if (attributes) $(el).attr(attributes);
if (content) $(el).html(content);
return el;
},
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
},
// Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.bind(eventName, method);
} else {
this.$el.delegate(selector, eventName, method);
}
}
},
// Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents: function() {
this.$el.unbind('.delegateEvents' + this.cid);
},
// Performs the initial configuration of a View with a set of options.
// Keys with special meaning *(model, collection, id, className)*, are
// attached directly to the view.
_configure: function(options) {
if (this.options) options = _.extend({}, this.options, options);
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
},
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() {
if (!this.el) {
var attrs = getValue(this, 'attributes') || {};
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.setElement(this.make(this.tagName, attrs), false);
} else {
this.setElement(this.el, false);
}
}
});
// The self-propagating extend function that Backbone classes use.
var extend = function (protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Set up inheritance for the model, collection, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET'
};
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the
// model in question. By default, makes a RESTful Ajax request
// to the model's `url()`. Some possible customizations could be:
//
// * Use `setTimeout` to batch rapid-fire updates into a single request.
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
// as well as all requests with the body as `application/x-www-form-urlencoded`
// instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
options || (options = {});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = getValue(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}
// Make the request, allowing the user to override any Ajax options.
return $.ajax(_.extend(params, options));
};
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var inherits = function(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// Throw an error when a URL is needed, and none is supplied.
var urlError = function() {
throw new Error('A "url" property or function must be specified');
};
}).call(this);
// Knockout JavaScript library v2.1.0
// (c) Steven Sanderson - http://knockoutjs.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)
(function(window,document,navigator,undefined){
var DEBUG=true;
!function(factory) {
// Support three module loading scenarios
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
// [1] CommonJS/Node.js
var target = module['exports'] || exports; // module.exports is for Node.js
factory(target);
} else if (typeof define === 'function' && define['amd']) {
// [2] AMD anonymous module
define(['exports'], factory);
} else {
// [3] No module loader (plain ");
};
if (jQueryTmplVersion > 0) {
jQuery['tmpl']['tag']['ko_code'] = {
open: "__.push($1 || '');"
};
jQuery['tmpl']['tag']['ko_with'] = {
open: "with($1) {",
close: "} "
};
}
};
ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
// Use this one by default *only if jquery.tmpl is referenced*
var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
})();
});
})(window,document,navigator);
/*
knockback.js 0.16.3
(c) 2011, 2012 Kevin Malakoff.
Knockback.js is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
Dependencies: Knockout.js, Backbone.js, and Underscore.js.
*/
(function() {
return (function(factory) {
// AMD
if (typeof define === 'function' && define.amd) {
return define('knockback', ['underscore', 'backbone', 'knockout'], factory);
}
// CommonJS/NodeJS or No Loader
else {
return factory.call(this);
}
})(function() {// Generated by CoffeeScript 1.3.3
/*
knockback-core.js 0.16.3
(c) 2011, 2012 Kevin Malakoff.
Knockback.js is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
Dependencies: Knockout.js, Backbone.js, and Underscore.js.
*/
var Backbone, EMAIL_REGEXP, KB_TYPE_ARRAY, KB_TYPE_COLLECTION, KB_TYPE_MODEL, KB_TYPE_SIMPLE, KB_TYPE_UNKNOWN, NUMBER_REGEXP, URL_REGEXP, addStatisticsEvent, arraySlice, arraySplice, collapseOptions, kb, ko, legacyWarning, onReady, throwMissing, throwUnexpected, _, _argumentsAddKey, _unwrapModels, _wrappedKey,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
kb = (function() {
function kb() {}
kb.VERSION = '0.16.3';
kb.TYPE_UNKNOWN = 0;
kb.TYPE_SIMPLE = 1;
kb.TYPE_ARRAY = 2;
kb.TYPE_MODEL = 3;
kb.TYPE_COLLECTION = 4;
kb.release = function(obj, pre_release_fn) {
var array, item, view_model, view_models, _i, _j, _len, _len1;
if ((!obj || (obj !== Object(obj))) || ((typeof obj === 'function') && !ko.isObservable(obj)) || obj.__kb_destroyed || ((obj instanceof Backbone.Model) || (obj instanceof Backbone.Collection))) {
return this;
}
if (_.isArray(obj)) {
array = obj.splice(0, obj.length);
for (_i = 0, _len = array.length; _i < _len; _i++) {
item = array[_i];
kb.release(item);
}
return this;
}
obj.__kb_destroyed = true;
!pre_release_fn || pre_release_fn();
if (ko.isObservable(obj) || (typeof obj.dispose === 'function') || (typeof obj.destroy === 'function') || (typeof obj.release === 'function')) {
if (ko.isObservable(obj) && _.isArray(array = obj())) {
if (obj.__kb_is_co || (obj.__kb_is_o && (obj.valueType() === KB_TYPE_COLLECTION))) {
if (obj.destroy) {
obj.destroy();
} else if (obj.dispose) {
obj.dispose();
}
} else if (array.length) {
view_models = array.slice(0);
array.splice(0, array.length);
for (_j = 0, _len1 = view_models.length; _j < _len1; _j++) {
view_model = view_models[_j];
kb.release(view_model);
}
}
} else if (obj.release) {
obj.release();
} else if (obj.destroy) {
obj.destroy();
} else if (obj.dispose) {
obj.dispose();
}
} else {
this.releaseKeys(obj);
}
return this;
};
kb.releaseKeys = function(obj) {
var key, value;
for (key in obj) {
value = obj[key];
(key === '__kb') || kb.release(value, (function() {
return obj[key] = null;
}));
}
return this;
};
kb.releaseOnNodeRemove = function(view_model, node) {
view_model || throwUnexpected(this, 'missing view model');
node || throwUnexpected(this, 'missing node');
return ko.utils.domNodeDisposal.addDisposeCallback(node, function() {
return kb.release(view_model);
});
};
kb.renderTemplate = function(template, view_model, options) {
var el, observable;
if (options == null) {
options = {};
}
el = document.createElement('div');
observable = ko.renderTemplate(template, view_model, options, el, 'replaceChildren');
if (el.children.length === 1) {
el = el.children[0];
}
kb.releaseOnNodeRemove(view_model, el);
observable.dispose();
return el;
};
kb.renderAutoReleasedTemplate = function(template, view_model, options) {
if (options == null) {
options = {};
}
legacyWarning('kb.renderAutoReleasedTemplate', '0.16.3', 'Please use kb.renderTemplate instead');
return this.renderTemplate(template, view_model, options = {});
};
kb.applyBindings = function(view_model, node) {
ko.applyBindings(view_model, node);
return kb.releaseOnNodeRemove(view_model, node);
};
return kb;
})();
this.Knockback = this.kb = kb;
if (typeof exports !== 'undefined') {
module.exports = kb;
}
if (!this._ && (typeof require !== 'undefined')) {
try {
_ = require('lodash');
} catch (e) {
_ = require('underscore');
}
} else {
_ = this._;
}
kb._ = _ = _.hasOwnProperty('_') ? _._ : _;
kb.Backbone = Backbone = !this.Backbone && (typeof require !== 'undefined') ? require('backbone') : this.Backbone;
kb.ko = ko = !this.ko && (typeof require !== 'undefined') ? require('knockout') : this.ko;
throwMissing = function(instance, message) {
throw "" + instance.constructor.name + ": " + message + " is missing";
};
throwUnexpected = function(instance, message) {
throw "" + instance.constructor.name + ": " + message + " is unexpected";
};
legacyWarning = function(identifier, last_version, message) {
var _base;
this._legacy_warnings || (this._legacy_warnings = {});
(_base = this._legacy_warnings)[identifier] || (_base[identifier] = 0);
this._legacy_warnings[identifier]++;
return console.warn("warning: '" + identifier + "' has been deprecated (will be removed in Knockback after " + last_version + "). " + message + ".");
};
arraySplice = Array.prototype.splice;
collapseOptions = function(options) {
var result;
result = _.clone(options);
while (options.options) {
_.defaults(result, options.options);
options = options.options;
}
delete result.options;
return result;
};
KB_TYPE_UNKNOWN = kb.TYPE_UNKNOWN;
KB_TYPE_SIMPLE = kb.TYPE_SIMPLE;
KB_TYPE_ARRAY = kb.TYPE_ARRAY;
KB_TYPE_MODEL = kb.TYPE_MODEL;
KB_TYPE_COLLECTION = kb.TYPE_COLLECTION;
/*
knockback-utils.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.js is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
Dependencies: Knockout.js, Backbone.js, and Underscore.js.
Optional dependency: Backbone.ModelRef.js.
*/
_wrappedKey = function(obj, key, value) {
if (arguments.length === 2) {
if (obj && obj.__kb && obj.__kb.hasOwnProperty(key)) {
return obj.__kb[key];
} else {
return void 0;
}
}
obj || throwUnexpected(this, "no obj for wrapping " + key);
obj.__kb || (obj.__kb = {});
obj.__kb[key] = value;
return value;
};
_argumentsAddKey = function(args, key) {
arraySplice.call(args, 1, 0, key);
return args;
};
_unwrapModels = function(obj) {
var key, result, value;
if (!obj) {
return obj;
} else if (obj.__kb) {
if ('object' in obj.__kb) {
return obj.__kb.object;
} else {
return obj;
}
} else if (_.isArray(obj)) {
return _.map(obj, function(test) {
return _unwrapModels(test);
});
} else if (_.isObject(obj) && !ko.isObservable(obj) && !_.isDate(obj) && !_.isString(obj)) {
result = {};
for (key in obj) {
value = obj[key];
result[key] = _unwrapModels(value);
}
return result;
} else {
return obj;
}
};
kb.utils = (function() {
function utils() {}
utils.wrappedObservable = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'observable'));
};
utils.wrappedObject = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'object'));
};
utils.wrappedModel = function(obj, value) {
if (arguments.length === 1) {
value = _wrappedKey(obj, 'object');
if (_.isUndefined(value)) {
return obj;
} else {
return value;
}
} else {
return _wrappedKey(obj, 'object', value);
}
};
utils.wrappedStore = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'store'));
};
utils.wrappedStoreIsOwned = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'store_is_owned'));
};
utils.wrappedFactory = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'factory'));
};
utils.wrappedModelWatcher = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'model_watcher'));
};
utils.wrappedModelWatcherIsOwned = function(obj, value) {
return _wrappedKey.apply(this, _argumentsAddKey(arguments, 'model_watcher_is_owned'));
};
utils.wrappedDestroy = function(obj) {
var __kb;
if (!obj.__kb) {
return;
}
if (obj.__kb.model_watcher) {
obj.__kb.model_watcher.releaseCallbacks(obj);
}
__kb = obj.__kb;
obj.__kb = null;
if (__kb.observable) {
__kb.observable.destroy = __kb.observable.release = null;
this.wrappedDestroy(__kb.observable);
__kb.observable = null;
}
__kb.factory = null;
if (__kb.model_watcher_is_owned) {
__kb.model_watcher.destroy();
}
__kb.model_watcher = null;
if (__kb.store_is_owned) {
__kb.store.destroy();
}
return __kb.store = null;
};
utils.valueType = function(observable) {
if (!observable) {
return KB_TYPE_UNKNOWN;
}
if (observable.__kb_is_o) {
return observable.valueType();
}
if (observable.__kb_is_co || (observable instanceof Backbone.Collection)) {
return KB_TYPE_COLLECTION;
}
if ((observable instanceof kb.ViewModel) || (observable instanceof Backbone.Model)) {
return KB_TYPE_MODEL;
}
if (_.isArray(observable)) {
return KB_TYPE_ARRAY;
}
return KB_TYPE_SIMPLE;
};
utils.pathJoin = function(path1, path2) {
return (path1 ? (path1[path1.length - 1] !== '.' ? "" + path1 + "." : path1) : '') + path2;
};
utils.optionsPathJoin = function(options, path) {
return _.defaults({
path: this.pathJoin(options.path, path)
}, options);
};
utils.inferCreator = function(value, factory, path, owner, key) {
var creator, relation;
if (factory) {
creator = factory.creatorForPath(value, path);
}
if (creator) {
return creator;
}
if (owner && Backbone.RelationalModel && (owner instanceof Backbone.RelationalModel)) {
key = ko.utils.unwrapObservable(key);
relation = _.find(owner.getRelations(), function(test) {
return test.key === key;
});
if (relation) {
if (relation.collectionType || _.isArray(relation.keyContents)) {
return kb.CollectionObservable;
} else {
return kb.ViewModel;
}
}
}
if (!value) {
return null;
}
if (value instanceof Backbone.Model) {
return kb.ViewModel;
}
if (value instanceof Backbone.Collection) {
return kb.CollectionObservable;
}
return null;
};
utils.createFromDefaultCreator = function(obj, options) {
if (obj instanceof Backbone.Model) {
return kb.viewModel(obj, options);
}
if (obj instanceof Backbone.Collection) {
return kb.collectionObservable(obj, options);
}
if (_.isArray(obj)) {
return ko.observableArray(obj);
}
return ko.observable(obj);
};
utils.release = function(obj) {
legacyWarning('kb.utils.release', '0.16.3', 'Please use kb.release instead');
return kb.release(obj);
};
return utils;
})();
/*
knockback_factory.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Factory is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.Factory = (function() {
Factory.useOptionsOrCreate = function(options, obj, owner_path) {
var factory;
if (options.factory && (!options.factories || (options.factories && options.factory.hasPathMappings(options.factories, owner_path)))) {
return kb.utils.wrappedFactory(obj, options.factory);
}
factory = kb.utils.wrappedFactory(obj, new kb.Factory(options.factory));
if (options.factories) {
factory.addPathMappings(options.factories, owner_path);
}
return factory;
};
function Factory(parent_factory) {
this.parent_factory = parent_factory;
this.paths = {};
}
Factory.prototype.hasPath = function(path) {
return this.paths.hasOwnProperty(path) || (this.parent_factory && this.parent_factory.hasPath(path));
};
Factory.prototype.addPathMapping = function(path, create_info) {
return this.paths[path] = create_info;
};
Factory.prototype.addPathMappings = function(factories, owner_path) {
var create_info, path;
for (path in factories) {
create_info = factories[path];
this.paths[kb.utils.pathJoin(owner_path, path)] = create_info;
}
return this;
};
Factory.prototype.hasPathMappings = function(factories, owner_path) {
var all_exist, creator, existing_creator, path;
all_exist = true;
for (path in factories) {
creator = factories[path];
all_exist &= (existing_creator = this.creatorForPath(null, kb.utils.pathJoin(owner_path, path))) && (creator === existing_creator);
}
return all_exist;
};
Factory.prototype.creatorForPath = function(obj, path) {
var creator;
if ((creator = this.paths[path])) {
if (creator.view_model) {
return creator.view_model;
} else {
return creator;
}
}
if (this.parent_factory) {
if ((creator = this.parent_factory.creatorForPath(obj, path))) {
return creator;
}
}
return null;
};
return Factory;
})();
/*
knockback_store.js
(c) 2012 Kevin Malakoff.
Knockback.Store is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.Store = (function() {
Store.useOptionsOrCreate = function(options, obj, observable) {
if (options.store) {
options.store.register(obj, observable, options);
return kb.utils.wrappedStore(observable, options.store);
} else {
kb.utils.wrappedStoreIsOwned(observable, true);
return kb.utils.wrappedStore(observable, new kb.Store());
}
};
function Store() {
this.observable_records = [];
this.replaced_observables = [];
}
Store.prototype.destroy = function() {
var observable, record, _i, _j, _len, _len1, _ref, _ref1;
_ref = this.observable_records;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
record = _ref[_i];
kb.release(record.observable);
}
_ref1 = this.replaced_observables;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
observable = _ref1[_j];
kb.release(observable);
}
this.observable_records = null;
return this.replaced_observables = null;
};
Store.prototype.register = function(obj, observable, options) {
var creator;
if (!observable) {
return;
}
if (ko.isObservable(observable) || observable.__kb_is_co) {
return;
}
kb.utils.wrappedObject(observable, obj);
if (!obj) {
observable.__kb_null = true;
}
creator = options.creator ? options.creator : (options.path && options.factory ? options.factory.creatorForPath(obj, options.path) : null);
if (!creator) {
creator = observable.constructor;
}
this.observable_records.push({
obj: obj,
observable: observable,
creator: creator
});
return observable;
};
Store.prototype.findIndex = function(obj, creator) {
var index, record, _ref;
if (!obj || (obj instanceof Backbone.Model)) {
_ref = this.observable_records;
for (index in _ref) {
record = _ref[index];
if (!record.observable) {
continue;
}
if (record.observable.__kb_destroyed) {
record.obj = null;
record.observable = null;
continue;
}
if ((!obj && !record.observable.__kb_null) || (obj && (record.observable.__kb_null || (record.obj !== obj)))) {
continue;
} else if ((record.creator === creator) || (record.creator.create && (record.creator.create === creator.create))) {
return index;
}
}
}
return -1;
};
Store.prototype.find = function(obj, creator) {
var index;
if ((index = this.findIndex(obj, creator)) < 0) {
return null;
} else {
return this.observable_records[index].observable;
}
};
Store.prototype.isRegistered = function(observable) {
var record, _i, _len, _ref;
_ref = this.observable_records;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
record = _ref[_i];
if (record.observable === observable) {
return true;
}
}
return false;
};
Store.prototype.findOrCreate = function(obj, options) {
var creator, observable;
options.store = this;
options.creator || (options.creator = kb.utils.inferCreator(obj, options.factory, options.path));
if (!options.creator && (obj instanceof Backbone.Model)) {
options.creator = kv.ViewModel;
}
creator = options.creator;
if (!creator) {
return kb.utils.createFromDefaultCreator(obj, options);
} else if (creator.models_only) {
return obj;
}
if (creator) {
observable = this.find(obj, creator);
}
if (observable) {
return observable;
}
if (creator.create) {
observable = creator.create(obj, options);
} else {
observable = new creator(obj, options);
}
observable || (observable = ko.observable(null));
if (!ko.isObservable(observable)) {
this.isRegistered(observable) || this.register(obj, observable, options);
}
return observable;
};
Store.prototype.findOrReplace = function(obj, creator, observable) {
var index, record;
obj || raiseUnexpected('obj missing');
if ((index = this.findIndex(obj, creator)) < 0) {
return this.register(obj, observable, {
creator: creator
});
} else {
record = this.observable_records[index];
(kb.utils.wrappedObject(record.observable) === obj) || throwUnexpected(this, 'different object');
if (record.observable !== observable) {
(record.observable.constructor === observable.constructor) || throwUnexpected(this, 'replacing different type');
this.replaced_observables.push(record.observable);
record.observable = observable;
}
return observable;
}
};
return Store;
})();
/*
knockback_model_watcher.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Observable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
addStatisticsEvent = function(model, event_name, info) {
return !kb.statistics || kb.statistics.addModelEvent({
name: event_name,
model: model,
key: info.key,
path: info.path
});
};
kb.ModelWatcher = (function() {
ModelWatcher.useOptionsOrCreate = function(options, model, obj, callback_options) {
if (options.model_watcher) {
if (!(options.model_watcher.model() === model || (options.model_watcher.model_ref === model))) {
throwUnexpected(this, 'model not matching');
}
return kb.utils.wrappedModelWatcher(obj, options.model_watcher).registerCallbacks(obj, callback_options);
} else {
kb.utils.wrappedModelWatcherIsOwned(obj, true);
return kb.utils.wrappedModelWatcher(obj, new kb.ModelWatcher(model)).registerCallbacks(obj, callback_options);
}
};
function ModelWatcher(model, obj, callback_options) {
this._onModelUnloaded = __bind(this._onModelUnloaded, this);
this._onModelLoaded = __bind(this._onModelLoaded, this);
this.__kb || (this.__kb = {});
this.__kb.callbacks = {};
this.__kb._onModelLoaded = _.bind(this._onModelLoaded, this);
this.__kb._onModelUnloaded = _.bind(this._onModelUnloaded, this);
if (callback_options) {
this.registerCallbacks(obj, callback_options);
}
if (model) {
this.model(model);
} else {
this.m = null;
}
}
ModelWatcher.prototype.destroy = function() {
this.model(null);
this.__kb.callbacks = null;
return kb.utils.wrappedDestroy(this);
};
ModelWatcher.prototype.model = function(new_model) {
var callbacks, event_name, info, list, previous_model, _i, _len, _ref;
if ((arguments.length === 0) || (this.m === new_model)) {
return this.m;
}
if (this.model_ref) {
this.model_ref.unbind('loaded', this.__kb._onModelLoaded);
this.model_ref.unbind('unloaded', this.__kb._onModelUnloaded);
this.model_ref.release();
this.model_ref = null;
}
if (Backbone.ModelRef && (new_model instanceof Backbone.ModelRef)) {
this.model_ref = new_model;
this.model_ref.retain();
this.model_ref.bind('loaded', this.__kb._onModelLoaded);
this.model_ref.bind('unloaded', this.__kb._onModelUnloaded);
new_model = this.model_ref.model();
} else {
delete this.model_ref;
}
previous_model = this.m;
this.m = new_model;
_ref = this.__kb.callbacks;
for (event_name in _ref) {
callbacks = _ref[event_name];
if (previous_model) {
previous_model.unbind(event_name, callbacks.fn);
}
if (new_model) {
new_model.bind(event_name, callbacks.fn);
}
list = callbacks.list;
for (_i = 0, _len = list.length; _i < _len; _i++) {
info = list[_i];
if (info.model) {
info.model(new_model);
}
}
}
return new_model;
};
ModelWatcher.prototype.registerCallbacks = function(obj, callback_info) {
var callbacks, event_name, info, list;
obj || throwMissing(this, 'obj');
callback_info || throwMissing(this, 'info');
event_name = callback_info.event_name ? callback_info.event_name : 'change';
callbacks = this.__kb.callbacks[event_name];
if (!callbacks) {
list = [];
callbacks = {
list: list,
fn: function(model) {
var info, _i, _len;
for (_i = 0, _len = list.length; _i < _len; _i++) {
info = list[_i];
if (info.update && !info.rel_fn) {
if (model && info.key && (model.hasChanged && !model.hasChanged(ko.utils.unwrapObservable(info.key)))) {
continue;
}
!kb.statistics || addStatisticsEvent(model, event_name, info);
info.update();
}
}
return null;
}
};
this.__kb.callbacks[event_name] = callbacks;
if (this.m) {
this.m.bind(event_name, callbacks.fn);
}
}
info = _.defaults({
obj: obj
}, callback_info);
callbacks.list.push(info);
if (this.m) {
if (Backbone.RelationalModel && (this.m instanceof Backbone.RelationalModel)) {
this._modelBindRelatationalInfo(event_name, info);
}
info.model(this.m) && info.model;
}
return this;
};
ModelWatcher.prototype.releaseCallbacks = function(obj) {
var callbacks, event_name, index, info, _ref, _ref1;
if (!this.__kb.callbacks) {
return;
}
_ref = this.__kb.callbacks;
for (event_name in _ref) {
callbacks = _ref[event_name];
_ref1 = callbacks.list;
for (index in _ref1) {
info = _ref1[index];
if (info.obj === obj) {
callbacks.list.splice(index, 1);
if (info.rel_fn) {
this._modelUnbindRelatationalInfo(event_name, info);
}
if (info.model) {
info.model(null);
}
return;
}
}
}
};
ModelWatcher.prototype._onModelLoaded = function(model) {
var callbacks, event_name, info, is_relational, list, _i, _len, _ref;
is_relational = Backbone.RelationalModel && (model instanceof Backbone.RelationalModel);
this.m = model;
_ref = this.__kb.callbacks;
for (event_name in _ref) {
callbacks = _ref[event_name];
model.bind(event_name, callbacks.fn);
list = callbacks.list;
for (_i = 0, _len = list.length; _i < _len; _i++) {
info = list[_i];
if (is_relational) {
this._modelBindRelatationalInfo(event_name, info);
}
if (info.model) {
info.model(model);
}
}
}
return this;
};
ModelWatcher.prototype._onModelUnloaded = function(model) {
var callbacks, event_name, info, list, _i, _len, _ref;
this.m = null;
_ref = this.__kb.callbacks;
for (event_name in _ref) {
callbacks = _ref[event_name];
model.unbind(event_name, callbacks.fn);
list = callbacks.list;
for (_i = 0, _len = list.length; _i < _len; _i++) {
info = list[_i];
if (info.rel_fn) {
this._modelUnbindRelatationalInfo(event_name, info);
}
if (info.model) {
info.model(null);
}
}
}
return this;
};
ModelWatcher.prototype._modelBindRelatationalInfo = function(event_name, info) {
var key, relation;
if ((event_name === 'change') && info.key && info.update) {
key = ko.utils.unwrapObservable(info.key);
relation = _.find(this.m.getRelations(), function(test) {
return test.key === key;
});
if (!relation) {
return;
}
info.rel_fn = function(model) {
!kb.statistics || addStatisticsEvent(model, "" + event_name + " (relational)", info);
return info.update();
};
if (relation.collectionType || _.isArray(relation.keyContents)) {
info.is_collection = true;
this.m.bind("add:" + info.key, info.rel_fn);
this.m.bind("remove:" + info.key, info.rel_fn);
} else {
this.m.bind("update:" + info.key, info.rel_fn);
}
}
return this;
};
ModelWatcher.prototype._modelUnbindRelatationalInfo = function(event_name, info) {
if (!info.rel_fn) {
return;
}
if (info.is_collection) {
this.m.unbind("add:" + info.key, info.rel_fn);
this.m.unbind("remove:" + info.key, info.rel_fn);
} else {
this.m.unbind("update:" + info.key, info.rel_fn);
}
info.rel_fn = null;
return this;
};
return ModelWatcher;
})();
kb.modelObservable = function(model, observable) {
return new kb.ModelWatcher(model, observable);
};
/*
knockback-observable.js
(c) 2012 Kevin Malakoff.
Knockback.Observable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.Observable = (function() {
function Observable(model, options, vm) {
var create_options, model_watcher, observable,
_this = this;
this.vm = vm;
options || throwMissing(this, 'options');
this.vm || (this.vm = {});
if (_.isString(options) || ko.isObservable(options)) {
create_options = this.create_options = {
key: options
};
} else {
create_options = this.create_options = collapseOptions(options);
}
this.key = create_options.key;
delete create_options.key;
this.key || throwMissing(this, 'key');
!create_options.args || (this.args = create_options.args, delete create_options.args);
!create_options.read || (this.read = create_options.read, delete create_options.read);
!create_options.write || (this.write = create_options.write, delete create_options.write);
model_watcher = create_options.model_watcher;
delete create_options.model_watcher;
this.vo = ko.observable(null);
observable = kb.utils.wrappedObservable(this, ko.dependentObservable({
read: function() {
var arg, args, new_value, _i, _len, _ref;
args = [ko.utils.unwrapObservable(_this.key)];
if (_this.args) {
if (_.isArray(_this.args)) {
_ref = _this.args;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
arg = _ref[_i];
args.push(ko.utils.unwrapObservable(arg));
}
} else {
args.push(ko.utils.unwrapObservable(_this.args));
}
}
if (_this.m) {
new_value = _this.read ? _this.read.apply(_this.vm, args) : _this.m.get.apply(_this.m, args);
_this.update(new_value);
}
return ko.utils.unwrapObservable(_this.vo());
},
write: function(new_value) {
var arg, args, set_info, unwrapped_new_value, _i, _len, _ref;
unwrapped_new_value = _unwrapModels(new_value);
set_info = {};
set_info[ko.utils.unwrapObservable(_this.key)] = unwrapped_new_value;
args = _this.write ? [unwrapped_new_value] : [set_info];
if (_this.args) {
if (_.isArray(_this.args)) {
_ref = _this.args;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
arg = _ref[_i];
args.push(ko.utils.unwrapObservable(arg));
}
} else {
args.push(ko.utils.unwrapObservable(_this.args));
}
}
if (_this.m) {
if (_this.write) {
_this.write.apply(_this.vm, args);
} else {
_this.m.set.apply(_this.m, args);
}
}
return _this.update(new_value);
},
owner: this.vm
}));
observable.__kb_is_o = true;
create_options.store = kb.utils.wrappedStore(observable, create_options.store);
create_options.path = kb.utils.pathJoin(create_options.path, this.key);
if (create_options.factories && ((typeof create_options.factories === 'function') || create_options.factories.create)) {
create_options.factory = kb.utils.wrappedFactory(observable, new kb.Factory(create_options.factory));
create_options.factory.addPathMapping(create_options.path, create_options.factories);
} else {
create_options.factory = kb.Factory.useOptionsOrCreate(create_options, observable, create_options.path);
}
delete create_options.factories;
observable.value = _.bind(this.value, this);
observable.valueType = _.bind(this.valueType, this);
observable.destroy = _.bind(this.destroy, this);
kb.ModelWatcher.useOptionsOrCreate({
model_watcher: model_watcher
}, model, this, {
model: _.bind(this.model, this),
update: _.bind(this.update, this),
key: this.key,
path: create_options.path
});
this.__kb_value || this.update();
if (kb.LocalizedObservable && create_options.localizer) {
observable = new create_options.localizer(observable);
delete create_options.localizer;
}
if (kb.DefaultObservable && create_options.hasOwnProperty('default')) {
observable = kb.defaultObservable(observable, create_options["default"]);
delete create_options["default"];
}
return observable;
}
Observable.prototype.destroy = function() {
this.__kb_destroyed = true;
kb.release(this.__kb_value);
this.__kb_value = null;
this.vm = null;
this.create_options = null;
return kb.utils.wrappedDestroy(this);
};
Observable.prototype.value = function() {
return this.__kb_value;
};
Observable.prototype.valueType = function() {
var new_value;
new_value = this.m ? this.m.get(this.key) : null;
this.value_type || this._updateValueObservable(new_value);
return this.value_type;
};
Observable.prototype.model = function(new_model) {
if ((arguments.length === 0) || (this.m === new_model)) {
return this.m;
}
this.m = new_model;
return this.__kb_destroyed || this.update();
};
Observable.prototype.update = function(new_value) {
var new_type, value;
if (this.m && !arguments.length) {
new_value = this.m.get(ko.utils.unwrapObservable(this.key));
}
new_value || (new_value = null);
new_type = kb.utils.valueType(new_value);
if (!this.__kb_value || (this.__kb_value.__kb_destroyed || (this.__kb_value.__kb_null && new_value))) {
this.__kb_value = null;
this.value_type = void 0;
}
value = this.__kb_value;
if (_.isUndefined(this.value_type) || (this.value_type !== new_type && new_type !== KB_TYPE_UNKNOWN)) {
if ((this.value_type === KB_TYPE_COLLECTION) && (new_type === KB_TYPE_ARRAY)) {
return value(new_value);
} else {
return this._updateValueObservable(new_value);
}
} else if (this.value_type === KB_TYPE_MODEL) {
if (typeof value.model === 'function') {
if (value.model() !== new_value) {
return value.model(new_value);
}
} else if (kb.utils.wrappedObject(value) !== new_value) {
return this._updateValueObservable(new_value);
}
} else if (this.value_type === KB_TYPE_COLLECTION) {
if (value.collection() !== new_value) {
return value.collection(new_value);
}
} else {
if (value() !== new_value) {
return value(new_value);
}
}
};
Observable.prototype._updateValueObservable = function(new_value) {
var create_options, creator, previous_value, value;
create_options = this.create_options;
create_options.creator = kb.utils.inferCreator(new_value, create_options.factory, create_options.path, this.m, this.key);
this.value_type = KB_TYPE_UNKNOWN;
creator = create_options.creator;
previous_value = this.__kb_value;
this.__kb_value = null;
if (previous_value) {
kb.release(previous_value);
}
if (creator) {
if (create_options.store) {
value = create_options.store.findOrCreate(new_value, create_options);
} else {
if (creator.models_only) {
value = new_value;
this.value_type = KB_TYPE_SIMPLE;
} else if (creator.create) {
value = creator.create(new_value, create_options);
} else {
value = new creator(new_value, create_options);
}
}
} else {
this.value_type = KB_TYPE_SIMPLE;
if (_.isArray(new_value)) {
value = ko.observableArray(new_value);
} else {
value = ko.observable(new_value);
}
}
if (this.value_type === KB_TYPE_UNKNOWN) {
if (!ko.isObservable(value)) {
this.value_type = KB_TYPE_MODEL;
if (typeof value.model !== 'function') {
kb.utils.wrappedObject(value, new_value);
}
} else if (value.__kb_is_co) {
this.value_type = KB_TYPE_COLLECTION;
} else {
this.value_type = KB_TYPE_SIMPLE;
}
}
this.__kb_value = value;
return this.vo(value);
};
return Observable;
})();
kb.observable = function(model, options, view_model) {
return new kb.Observable(model, options, view_model);
};
/*
knockback-view-model.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Observable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.ViewModel = (function() {
ViewModel.extend = Backbone.Model.extend;
function ViewModel(model, options, view_model) {
var attribute_keys, bb_model, keys, mapped_keys, mapping_info, model_watcher, vm_key, _ref;
!model || (model instanceof Backbone.Model) || ((typeof model.get === 'function') && (typeof model.bind === 'function')) || throwUnexpected(this, 'not a model');
options || (options = {});
view_model || (view_model = {});
if (_.isArray(options)) {
options = {
keys: options
};
} else {
options = collapseOptions(options);
}
this.__kb || (this.__kb = {});
this.__kb.vm_keys = {};
this.__kb.model_keys = {};
this.__kb.view_model = _.isUndefined(view_model) ? this : view_model;
!options.internals || (this.__kb.internals = options.internals);
!options.excludes || (this.__kb.excludes = options.excludes);
kb.Store.useOptionsOrCreate(options, model, this);
this.__kb.path = options.path;
kb.Factory.useOptionsOrCreate(options, this, options.path);
model_watcher = kb.utils.wrappedModelWatcher(this, new kb.ModelWatcher(model, this, {
model: _.bind(this.model, this)
}));
if (options.requires && _.isArray(options.requires)) {
keys = _.clone(options.requires);
}
if (this.__kb.internals) {
keys = keys ? _.union(keys, this.__kb.internals) : _.clone(this.__kb.internals);
}
if (options.keys) {
if (_.isArray(options.keys)) {
this.__kb.keys = options.keys;
keys = keys ? _.union(keys, options.keys) : _.clone(options.keys);
} else {
mapped_keys = {};
_ref = options.keys;
for (vm_key in _ref) {
mapping_info = _ref[vm_key];
mapped_keys[_.isString(mapping_info) ? mapping_info : (mapping_info.key ? mapping_info.key : vm_key)] = true;
}
this.__kb.keys = _.keys(mapped_keys);
}
} else {
bb_model = model_watcher.model();
if (bb_model && bb_model.attributes) {
attribute_keys = _.keys(bb_model.attributes);
keys = keys ? _.union(keys, attribute_keys) : attribute_keys;
}
}
if (keys && this.__kb.excludes) {
keys = _.difference(keys, this.__kb.excludes);
}
if (_.isObject(options.keys) && !_.isArray(options.keys)) {
this._mapObservables(model, options.keys);
}
if (_.isObject(options.requires) && !_.isArray(options.requires)) {
this._mapObservables(model, options.requires);
}
!options.mappings || this._mapObservables(model, options.mappings);
!keys || this._createObservables(model, keys);
!kb.statistics || kb.statistics.register('ViewModel', this);
}
ViewModel.prototype.destroy = function() {
var vm_key;
if (this.__kb.view_model !== this) {
for (vm_key in this.__kb.vm_keys) {
this.__kb.view_model[vm_key] = null;
}
}
this.__kb.view_model = null;
kb.releaseKeys(this);
kb.utils.wrappedDestroy(this);
return !kb.statistics || kb.statistics.unregister('ViewModel', this);
};
ViewModel.prototype.shareOptions = function() {
return {
store: kb.utils.wrappedStore(this),
factory: kb.utils.wrappedFactory(this)
};
};
ViewModel.prototype.model = function(new_model) {
var missing, model, model_watcher;
model = kb.utils.wrappedObject(this);
if ((arguments.length === 0) || (model === new_model)) {
return model;
}
if (this.__kb_null) {
!new_model || throwUnexpected(this, 'model set on shared null');
return;
}
kb.utils.wrappedObject(this, new_model);
model_watcher = kb.utils.wrappedModelWatcher(this);
if (!model_watcher) {
return;
}
model_watcher.model(new_model);
if (this.__kb.keys || !new_model || !new_model.attributes) {
return;
}
missing = _.difference(_.keys(new_model.attributes), _.keys(this.__kb.model_keys));
if (missing) {
return this._createObservables(new_model, missing);
}
};
ViewModel.prototype._createObservables = function(model, keys) {
var create_options, key, vm_key, _i, _len;
create_options = {
store: kb.utils.wrappedStore(this),
factory: kb.utils.wrappedFactory(this),
path: this.__kb.path,
model_watcher: kb.utils.wrappedModelWatcher(this)
};
for (_i = 0, _len = keys.length; _i < _len; _i++) {
key = keys[_i];
vm_key = this.__kb.internals && _.contains(this.__kb.internals, key) ? "_" + key : key;
if (this[vm_key]) {
continue;
}
this.__kb.vm_keys[vm_key] = true;
this.__kb.model_keys[key] = true;
create_options.key = key;
this[vm_key] = this.__kb.view_model[vm_key] = kb.observable(model, create_options, this);
}
return this;
};
ViewModel.prototype._mapObservables = function(model, mappings) {
var create_options, mapping_info, vm_key;
create_options = {
store: kb.utils.wrappedStore(this),
factory: kb.utils.wrappedFactory(this),
path: this.__kb.path,
model_watcher: kb.utils.wrappedModelWatcher(this)
};
for (vm_key in mappings) {
mapping_info = mappings[vm_key];
if (this[vm_key]) {
continue;
}
mapping_info = _.isString(mapping_info) ? {
key: mapping_info
} : _.clone(mapping_info);
mapping_info.key || (mapping_info.key = vm_key);
this.__kb.vm_keys[vm_key] = true;
this.__kb.model_keys[mapping_info.key] = true;
this[vm_key] = this.__kb.view_model[vm_key] = kb.observable(model, _.defaults(mapping_info, create_options), this);
}
return this;
};
return ViewModel;
})();
kb.viewModel = function(model, options, view_model) {
return new kb.ViewModel(model, options, view_model);
};
kb.observables = function(model, binding_info, view_model) {
legacyWarning('kb.observables', '0.16.3', 'Please use kb.viewModel instead');
return new kb.ViewModel(model, binding_info, view_model);
};
/*
knockback-collection-observable.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.CollectionObservable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.CollectionObservable = (function() {
CollectionObservable.extend = Backbone.Model.extend;
function CollectionObservable(collection, options) {
var create_options, observable,
_this = this;
!collection || (collection instanceof Backbone.Collection) || throwUnexpected(this, 'not a collection');
options || (options = {});
observable = kb.utils.wrappedObservable(this, ko.observableArray([]));
observable.__kb_is_co = true;
this.in_edit = 0;
this.__kb || (this.__kb = {});
this.__kb._onCollectionChange = _.bind(this._onCollectionChange, this);
options = collapseOptions(options);
if (options.sort_attribute) {
this.sorted_index_fn = ko.observable(this._sortAttributeFn(options.sort_attribute));
} else {
if (options.sorted_index) {
legacyWarning(this, '0.16.3', 'use sorted_index_fn instead');
options.sorted_index_fn = options.sorted_index;
}
this.sorted_index_fn = ko.observable(options.sorted_index_fn);
}
if (options.filters) {
this.filters = ko.observableArray(_.isArray(options.filters) ? options.filters : options.filters ? [options.filters] : void 0);
} else {
this.filters = ko.observableArray([]);
}
create_options = this.create_options = {
store: kb.Store.useOptionsOrCreate(options, collection, observable)
};
this.path = options.path;
create_options.factory = kb.utils.wrappedFactory(observable, this._shareOrCreateFactory(options));
create_options.path = kb.utils.pathJoin(options.path, 'models');
create_options.creator = create_options.factory.creatorForPath(null, create_options.path);
if (create_options.creator) {
this.models_only = create_options.creator.models_only;
}
observable.destroy = _.bind(this.destroy, this);
observable.shareOptions = _.bind(this.shareOptions, this);
observable.collection = _.bind(this.collection, this);
observable.viewModelByModel = _.bind(this.viewModelByModel, this);
observable.sortedIndex = _.bind(this.sortedIndex, this);
observable.sortAttribute = _.bind(this.sortAttribute, this);
observable.hasViewModels = _.bind(this.hasViewModels, this);
this._col = ko.observable();
this.collection(collection);
this._mapper = ko.dependentObservable(function() {
var filters, model, models, sorted_index_fn, view_model, view_models, _i, _len;
if (_this.in_edit) {
return;
}
observable = kb.utils.wrappedObservable(_this);
collection = _this._col();
if (collection) {
models = collection.models;
}
sorted_index_fn = _this.sorted_index_fn();
filters = _this.filters();
if (!models || (collection.models.length === 0)) {
view_models = [];
} else {
if (filters.length) {
models = _.filter(models, function(model) {
return !_this._modelIsFiltered(model);
});
}
if (sorted_index_fn) {
view_models = [];
for (_i = 0, _len = models.length; _i < _len; _i++) {
model = models[_i];
view_model = _this._createViewModel(model);
view_models.splice(sorted_index_fn(view_models, view_model), 0, view_model);
}
} else {
if (_this.models_only) {
view_models = filters.length ? models : models.slice();
} else {
view_models = _.map(models, function(model) {
return _this._createViewModel(model);
});
}
}
}
_this.in_edit++;
observable(view_models);
return _this.in_edit--;
});
observable.subscribe(_.bind(this._onObservableArrayChange, this));
!kb.statistics || kb.statistics.register('CollectionObservable', this);
return observable;
}
CollectionObservable.prototype.destroy = function() {
var array, collection, observable;
observable = kb.utils.wrappedObservable(this);
collection = this._col();
if (collection) {
collection.unbind('all', this.__kb._onCollectionChange);
array = observable();
array.splice(0, array.length);
}
kb.release(this.filters);
this.filters = this._col = this.sorted_index_fn = this._mapper = this.create_options = null;
kb.utils.wrappedDestroy(this);
return !kb.statistics || kb.statistics.unregister('CollectionObservable', this);
};
CollectionObservable.prototype.shareOptions = function() {
var observable;
observable = kb.utils.wrappedObservable(this);
return {
store: kb.utils.wrappedStore(observable),
factory: kb.utils.wrappedFactory(observable)
};
};
CollectionObservable.prototype.collection = function(collection) {
var observable, previous_collection;
observable = kb.utils.wrappedObservable(this);
previous_collection = this._col();
if ((arguments.length === 0) || (collection === previous_collection)) {
observable();
return previous_collection;
}
if (previous_collection) {
previous_collection.unbind('all', this.__kb._onCollectionChange);
}
if (collection) {
collection.bind('all', this.__kb._onCollectionChange);
}
this._col(collection);
return collection;
};
CollectionObservable.prototype.filters = function(filters) {
if (filters) {
return this.filters(_.isArray(filters) ? filters : [filters]);
} else {
return this.filters([]);
}
};
CollectionObservable.prototype.sortedIndex = function(sorted_index_fn) {
return this.sorted_index_fn(sorted_index_fn);
};
CollectionObservable.prototype.sortAttribute = function(sort_attribute) {
return this.sorted_index_fn(sort_attribute ? this._sortAttributeFn(sort_attribute) : null);
};
CollectionObservable.prototype.viewModelByModel = function(model) {
var id_attribute;
if (this.models_only) {
return null;
}
id_attribute = model.hasOwnProperty(model.idAttribute) ? model.idAttribute : 'cid';
return _.find(kb.utils.wrappedObservable(this)(), function(test) {
return test.__kb.object[id_attribute] === model[id_attribute];
});
};
CollectionObservable.prototype.hasViewModels = function() {
return !this.models_only;
};
CollectionObservable.prototype._shareOrCreateFactory = function(options) {
var absolute_models_path, existing_creator, factories, factory;
absolute_models_path = kb.utils.pathJoin(options.path, 'models');
factories = options.factories;
if ((factory = options.factory)) {
if ((existing_creator = factory.creatorForPath(null, absolute_models_path)) && (!factories || (factories['models'] === existing_creator))) {
if (!factories) {
return factory;
}
if (factory.hasPathMappings(factories, options.path)) {
return factory;
}
}
}
factory = new kb.Factory(options.factory);
if (factories) {
factory.addPathMappings(factories, options.path);
}
if (!factory.creatorForPath(null, absolute_models_path)) {
if (options.hasOwnProperty('models_only')) {
if (options.models_only) {
factory.addPathMapping(absolute_models_path, {
models_only: true
});
} else {
factory.addPathMapping(absolute_models_path, kb.ViewModel);
}
} else if (options.view_model) {
factory.addPathMapping(absolute_models_path, options.view_model);
} else if (options.create) {
factory.addPathMapping(absolute_models_path, {
create: options.create
});
} else {
factory.addPathMapping(absolute_models_path, kb.ViewModel);
}
}
return factory;
};
CollectionObservable.prototype._onCollectionChange = function(event, arg) {
var add_index, collection, observable, sorted_index_fn, view_model;
if (this.in_edit) {
return;
}
switch (event) {
case 'reset':
case 'resort':
if (event === 'resort' && !this.sorted_index_fn()) {
return;
}
return this._col.notifySubscribers(this._col());
case 'new':
case 'add':
if (this._modelIsFiltered(arg)) {
return;
}
observable = kb.utils.wrappedObservable(this);
collection = this._col();
view_model = this._createViewModel(arg);
if ((sorted_index_fn = this.sorted_index_fn())) {
add_index = sorted_index_fn(observable(), view_model);
} else {
add_index = collection.indexOf(arg);
}
this.in_edit++;
observable.splice(add_index, 0, view_model);
return this.in_edit--;
case 'remove':
case 'destroy':
return this._onModelRemove(arg);
case 'change':
if (this._modelIsFiltered(arg)) {
return this._onModelRemove(arg);
} else if (this.sorted_index_fn()) {
return this._onModelResort(arg);
}
}
};
CollectionObservable.prototype._onModelRemove = function(model) {
var observable, view_model;
view_model = this.models_only ? model : this.viewModelByModel(model);
if (!view_model) {
return;
}
observable = kb.utils.wrappedObservable(this);
this.in_edit++;
observable.remove(view_model);
return this.in_edit--;
};
CollectionObservable.prototype._onModelResort = function(model) {
var new_index, observable, previous_index, sorted_index_fn, sorted_view_models, view_model;
observable = kb.utils.wrappedObservable(this);
view_model = this.models_only ? model : this.viewModelByModel(model);
previous_index = observable.indexOf(view_model);
if ((sorted_index_fn = this.sorted_index_fn())) {
sorted_view_models = _.clone(observable());
sorted_view_models.splice(previous_index, 1);
new_index = sorted_index_fn(sorted_view_models, view_model);
} else {
new_index = this._col().indexOf(model);
}
if (previous_index === new_index) {
return;
}
this.in_edit++;
observable.splice(previous_index, 1);
observable.splice(new_index, 0, view_model);
return this.in_edit--;
};
CollectionObservable.prototype._onObservableArrayChange = function(values) {
var collection, has_view_model, models, observable, value, _i, _j, _len, _len1,
_this = this;
if (this.in_edit) {
return;
}
observable = kb.utils.wrappedObservable(this);
collection = this._col();
if (!collection) {
return;
}
if (!this.models_only) {
for (_i = 0, _len = values.length; _i < _len; _i++) {
value = values[_i];
if (value && !(value instanceof Backbone.Model)) {
has_view_model = true;
break;
}
}
if (has_view_model) {
for (_j = 0, _len1 = values.length; _j < _len1; _j++) {
value = values[_j];
this.create_options.store.findOrReplace(kb.utils.wrappedObject(value), this.create_options.creator, value);
}
}
}
models = _.map(values, function(test) {
return kb.utils.wrappedModel(test);
});
if (this.filters().length) {
models = _.filter(models, function(model) {
return !_this._modelIsFiltered(model);
});
}
this.in_edit++;
collection.reset(models);
this.in_edit--;
return this;
};
CollectionObservable.prototype._sortAttributeFn = function(sort_attribute) {
if (this.models_only) {
return function(models, model) {
var attribute_name;
attribute_name = ko.utils.unwrapObservable(sort_attribute);
return _.sortedIndex(models, model, function(test) {
return test.get(attribute_name);
});
};
} else {
return function(view_models, model) {
var attribute_name;
attribute_name = ko.utils.unwrapObservable(sort_attribute);
return _.sortedIndex(view_models, model, function(test) {
return kb.utils.wrappedModel(test).get(attribute_name);
});
};
}
};
CollectionObservable.prototype._createViewModel = function(model) {
if (this.models_only) {
return model;
} else {
return this.create_options.store.findOrCreate(model, this.create_options);
}
};
CollectionObservable.prototype._modelIsFiltered = function(model) {
var filter, filters, _i, _len;
filters = this.filters();
for (_i = 0, _len = filters.length; _i < _len; _i++) {
filter = filters[_i];
filter = ko.utils.unwrapObservable(filter);
if (((typeof filter === 'function') && filter(model)) || (model && (model.id === filter))) {
return true;
}
}
return false;
};
return CollectionObservable;
})();
kb.collectionObservable = function(collection, options) {
return new kb.CollectionObservable(collection, options);
};
kb.sortedIndexWrapAttr = kb.siwa = function(attribute_name, wrapper_constructor) {
return function(models, model) {
return _.sortedIndex(models, model, function(test) {
return new wrapper_constructor(kb.utils.wrappedModel(test).get(attribute_name));
});
};
};
/*
knockback-inject.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Inject is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
ko.bindingHandlers['inject'] = {
'init': function(element, value_accessor, all_bindings_accessor, view_model) {
var data, wrapper;
data = ko.utils.unwrapObservable(value_accessor());
wrapper = ko.dependentObservable(function() {
var key, value, _results;
if (_.isFunction(data)) {
return data(view_model, element, value_accessor, all_bindings_accessor);
} else if (_.isObject(data)) {
_results = [];
for (key in data) {
value = data[key];
if (_.isObject(value) && value.resolve && _.isFunction(value.resolve)) {
_results.push(view_model[key] = value.resolve(view_model, element, value_accessor, all_bindings_accessor));
} else {
_results.push(view_model[key] = value);
}
}
return _results;
}
});
return wrapper.dispose();
}
};
kb.injectApps = function(root) {
var app, apps, getAppElements, options, _i, _len;
apps = [];
getAppElements = function(el) {
var attr, child_el, _i, _len, _ref;
if (!el.__kb_injected) {
if (el.attributes && (attr = _.find(el.attributes, function(attr) {
return attr.name === 'kb-app';
}))) {
el.__kb_injected = true;
apps.push([el, attr]);
}
}
_ref = el.childNodes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child_el = _ref[_i];
getAppElements(child_el);
}
};
getAppElements(root || document);
for (_i = 0, _len = apps.length; _i < _len; _i++) {
app = apps[_i];
if (app[1].value) {
options = ko.utils.buildEvalWithinScopeFunction("{" + app[1].value + "}", 0)();
}
options || (options = {});
options.view_model || (options.view_model = {});
if (options.beforeBinding) {
options.beforeBinding(options.view_model, app[0], options);
}
kb.applyBindings(options.view_model, app[0], {});
if (options.afterBinding) {
options.afterBinding(options.view_model, app[0], options);
}
}
};
(onReady = function() {
if (!document.body) {
return setTimeout(onReady, 1);
}
kb.injectApps();
})();
/*
knockback_default_wrapper.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.DefaultObservable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.DefaultObservable = (function() {
function DefaultObservable(target_observable, dv) {
var observable,
_this = this;
this.dv = dv;
observable = kb.utils.wrappedObservable(this, ko.dependentObservable({
read: function() {
var current_target;
if ((current_target = ko.utils.unwrapObservable(target_observable()))) {
return current_target;
} else {
return ko.utils.unwrapObservable(_this.dv);
}
},
write: function(value) {
return target_observable(value);
}
}));
observable.destroy = _.bind(this.destroy, this);
observable.setToDefault = _.bind(this.setToDefault, this);
return observable;
}
DefaultObservable.prototype.destroy = function() {
return kb.utils.wrappedDestroy(this);
};
DefaultObservable.prototype.setToDefault = function() {
return kb.utils.wrappedObservable(this)(this.dv);
};
return DefaultObservable;
})();
kb.defaultObservable = function(target, default_value) {
return new kb.DefaultObservable(target, default_value);
};
kb.defaultWrapper = function(target, default_value) {
legacyWarning('ko.defaultWrapper', '0.16.3', 'Please use kb.defaultObservable instead');
return new kb.DefaultObservable(target, default_value);
};
/*
knockback-formatted-observable.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.FormattedObservable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
arraySlice = Array.prototype.slice;
kb.toFormattedString = function(format) {
var arg, args, index, parameter_index, result, value;
result = format.slice();
args = arraySlice.call(arguments, 1);
for (index in args) {
arg = args[index];
value = ko.utils.unwrapObservable(arg);
value || (value = '');
parameter_index = format.indexOf("\{" + index + "\}");
while (parameter_index >= 0) {
result = result.replace("{" + index + "}", value);
parameter_index = format.indexOf("\{" + index + "\}", parameter_index + 1);
}
}
return result;
};
kb.parseFormattedString = function(string, format) {
var count, format_indices_to_matched_indices, index, match_index, matches, parameter_count, parameter_index, positions, regex, regex_string, result, results, sorted_positions;
regex_string = format.slice();
index = 0;
parameter_count = 0;
positions = {};
while (regex_string.search("\\{" + index + "\\}") >= 0) {
parameter_index = format.indexOf("\{" + index + "\}");
while (parameter_index >= 0) {
regex_string = regex_string.replace("\{" + index + "\}", '(.*)');
positions[parameter_index] = index;
parameter_count++;
parameter_index = format.indexOf("\{" + index + "\}", parameter_index + 1);
}
index++;
}
count = index;
regex = new RegExp(regex_string);
matches = regex.exec(string);
if (matches) {
matches.shift();
}
if (!matches || (matches.length !== parameter_count)) {
result = [];
while (count-- > 0) {
result.push('');
}
return result;
}
sorted_positions = _.sortBy(_.keys(positions), function(parameter_index, format_index) {
return parseInt(parameter_index, 10);
});
format_indices_to_matched_indices = {};
for (match_index in sorted_positions) {
parameter_index = sorted_positions[match_index];
index = positions[parameter_index];
if (format_indices_to_matched_indices.hasOwnProperty(index)) {
continue;
}
format_indices_to_matched_indices[index] = match_index;
}
results = [];
index = 0;
while (index < count) {
results.push(matches[format_indices_to_matched_indices[index]]);
index++;
}
return results;
};
kb.FormattedObservable = (function() {
function FormattedObservable(format, args) {
var observable, observable_args;
if (_.isArray(args)) {
format = format;
observable_args = args;
} else {
observable_args = arraySlice.call(arguments, 1);
}
observable = kb.utils.wrappedObservable(this, ko.dependentObservable({
read: function() {
var arg, _i, _len;
args = [ko.utils.unwrapObservable(format)];
for (_i = 0, _len = observable_args.length; _i < _len; _i++) {
arg = observable_args[_i];
args.push(ko.utils.unwrapObservable(arg));
}
return kb.toFormattedString.apply(null, args);
},
write: function(value) {
var index, matches, max_count;
matches = kb.parseFormattedString(value, ko.utils.unwrapObservable(format));
max_count = Math.min(observable_args.length, matches.length);
index = 0;
while (index < max_count) {
observable_args[index](matches[index]);
index++;
}
return this;
}
}));
return observable;
}
FormattedObservable.prototype.destroy = function() {
return kb.utils.wrappedDestroy(this);
};
return FormattedObservable;
})();
kb.formattedObservable = function(format, args) {
return new kb.FormattedObservable(format, arraySlice.call(arguments, 1));
};
/*
knockback-localized-observable.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.LocalizedObservable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.LocalizedObservable = (function() {
LocalizedObservable.extend = Backbone.Model.extend;
function LocalizedObservable(value, options, vm) {
var observable,
_this = this;
this.value = value;
this.vm = vm;
options || (options = {});
this.vm || (this.vm = {});
this.read || throwMissing(this, 'read');
kb.locale_manager || throwMissing(this, 'kb.locale_manager');
this.__kb || (this.__kb = {});
this.__kb._onLocaleChange = _.bind(this._onLocaleChange, this);
this.__kb._onChange = options.onChange;
if (this.value) {
value = ko.utils.unwrapObservable(this.value);
}
this.vo = ko.observable(!value ? null : this.read(value, null));
observable = kb.utils.wrappedObservable(this, ko.dependentObservable({
read: function() {
if (_this.value) {
ko.utils.unwrapObservable(_this.value);
}
_this.vo();
return _this.read(ko.utils.unwrapObservable(_this.value));
},
write: function(value) {
_this.write || throwUnexpected(_this, 'writing to read-only');
_this.write(value, ko.utils.unwrapObservable(_this.value));
_this.vo(value);
if (_this.__kb._onChange) {
return _this.__kb._onChange(value);
}
},
owner: this.vm
}));
observable.destroy = _.bind(this.destroy, this);
observable.observedValue = _.bind(this.observedValue, this);
observable.resetToCurrent = _.bind(this.resetToCurrent, this);
kb.locale_manager.bind('change', this.__kb._onLocaleChange);
if (options.hasOwnProperty('default')) {
observable = kb.DefaultObservable && ko.defaultObservable(observable, options["default"]);
}
return observable;
}
LocalizedObservable.prototype.destroy = function() {
kb.locale_manager.unbind('change', this.__kb._onLocaleChange);
this.vm = null;
return kb.utils.wrappedDestroy(this);
};
LocalizedObservable.prototype.resetToCurrent = function() {
var current_value, observable;
observable = kb.utils.wrappedObservable(this);
current_value = this.value ? this.read(ko.utils.unwrapObservable(this.value)) : null;
if (observable() === current_value) {
return;
}
return observable(current_value);
};
LocalizedObservable.prototype.observedValue = function(value) {
if (arguments.length === 0) {
return this.value;
}
this.value = value;
this._onLocaleChange();
return this;
};
LocalizedObservable.prototype._onLocaleChange = function() {
var value;
value = this.read(ko.utils.unwrapObservable(this.value));
this.vo(value);
if (this.__kb._onChange) {
return this.__kb._onChange(value);
}
};
return LocalizedObservable;
})();
kb.localizedObservable = function(value, options, view_model) {
return new kb.LocalizedObservable(value, options, view_model);
};
/*
knockback-triggered-observable.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Observable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
kb.TriggeredObservable = (function() {
function TriggeredObservable(model, event_name) {
var observable,
_this = this;
this.event_name = event_name;
model || throwMissing(this, 'model');
this.event_name || throwMissing(this, 'event_name');
this.vo = ko.observable();
observable = kb.utils.wrappedObservable(this, ko.dependentObservable(function() {
return _this.vo();
}));
observable.destroy = _.bind(this.destroy, this);
kb.utils.wrappedModelWatcher(this, new kb.ModelWatcher(model, this, {
model: _.bind(this.model, this),
update: _.bind(this.update, this),
event_name: this.event_name
}));
return observable;
}
TriggeredObservable.prototype.destroy = function() {
return kb.utils.wrappedDestroy(this);
};
TriggeredObservable.prototype.model = function(new_model) {
if ((arguments.length === 0) || (this.m === new_model)) {
return this.m;
}
if ((this.m = new_model)) {
return this.update();
}
};
TriggeredObservable.prototype.update = function() {
if (!this.m) {
return;
}
if (this.vo() !== this.m) {
return this.vo(this.m);
} else {
return this.vo.valueHasMutated();
}
};
return TriggeredObservable;
})();
kb.triggeredObservable = function(model, event_name) {
return new kb.TriggeredObservable(model, event_name);
};
/*
knockback-validators.js
(c) 2011, 2012 Kevin Malakoff.
Knockback.Observable is freely distributable under the MIT license.
See the following for full license details:
https://github.com/kmalakoff/knockback/blob/master/LICENSE
*/
URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
kb.validators = {
required: function(value) {
return !value;
},
url: function(value) {
return !URL_REGEXP.test(value);
},
email: function(value) {
return !EMAIL_REGEXP.test(value);
},
number: function(value) {
return !NUMBER_REGEXP.test(value);
}
};
kb.valueValidator = function(value, checks) {
_.isArray(checks) || (checks = [checks]);
return ko.dependentObservable(function() {
var check, current_value, results, validator, _i, _len;
results = {
invalid: false
};
current_value = ko.utils.unwrapObservable(value);
for (_i = 0, _len = checks.length; _i < _len; _i++) {
check = checks[_i];
validator = kb.validators[check];
if (!validator) {
continue;
}
results[check] = validator(current_value);
results.invalid |= results[check];
}
results.valid = !results.invalid;
return results;
});
};
kb.inputValidator = function(view_model, el, value_accessor) {
var $input_el, bindings, checks, input_name, options, result, skip_attach;
$input_el = $(el);
if ((input_name = $input_el.attr('name')) && !_.isString(input_name)) {
input_name = null;
}
skip_attach = value_accessor && value_accessor.skip_attach;
if (!(bindings = $input_el.attr('data-bind'))) {
return null;
}
options = ko.utils.buildEvalWithinScopeFunction("{" + bindings + "}", 1)([view_model]);
if (!(options && options.value)) {
return null;
}
checks = [];
switch ($input_el.attr('type')) {
case 'url':
checks.push('url');
break;
case 'email':
checks.push('email');
break;
case 'number':
checks.push('number');
}
if ($input_el.attr('required')) {
checks.push('required');
}
result = kb.valueValidator(options.value, checks);
if (input_name && !skip_attach) {
view_model["$" + input_name] = result;
}
return result;
};
kb.formValidator = function(view_model, el) {
var $root_el, form_name, input_el, name, results, validator, validators, _i, _len, _ref;
results = {};
validators = [];
$root_el = $(el);
if ((form_name = $root_el.attr('name')) && !_.isString(form_name)) {
form_name = null;
}
_ref = $root_el.find('input');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
input_el = _ref[_i];
if (!(name = $(input_el).attr('name'))) {
continue;
}
validator = kb.inputValidator(view_model, input_el, form_name ? {
skip_attach: true
} : null);
!validator || validators.push(results[name] = validator);
}
results.valid = ko.dependentObservable(function() {
var valid, _j, _len1;
valid = true;
for (_j = 0, _len1 = validators.length; _j < _len1; _j++) {
validator = validators[_j];
valid &= validator().valid;
}
return valid;
});
results.invalid = ko.dependentObservable(function() {
return !results.valid();
});
if (form_name) {
view_model["$" + form_name] = results;
}
return results;
};
; return kb;});
}).call(this);