Comparing version 0.2.12 to 0.3.1
{ | ||
"name": "temple", | ||
"version": "0.2.11", | ||
"version": "0.3.0-alpha", | ||
"description": "A modern JavaScript view framework.", | ||
@@ -29,2 +29,2 @@ "repo": "BeneathTheInk/Temple", | ||
] | ||
} | ||
} |
{ | ||
"name": "temple", | ||
"version": "0.2.11", | ||
"version": "0.3.0-alpha", | ||
"description": "A modern JavaScript view framework.", | ||
@@ -25,2 +25,2 @@ "repository": "BeneathTheInk/Temple", | ||
] | ||
} | ||
} |
464
lib/deps.js
@@ -0,1 +1,4 @@ | ||
// Copy of https://github.com/meteor/meteor/commits/e78861b7d0dbb60e5e2bf59bab2cb06ce6596c04/packages/deps/deps.js | ||
// (c) 2011-2014 Meteor Development Group | ||
////////////////////////////////////////////////// | ||
@@ -5,4 +8,3 @@ // Package docs at http://docs.meteor.com/#deps // | ||
var Deps = | ||
module.exports = {}; | ||
var Deps = module.exports = {}; | ||
@@ -20,14 +22,2 @@ // http://docs.meteor.com/#deps_active | ||
// _assign is like _.extend or the upcoming Object.assign. | ||
// Copy src's own, enumerable properties onto tgt and return | ||
// tgt. | ||
var _hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var _assign = function (tgt, src) { | ||
for (var k in src) { | ||
if (_hasOwnProperty.call(src, k)) | ||
tgt[k] = src[k]; | ||
} | ||
return tgt; | ||
}; | ||
var _debugFunc = function () { | ||
@@ -50,13 +40,17 @@ // lazy evaluation because `Meteor` does not exist right away | ||
// Like `Meteor._noYieldsAllowed(function () { f(comp); })` but shorter, | ||
// and doesn't clutter the stack with an extra frame on the client, | ||
// where `_noYieldsAllowed` is a no-op. `f` may be a computation | ||
// function or an onInvalidate callback. | ||
var callWithNoYieldsAllowed = function (f, comp) { | ||
// Takes a function `f`, and wraps it in a `Meteor._noYieldsAllowed` | ||
// block if we are running on the server. On the client, returns the | ||
// original function (since `Meteor._noYieldsAllowed` is a | ||
// no-op). This has the benefit of not adding an unnecessary stack | ||
// frame on the client. | ||
var withNoYieldsAllowed = function (f) { | ||
if ((typeof Meteor === 'undefined') || Meteor.isClient) { | ||
f(comp); | ||
return f; | ||
} else { | ||
Meteor._noYieldsAllowed(function () { | ||
f(comp); | ||
}); | ||
return function () { | ||
var args = arguments; | ||
Meteor._noYieldsAllowed(function () { | ||
f.apply(null, args); | ||
}); | ||
}; | ||
} | ||
@@ -88,3 +82,3 @@ }; | ||
if (! willFlush) { | ||
requestAnimationFrame(Deps.flush); | ||
setTimeout(Deps.flush, 0); | ||
willFlush = true; | ||
@@ -101,3 +95,3 @@ } | ||
// | ||
Deps.Computation = function (f, parent) { | ||
Deps.Computation = function (f, parent, ctx) { | ||
if (! constructingComputation) | ||
@@ -125,2 +119,3 @@ throw new Error( | ||
self._func = f; | ||
self._context = ctx || this; | ||
self._recomputing = false; | ||
@@ -139,91 +134,89 @@ | ||
_assign(Deps.Computation.prototype, { | ||
// http://docs.meteor.com/#computation_oninvalidate | ||
Deps.Computation.prototype.onInvalidate = function (f, ctx) { | ||
var self = this; | ||
// http://docs.meteor.com/#computation_oninvalidate | ||
onInvalidate: function (f) { | ||
var self = this; | ||
if (typeof f !== 'function') | ||
throw new Error("onInvalidate requires a function"); | ||
if (typeof f !== 'function') | ||
throw new Error("onInvalidate requires a function"); | ||
if (self.invalidated) { | ||
Deps.nonreactive(function () { | ||
withNoYieldsAllowed(f).call(ctx || self._context, self); | ||
}); | ||
} else { | ||
f._context = ctx; | ||
self._onInvalidateCallbacks.push(f); | ||
} | ||
}; | ||
if (self.invalidated) { | ||
Deps.nonreactive(function () { | ||
callWithNoYieldsAllowed(f, self); | ||
}); | ||
} else { | ||
self._onInvalidateCallbacks.push(f); | ||
// http://docs.meteor.com/#computation_invalidate | ||
Deps.Computation.prototype.invalidate = function () { | ||
var self = this; | ||
if (! self.invalidated) { | ||
// if we're currently in _recompute(), don't enqueue | ||
// ourselves, since we'll rerun immediately anyway. | ||
if (! self._recomputing && ! self.stopped) { | ||
requireFlush(); | ||
pendingComputations.push(this); | ||
} | ||
}, | ||
// http://docs.meteor.com/#computation_invalidate | ||
invalidate: function () { | ||
var self = this; | ||
if (! self.invalidated) { | ||
// if we're currently in _recompute(), don't enqueue | ||
// ourselves, since we'll rerun immediately anyway. | ||
if (! self._recomputing && ! self.stopped) { | ||
requireFlush(); | ||
pendingComputations.push(this); | ||
} | ||
self.invalidated = true; | ||
self.invalidated = true; | ||
// callbacks can't add callbacks, because | ||
// self.invalidated === true. | ||
for(var i = 0, f; f = self._onInvalidateCallbacks[i]; i++) { | ||
Deps.nonreactive(function () { | ||
callWithNoYieldsAllowed(f, self); | ||
}); | ||
} | ||
self._onInvalidateCallbacks = []; | ||
// callbacks can't add callbacks, because | ||
// self.invalidated === true. | ||
for(var i = 0, f; f = self._onInvalidateCallbacks[i]; i++) { | ||
Deps.nonreactive(function () { | ||
withNoYieldsAllowed(f).call(f._context || self._context, self); | ||
}); | ||
} | ||
}, | ||
self._onInvalidateCallbacks = []; | ||
} | ||
}; | ||
// http://docs.meteor.com/#computation_stop | ||
stop: function () { | ||
if (! this.stopped) { | ||
this.stopped = true; | ||
this.invalidate(); | ||
} | ||
}, | ||
// http://docs.meteor.com/#computation_stop | ||
Deps.Computation.prototype.stop = function () { | ||
if (! this.stopped) { | ||
this.stopped = true; | ||
this.invalidate(); | ||
} | ||
}; | ||
_compute: function () { | ||
var self = this; | ||
self.invalidated = false; | ||
Deps.Computation.prototype._compute = function () { | ||
var self = this; | ||
self.invalidated = false; | ||
var previous = Deps.currentComputation; | ||
setCurrentComputation(self); | ||
var previousInCompute = inCompute; | ||
inCompute = true; | ||
try { | ||
callWithNoYieldsAllowed(self._func, self); | ||
} finally { | ||
setCurrentComputation(previous); | ||
inCompute = false; | ||
} | ||
}, | ||
var previous = Deps.currentComputation; | ||
setCurrentComputation(self); | ||
var previousInCompute = inCompute; | ||
inCompute = true; | ||
try { | ||
withNoYieldsAllowed(self._func).call(self._context, self); | ||
} finally { | ||
setCurrentComputation(previous); | ||
inCompute = false; | ||
} | ||
}; | ||
_recompute: function () { | ||
var self = this; | ||
Deps.Computation.prototype._recompute = function () { | ||
var self = this; | ||
self._recomputing = true; | ||
try { | ||
while (self.invalidated && ! self.stopped) { | ||
try { | ||
self._compute(); | ||
} catch (e) { | ||
_throwOrLog("recompute", e); | ||
} | ||
// If _compute() invalidated us, we run again immediately. | ||
// A computation that invalidates itself indefinitely is an | ||
// infinite loop, of course. | ||
// | ||
// We could put an iteration counter here and catch run-away | ||
// loops. | ||
self._recomputing = true; | ||
try { | ||
while (self.invalidated && ! self.stopped) { | ||
try { | ||
self._compute(); | ||
} catch (e) { | ||
_throwOrLog("recompute", e); | ||
} | ||
} finally { | ||
self._recomputing = false; | ||
// If _compute() invalidated us, we run again immediately. | ||
// A computation that invalidates itself indefinitely is an | ||
// infinite loop, of course. | ||
// | ||
// We could put an iteration counter here and catch run-away | ||
// loops. | ||
} | ||
} finally { | ||
self._recomputing = false; | ||
} | ||
}); | ||
}; | ||
@@ -237,155 +230,160 @@ // | ||
_assign(Deps.Dependency.prototype, { | ||
// http://docs.meteor.com/#dependency_depend | ||
// | ||
// Adds `computation` to this set if it is not already | ||
// present. Returns true if `computation` is a new member of the set. | ||
// If no argument, defaults to currentComputation, or does nothing | ||
// if there is no currentComputation. | ||
depend: function (computation) { | ||
if (! computation) { | ||
if (! Deps.active) | ||
return false; | ||
// http://docs.meteor.com/#dependency_depend | ||
// | ||
// Adds `computation` to this set if it is not already | ||
// present. Returns true if `computation` is a new member of the set. | ||
// If no argument, defaults to currentComputation, or does nothing | ||
// if there is no currentComputation. | ||
Deps.Dependency.prototype.depend = function (computation) { | ||
if (! computation) { | ||
if (! Deps.active) | ||
return false; | ||
computation = Deps.currentComputation; | ||
} | ||
var self = this; | ||
var id = computation._id; | ||
if (! (id in self._dependentsById)) { | ||
self._dependentsById[id] = computation; | ||
computation.onInvalidate(function () { | ||
delete self._dependentsById[id]; | ||
}); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
computation = Deps.currentComputation; | ||
} | ||
var self = this; | ||
var id = computation._id; | ||
if (! (id in self._dependentsById)) { | ||
self._dependentsById[id] = computation; | ||
computation.onInvalidate(function () { | ||
delete self._dependentsById[id]; | ||
}); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
// http://docs.meteor.com/#dependency_changed | ||
changed: function () { | ||
var self = this; | ||
for (var id in self._dependentsById) | ||
self._dependentsById[id].invalidate(); | ||
}, | ||
// http://docs.meteor.com/#dependency_changed | ||
Deps.Dependency.prototype.changed = function () { | ||
var self = this; | ||
for (var id in self._dependentsById) | ||
self._dependentsById[id].invalidate(); | ||
}; | ||
// http://docs.meteor.com/#dependency_hasdependents | ||
hasDependents: function () { | ||
var self = this; | ||
for(var id in self._dependentsById) | ||
return true; | ||
return false; | ||
} | ||
}); | ||
// http://docs.meteor.com/#dependency_hasdependents | ||
Deps.Dependency.prototype.hasDependents = function () { | ||
var self = this; | ||
for(var id in self._dependentsById) | ||
return true; | ||
return false; | ||
}; | ||
_assign(Deps, { | ||
// http://docs.meteor.com/#deps_flush | ||
flush: function (_opts) { | ||
// XXX What part of the comment below is still true? (We no longer | ||
// have Spark) | ||
// | ||
// Nested flush could plausibly happen if, say, a flush causes | ||
// DOM mutation, which causes a "blur" event, which runs an | ||
// app event handler that calls Deps.flush. At the moment | ||
// Spark blocks event handlers during DOM mutation anyway, | ||
// because the LiveRange tree isn't valid. And we don't have | ||
// any useful notion of a nested flush. | ||
// | ||
// https://app.asana.com/0/159908330244/385138233856 | ||
if (inFlush) | ||
throw new Error("Can't call Deps.flush while flushing"); | ||
// http://docs.meteor.com/#deps_flush | ||
Deps.flush = function (_opts) { | ||
// XXX What part of the comment below is still true? (We no longer | ||
// have Spark) | ||
// | ||
// Nested flush could plausibly happen if, say, a flush causes | ||
// DOM mutation, which causes a "blur" event, which runs an | ||
// app event handler that calls Deps.flush. At the moment | ||
// Spark blocks event handlers during DOM mutation anyway, | ||
// because the LiveRange tree isn't valid. And we don't have | ||
// any useful notion of a nested flush. | ||
// | ||
// https://app.asana.com/0/159908330244/385138233856 | ||
if (inFlush) | ||
throw new Error("Can't call Deps.flush while flushing"); | ||
if (inCompute) | ||
throw new Error("Can't flush inside Deps.autorun"); | ||
if (inCompute) | ||
throw new Error("Can't flush inside Deps.autorun"); | ||
inFlush = true; | ||
willFlush = true; | ||
throwFirstError = !! (_opts && _opts._throwFirstError); | ||
inFlush = true; | ||
willFlush = true; | ||
throwFirstError = !! (_opts && _opts._throwFirstError); | ||
var finishedTry = false; | ||
try { | ||
while (pendingComputations.length || | ||
afterFlushCallbacks.length) { | ||
var finishedTry = false; | ||
try { | ||
while (pendingComputations.length || | ||
afterFlushCallbacks.length) { | ||
// recompute all pending computations | ||
while (pendingComputations.length) { | ||
var comp = pendingComputations.shift(); | ||
comp._recompute(); | ||
} | ||
// recompute all pending computations | ||
while (pendingComputations.length) { | ||
var comp = pendingComputations.shift(); | ||
comp._recompute(); | ||
} | ||
if (afterFlushCallbacks.length) { | ||
// call one afterFlush callback, which may | ||
// invalidate more computations | ||
var func = afterFlushCallbacks.shift(); | ||
try { | ||
func(); | ||
} catch (e) { | ||
_throwOrLog("afterFlush function", e); | ||
} | ||
if (afterFlushCallbacks.length) { | ||
// call one afterFlush callback, which may | ||
// invalidate more computations | ||
var func = afterFlushCallbacks.shift(); | ||
try { | ||
func.call(func._context); | ||
} catch (e) { | ||
_throwOrLog("afterFlush function", e); | ||
} | ||
} | ||
finishedTry = true; | ||
} finally { | ||
if (! finishedTry) { | ||
// we're erroring | ||
inFlush = false; // needed before calling `Deps.flush()` again | ||
Deps.flush({_throwFirstError: false}); // finish flushing | ||
} | ||
willFlush = false; | ||
inFlush = false; | ||
} | ||
}, | ||
finishedTry = true; | ||
} finally { | ||
if (! finishedTry) { | ||
// we're erroring | ||
inFlush = false; // needed before calling `Deps.flush()` again | ||
Deps.flush({_throwFirstError: false}); // finish flushing | ||
} | ||
willFlush = false; | ||
inFlush = false; | ||
} | ||
}; | ||
// http://docs.meteor.com/#deps_autorun | ||
// | ||
// Run f(). Record its dependencies. Rerun it whenever the | ||
// dependencies change. | ||
// | ||
// Returns a new Computation, which is also passed to f. | ||
// | ||
// Links the computation to the current computation | ||
// so that it is stopped if the current computation is invalidated. | ||
autorun: function (f) { | ||
if (typeof f !== 'function') | ||
throw new Error('Deps.autorun requires a function argument'); | ||
// http://docs.meteor.com/#deps_autorun | ||
// | ||
// Run f(). Record its dependencies. Rerun it whenever the | ||
// dependencies change. | ||
// | ||
// Returns a new Computation, which is also passed to f. | ||
// | ||
// Links the computation to the current computation | ||
// so that it is stopped if the current computation is invalidated. | ||
Deps.autorun = function (f, ctx) { | ||
if (typeof f !== 'function') | ||
throw new Error('Deps.autorun requires a function argument'); | ||
constructingComputation = true; | ||
var c = new Deps.Computation(f, Deps.currentComputation); | ||
constructingComputation = true; | ||
var c = new Deps.Computation(f, Deps.currentComputation, ctx); | ||
if (Deps.active) | ||
Deps.onInvalidate(function () { | ||
c.stop(); | ||
}); | ||
if (Deps.active) | ||
Deps.onInvalidate(function () { | ||
c.stop(); | ||
}); | ||
return c; | ||
}, | ||
return c; | ||
}; | ||
// http://docs.meteor.com/#deps_nonreactive | ||
// | ||
// Run `f` with no current computation, returning the return value | ||
// of `f`. Used to turn off reactivity for the duration of `f`, | ||
// so that reactive data sources accessed by `f` will not result in any | ||
// computations being invalidated. | ||
nonreactive: function (f) { | ||
var previous = Deps.currentComputation; | ||
setCurrentComputation(null); | ||
try { | ||
return f(); | ||
} finally { | ||
setCurrentComputation(previous); | ||
} | ||
}, | ||
// http://docs.meteor.com/#deps_nonreactive | ||
// | ||
// Run `f` with no current computation, returning the return value | ||
// of `f`. Used to turn off reactivity for the duration of `f`, | ||
// so that reactive data sources accessed by `f` will not result in any | ||
// computations being invalidated. | ||
Deps.nonreactive = function (f, ctx) { | ||
var previous = Deps.currentComputation; | ||
setCurrentComputation(null); | ||
try { | ||
return f.call(ctx); | ||
} finally { | ||
setCurrentComputation(previous); | ||
} | ||
}; | ||
// http://docs.meteor.com/#deps_oninvalidate | ||
onInvalidate: function (f) { | ||
if (! Deps.active) | ||
throw new Error("Deps.onInvalidate requires a currentComputation"); | ||
// similar to nonreactive but returns a function instead of | ||
// exectuing fn immediately. really just some sugar | ||
Deps.nonreactable = function (f, ctx) { | ||
return function() { | ||
return Deps.nonreactive(f, ctx || this); | ||
} | ||
} | ||
Deps.currentComputation.onInvalidate(f); | ||
}, | ||
// http://docs.meteor.com/#deps_oninvalidate | ||
Deps.onInvalidate = function (f, ctx) { | ||
if (! Deps.active) | ||
throw new Error("Deps.onInvalidate requires a currentComputation"); | ||
// http://docs.meteor.com/#deps_afterflush | ||
afterFlush: function (f) { | ||
afterFlushCallbacks.push(f); | ||
requireFlush(); | ||
} | ||
}); | ||
Deps.currentComputation.onInvalidate(f, ctx); | ||
}; | ||
// http://docs.meteor.com/#deps_afterflush | ||
Deps.afterFlush = function (f, ctx) { | ||
f._context = ctx; | ||
afterFlushCallbacks.push(f); | ||
requireFlush(); | ||
}; |
@@ -1,2 +0,2 @@ | ||
var _ = require("underscore"); | ||
var util = require("./util"); | ||
@@ -12,3 +12,3 @@ // Backbone.Events | ||
// var object = {}; | ||
// _.extend(object, Backbone.Events); | ||
// util.extend(object, Backbone.Events); | ||
// object.on('expand', function(){ alert('expanded'); }); | ||
@@ -34,8 +34,8 @@ // object.trigger('expand'); | ||
var self = this; | ||
var once = _.once(function() { | ||
self.off(name, once); | ||
var fn = once(function() { | ||
self.off(name, fn); | ||
callback.apply(this, arguments); | ||
}); | ||
once._callback = callback; | ||
return this.on(name, once, context); | ||
fn._callback = callback; | ||
return this.on(name, fn, context); | ||
}, | ||
@@ -54,3 +54,3 @@ | ||
} | ||
names = name ? [name] : _.keys(this._events); | ||
names = name ? [name] : Object.keys(this._events); | ||
for (i = 0, l = names.length; i < l; i++) { | ||
@@ -102,3 +102,3 @@ name = names[i]; | ||
obj.off(name, callback, this); | ||
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; | ||
if (remove || isEmpty(obj._events)) delete this._listeningTo[id]; | ||
} | ||
@@ -158,6 +158,6 @@ return this; | ||
// listening to. | ||
_.each(listenMethods, function(implementation, method) { | ||
util.each(listenMethods, function(implementation, method) { | ||
Events[method] = function(obj, name, callback) { | ||
var listeningTo = this._listeningTo || (this._listeningTo = {}); | ||
var id = obj._listenId || (obj._listenId = _.uniqueId('l')); | ||
var id = obj._listenId || (obj._listenId = util.uniqueId('l')); | ||
listeningTo[id] = obj; | ||
@@ -172,2 +172,20 @@ if (!callback && typeof name === 'object') callback = this; | ||
Events.bind = Events.on; | ||
Events.unbind = Events.off; | ||
Events.unbind = Events.off; | ||
function isEmpty(obj) { | ||
if (obj == null) return true; | ||
if (Array.isArray(obj) || typeof obj === "string") return obj.length === 0; | ||
for (var key in obj) if (util.has(obj, key)) return false; | ||
return true; | ||
} | ||
function once(func) { | ||
var ran = false, memo; | ||
return function() { | ||
if (ran) return memo; | ||
ran = true; | ||
memo = func.apply(this, arguments); | ||
func = null; | ||
return memo; | ||
} | ||
} |
468
lib/util.js
@@ -1,17 +0,78 @@ | ||
var _ = require("underscore"); | ||
var toArray = | ||
exports.toArray = function(obj) { | ||
return Array.prototype.slice.call(obj, 0); | ||
} | ||
// tests value as pojo (plain old javascript object) | ||
var isPlainObject = | ||
exports.isPlainObject = function(obj) { | ||
return obj != null && obj.__proto__ === Object.prototype; | ||
var has = | ||
exports.has = function(obj, key) { | ||
return Object.prototype.hasOwnProperty.call(obj, key); | ||
} | ||
// tests obj as a subclass of parent | ||
// here, a class is technically a subclass of itself | ||
exports.isSubClass = function(parent, obj) { | ||
return obj === parent || (obj != null && obj.prototype instanceof parent); | ||
var extend = | ||
exports.extend = function(obj) { | ||
toArray(arguments).slice(1).forEach(function(mixin) { | ||
if (!mixin) return; | ||
for (var key in mixin) { | ||
obj[key] = mixin[key]; | ||
} | ||
}); | ||
return obj; | ||
} | ||
var each = | ||
exports.each = function(obj, iterator, context) { | ||
if (obj == null) return obj; | ||
if (obj.forEach === Array.prototype.forEach) { | ||
obj.forEach(iterator, context); | ||
} else if (obj.length === +obj.length) { | ||
for (var i = 0, length = obj.length; i < length; i++) { | ||
iterator.call(context, obj[i], i, obj); | ||
} | ||
} else { | ||
var keys = Object.keys(obj); | ||
for (var i = 0, length = keys.length; i < length; i++) { | ||
iterator.call(context, obj[keys[i]], keys[i], obj); | ||
} | ||
} | ||
return obj; | ||
} | ||
var flatten = | ||
exports.flatten = function(input, output) { | ||
if (output == null) output = []; | ||
each(input, function(value) { | ||
if (Array.isArray(value)) flatten(value, output); | ||
else output.push(value); | ||
}); | ||
return output; | ||
} | ||
exports.pick = function(obj) { | ||
return flatten(toArray(arguments).slice(1)) | ||
.reduce(function(nobj, key) { | ||
nobj[key] = obj[key]; | ||
return nobj; | ||
}, {}); | ||
} | ||
var isObject = | ||
exports.isObject = function(obj) { | ||
return obj === Object(obj); | ||
} | ||
exports.uniqueId = (function() { | ||
var id = 0; | ||
return function(prefix) { | ||
return (prefix || "") + (++id); | ||
} | ||
})(); | ||
// the subclassing function found in Backbone | ||
var subclass = | ||
exports.subclass = function(protoProps, staticProps) { | ||
@@ -24,3 +85,3 @@ var parent = this; | ||
// by us to simply call the parent's constructor. | ||
if (protoProps && _.has(protoProps, 'constructor')) { | ||
if (protoProps && has(protoProps, 'constructor')) { | ||
child = protoProps.constructor; | ||
@@ -32,3 +93,3 @@ } else { | ||
// Add static properties to the constructor function, if supplied. | ||
_.extend(child, parent, staticProps); | ||
extend(child, parent, staticProps); | ||
@@ -43,3 +104,3 @@ // Set the prototype chain to inherit from `parent`, without calling | ||
// if supplied. | ||
if (protoProps) _.extend(child.prototype, protoProps); | ||
if (protoProps) extend(child.prototype, protoProps); | ||
@@ -53,356 +114,87 @@ // Set a convenience property in case the parent's prototype is needed | ||
// cleans an array of path parts | ||
var sanitizePathParts = | ||
exports.sanitizePathParts = function(parts) { | ||
return parts.filter(function(a) { | ||
return a != null && a !== ""; | ||
}).map(function(a) { | ||
var s = a.toString(); | ||
if (s[0] === ".") s = s.substr(1); | ||
if (s.substr(-1) === ".") s = s.substr(0, s.length - 1); | ||
return s; | ||
}); | ||
exports.isNodeAtDOMPosition = function(node, parent, before) { | ||
return node.parentNode === parent && node.nextSibling === before; | ||
} | ||
// splits a path by period | ||
var splitPath = | ||
exports.splitPath = function(path) { | ||
var parts = _.isArray(path) ? path : _.isString(path) ? path.split(".") : [ path ]; | ||
return sanitizePathParts(parts); | ||
} | ||
var matchesSelector = Element.prototype.matches || | ||
Element.prototype.webkitMatchesSelector || | ||
Element.prototype.mozMatchesSelector || | ||
Element.prototype.msMatchesSelector; | ||
// parses a string path as a dynamic path | ||
var parsePath = | ||
exports.parsePath = function(path) { | ||
return splitPath(path).map(function(part) { | ||
if (part.indexOf("*") > -1 && part !== "**") { | ||
return new RegExp("^" + part.split("*").join("([^\\.]*)") + "$"); | ||
} | ||
return part; | ||
}); | ||
exports.matchesSelector = function(elem, selector) { | ||
return matchesSelector.call(elem, selector) | ||
} | ||
// concats path parts together into a string | ||
var joinPathParts = | ||
exports.joinPathParts = function() { | ||
return sanitizePathParts(_.flatten(_.toArray(arguments))).join("."); | ||
} | ||
var Deps = require("./deps"); | ||
// deeply looks for a value at path in obj | ||
var get = | ||
exports.get = function(obj, parts, getter) { | ||
parts = splitPath(parts); | ||
var defineReactiveProperty = | ||
exports.defineReactiveProperty = function(obj, prop, value, coerce) { | ||
if (!isObject(obj)) throw new Error("Expecting object to define the reactive property on."); | ||
if (typeof prop !== "string") throw new Error("Expecting string for property name."); | ||
// custom getter | ||
if (!_.isFunction(getter)) { | ||
getter = function(obj, path) { return obj[path]; } | ||
if (typeof value === "function" && coerce == null) { | ||
coerce = value; | ||
value = void 0; | ||
} | ||
while (parts.length) { | ||
if (obj == null) return; | ||
obj = getter(obj, parts.shift()); | ||
} | ||
if (typeof coerce !== "function") coerce = function(v) { return v; }; | ||
return obj; | ||
} | ||
// reduces paths so they are unique and short | ||
var findShallowestUniquePaths = | ||
exports.findShallowestUniquePaths = function(paths) { | ||
return paths.reduce(function(m, keys) { | ||
// first check if a shorter or equal path exists | ||
if (m.some(function(k) { | ||
return arrayStartsWith(keys, k); | ||
})) return m; | ||
// next check for any longer paths that need to be removed | ||
m.slice(0).forEach(function(k, index) { | ||
if (arrayStartsWith(k, keys)) m.splice(index, 1); | ||
// runs the coercion function non-reactively to prevent infinite loops | ||
function process(v) { | ||
return Deps.nonreactive(function() { | ||
return coerce.call(obj, v, prop, obj); | ||
}); | ||
// and lastly add the path to output | ||
m.push(keys); | ||
return m; | ||
}, []); | ||
} | ||
// determines if the values of array match the start of another array | ||
// can be read as: does [a1] start with [a2] | ||
var arrayStartsWith = | ||
exports.arrayStartsWith = function(a1, a2) { | ||
var max = a2.length; | ||
return max <= a1.length && _.isEqual(a2, a1.slice(0, max)); | ||
} | ||
// finds all changed, matching subpaths | ||
var findAllChanges = | ||
exports.findAllChanges = function(chg, parts, onPath, ctx) { | ||
var parts, paths, base, getter, | ||
args = _.toArray(arguments).slice(2); | ||
// clone parts so we don't affect the original | ||
parts = parts.slice(0); | ||
// match the beginning of parts | ||
if (!matchPathStart(chg.keypath, parts)) return; | ||
paths = []; | ||
base = joinPathParts(chg.keypath); | ||
getter = function(obj, path) { | ||
return chg.model.createHandle(obj)("get", path); | ||
} | ||
// generate a list of effected paths | ||
findAllMatchingPaths(chg.model, chg.value, parts, paths); | ||
findAllMatchingPaths(chg.model, chg.oldValue, parts, paths); | ||
paths = findShallowestUniquePaths(paths); | ||
var dep = new Deps.Dependency; | ||
value = process(value); | ||
// fire the callback on each path that changed | ||
paths.forEach(function(keys, index, list) { | ||
var nval, oval; | ||
nval = get(chg.value, keys, getter); | ||
oval = get(chg.oldValue, keys, getter); | ||
if (nval === oval) return; | ||
Object.defineProperty(obj, prop, { | ||
configurable: true, | ||
enumerable: true, | ||
set: function(val) { | ||
val = process(val); | ||
onPath.call(ctx, { | ||
model: chg.model.getModel(keys), | ||
keypath: chg.keypath.concat(keys), | ||
type: changeType(nval, oval), | ||
value: nval, | ||
oldValue: oval | ||
}); | ||
}); | ||
} | ||
if (val !== value) { | ||
value = val; | ||
dep.changed(); | ||
} | ||
// matchs the start of a keypath to a list of match parts | ||
// parts is modified to the remaining segments that were not matched | ||
var matchPathStart = | ||
exports.matchPathStart = function(keys, parts) { | ||
var i, part; | ||
for (i = 0; i < keys.length; i++) { | ||
part = parts.shift(); | ||
if (_.isRegExp(part) && part.test(keys[i])) continue; | ||
if (part === "**") { | ||
// look ahead | ||
if (parts[0] == null || parts[0] !== keys[i + 1]) { | ||
parts.unshift(part); | ||
} | ||
continue; | ||
return value; | ||
}, | ||
get: function() { | ||
dep.depend(); | ||
return value; | ||
} | ||
if (part !== keys[i]) return false; | ||
} | ||
}); | ||
return true; | ||
return obj; | ||
} | ||
// deeply traverses a value in search of all paths that match parts | ||
var findAllMatchingPaths = | ||
exports.findAllMatchingPaths = function(model, value, parts, paths, base) { | ||
if (paths == null) paths = []; | ||
if (base == null) base = []; | ||
if (!parts.length) { | ||
paths.push(base); | ||
return paths; | ||
exports.defineReactiveProperties = function(obj, props, coerce) { | ||
for (var prop in props) { | ||
defineReactiveProperty(obj, prop, props[prop], coerce || false); | ||
} | ||
var handle = model.createHandle(value), | ||
part = parts[0], | ||
rest = parts.slice(1); | ||
if (_.isRegExp(part)) { | ||
handle("keys").forEach(function(k) { | ||
findAllMatchingPaths(model.getModel(k), handle("get", k), rest, paths, base.concat(k)); | ||
}); | ||
} else if (part === "**") { | ||
if (handle("isLeaf")) { | ||
if (!rest.length) paths.push(base); | ||
return paths; | ||
} | ||
handle("keys").forEach(function(k) { | ||
var _rest = rest, | ||
_base = base; | ||
// look ahead | ||
if (rest[0] == null || rest[0] !== k) { | ||
_rest = [part].concat(rest); | ||
_base = base.concat(k); | ||
} | ||
findAllMatchingPaths(model.getModel(k), handle("get", k), _rest, paths, _base); | ||
}); | ||
} else { | ||
findAllMatchingPaths(model.getModel(part), handle("get", part), rest, paths, base.concat(part)); | ||
} | ||
return paths; | ||
return obj; | ||
} | ||
// array write operations | ||
var mutatorMethods = [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ]; | ||
var defineComputedProperty = | ||
exports.defineComputedProperty = function(obj, prop, value) { | ||
if (typeof value !== "function") | ||
throw new Error("Expecting function for computed property value."); | ||
// patches an array so we can listen to write operations | ||
var patchArray = | ||
exports.patchArray = function(arr) { | ||
if (arr._patched) return arr; | ||
var patchedArrayProto = [], | ||
observers = []; | ||
Object.defineProperty(patchedArrayProto, "_patched", { value: true }); | ||
Object.defineProperty(patchedArrayProto, "_observers", { value: [] }); | ||
Object.defineProperty(patchedArrayProto, "observe", { | ||
value: function(fn) { | ||
if (typeof fn !== "function") throw new Error("Expecting function to observe with."); | ||
this._observers.push(fn); | ||
return this; | ||
Object.defineProperty(obj, prop, { | ||
configurable: true, | ||
enumerable: true, | ||
get: function() { | ||
return value.call(obj); | ||
} | ||
}); | ||
} | ||
Object.defineProperty(patchedArrayProto, "stopObserving", { | ||
value: function(fn) { | ||
var index = this._observers.indexOf(fn); | ||
if (index > -1) this._observers.splice(index, 1); | ||
return this; | ||
} | ||
exports.defineComputedProperties = function(obj, props) { | ||
Object.keys(props).forEach(function(key) { | ||
defineComputedProperty(obj, key, props[key]); | ||
}); | ||
mutatorMethods.forEach(function(methodName) { | ||
Object.defineProperty(patchedArrayProto, methodName, { | ||
value: method | ||
}); | ||
function method() { | ||
var spliceEquivalent, summary, start, | ||
original, size, i, index, result; | ||
// push, pop, shift and unshift can all be represented as a splice operation. | ||
// this makes life easier later | ||
spliceEquivalent = getSpliceEquivalent(this, methodName, _.toArray(arguments)); | ||
summary = summariseSpliceOperation(this, spliceEquivalent); | ||
// make a copy of the original values | ||
if (summary != null) { | ||
start = summary.start; | ||
original = Array.prototype.slice.call(this, start, !summary.balance ? start + summary.added : void 0); | ||
size = (summary.balance > 0 ? summary.added : 0) + original.length; | ||
} else { | ||
start = 0; | ||
original = Array.prototype.slice.call(this, 0); | ||
size = original.length; | ||
} | ||
// apply the underlying method | ||
result = Array.prototype[methodName].apply(this, arguments); | ||
// trigger changes | ||
for (i = 0; i < size; i++) { | ||
index = i + start; | ||
this._observers.forEach(function(fn) { | ||
fn.call(this, index, this[index], original[i]); | ||
}, this); | ||
} | ||
return result; | ||
}; | ||
}); | ||
if (({}).__proto__) arr.__proto__ = patchedArrayProto; | ||
else { | ||
mutatorMethods.forEach(function(methodName) { | ||
Object.defineProperty(arr, methodName, { | ||
value: patchedArrayProto[methodName], | ||
configurable: true | ||
}); | ||
}); | ||
} | ||
return arr; | ||
} | ||
// converts array write operations into splice equivalent arguments | ||
var getSpliceEquivalent = | ||
exports.getSpliceEquivalent = function ( array, methodName, args ) { | ||
switch ( methodName ) { | ||
case 'splice': | ||
return args; | ||
case 'sort': | ||
case 'reverse': | ||
return null; | ||
case 'pop': | ||
if ( array.length ) { | ||
return [ -1 ]; | ||
} | ||
return null; | ||
case 'push': | ||
return [ array.length, 0 ].concat( args ); | ||
case 'shift': | ||
return [ 0, 1 ]; | ||
case 'unshift': | ||
return [ 0, 0 ].concat( args ); | ||
} | ||
} | ||
// returns a summary pf how an array will be changed after the splice operation | ||
var summariseSpliceOperation = | ||
exports.summariseSpliceOperation = function ( array, args ) { | ||
var start, addedItems, removedItems, balance; | ||
if ( !args ) { | ||
return null; | ||
} | ||
// figure out where the changes started... | ||
start = +( args[0] < 0 ? array.length + args[0] : args[0] ); | ||
// ...and how many items were added to or removed from the array | ||
addedItems = Math.max( 0, args.length - 2 ); | ||
removedItems = ( args[1] !== undefined ? args[1] : array.length - start ); | ||
// It's possible to do e.g. [ 1, 2, 3 ].splice( 2, 2 ) - i.e. the second argument | ||
// means removing more items from the end of the array than there are. In these | ||
// cases we need to curb JavaScript's enthusiasm or we'll get out of sync | ||
removedItems = Math.min( removedItems, array.length - start ); | ||
balance = addedItems - removedItems; | ||
return { | ||
start: start, | ||
balance: balance, | ||
added: addedItems, | ||
removed: removedItems | ||
}; | ||
} | ||
// tests a node against a selector | ||
exports.matchSelector = function(node, selector) { | ||
var nodes, i; | ||
nodes = ( node.parentNode || node.ownerDocument ).querySelectorAll( selector ); | ||
i = nodes.length; | ||
while ( i-- ) { | ||
if ( nodes[i] === node ) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
// returns the type of changes based on old and new values | ||
// expects oval !== nval | ||
var changeType = | ||
exports.changeType = function(nval, oval) { | ||
return _.isUndefined(oval) ? "add" : _.isUndefined(nval) ? "delete" : "update"; | ||
} |
{ | ||
"name": "templejs", | ||
"version": "0.2.12", | ||
"version": "0.3.1", | ||
"description": "A modern JavaScript view framework.", | ||
@@ -17,16 +17,11 @@ "author": "Beneath the Ink <info@beneaththeink.com>", | ||
}, | ||
"main": "./lib/temple.js", | ||
"dependencies": { | ||
"debug": "1.0.2", | ||
"hogan.js": "3.0.2", | ||
"underscore": "1.6.0" | ||
}, | ||
"main": "./lib/index.js", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"grunt": "0.4.5", | ||
"grunt-browserify": "2.1.0", | ||
"grunt-browserify": "2.1.3", | ||
"grunt-contrib-clean": "0.5.0", | ||
"grunt-contrib-copy": "0.5.0", | ||
"grunt-contrib-uglify": "0.5.0", | ||
"grunt-contrib-watch": "0.6.1", | ||
"grunt-peg": "1.4.0", | ||
"grunt-wait": "0.1.0", | ||
"grunt-wrap2000": "0.1.0" | ||
@@ -36,14 +31,10 @@ }, | ||
"keywords": [ | ||
"mustache", | ||
"view", | ||
"framework", | ||
"DOM", | ||
"html", | ||
"template", | ||
"templating", | ||
"data binding", | ||
"binding", | ||
"declarative", | ||
"view model", | ||
"reactive" | ||
"reactive", | ||
"dependency" | ||
] | ||
} |
@@ -5,5 +5,6 @@ # Temple | ||
* __Reactive__ - Powered by a ViewModel that automatically updates the DOM as the data changes. | ||
* __Modular & Extensible__ - Views are encapsulated, reusable components, making testing and separation of concerns easy. | ||
* __Mustache__ - Includes an *optional* [Mustache](http://mustache.github.io/) + XML language parser and renderer. | ||
* __Data Neutral__ - Temple is focused purely on the View aspect of web applications and can be easily integrated with existing frameworks and platforms. | ||
* __Tiny__ - Temple has no external dependencies and weighs in at just under 19KB minified. | ||
* __Reactive__ - Keep the interface up-to-date flexibly with auto-running computations powered by [Meteor](http://meteor.com)'s [dependency package](https://github.com/meteor/meteor/blob/e78861b7d0dbb60e5e2bf59bab2cb06ce6596c04/packages/deps/deps.js). | ||
@@ -29,28 +30,41 @@ __Note: This library is under active development. Use at your own risk!__ | ||
```javascript | ||
// create a template | ||
new Temple.Mustache("<span style='color: {{ color }};'>{{ message }}</span>") | ||
// A simple clock component | ||
var Clock = Temple.extend({ | ||
// on init, append a new text binding to hold the time value | ||
initialize: function() { | ||
this.time = this.appendChild(Clock.getTime()); | ||
}, | ||
// add data | ||
.set({ | ||
colors: [ "red", "blue", "green" ], | ||
colorIndex: 0, | ||
message: "Hello World", | ||
color: function() { | ||
return this.get("colors." + this.get("colorIndex")); | ||
// start an interval on mount that will continiously update the time | ||
beforeMount: function(comp) { | ||
this.interval = setInterval(this.invalidate.bind(this), 500); | ||
}, | ||
// when the view is unmounted, clear the interval | ||
onStop: function() { | ||
clearInterval(this.interval); | ||
delete this.interval; | ||
}, | ||
// updates the value of the text binding to the current time | ||
render: function() { | ||
this.time.setValue(Clock.getTime()); | ||
} | ||
}) | ||
}, { | ||
// a static method that returns the current time as a string | ||
getTime: function() { | ||
var date = new Date; | ||
// mix in some reactive changes | ||
.use(function() { | ||
this.toggleColor = function() { | ||
var newIndex = (this.get("colorIndex") + 1) % this.get("colors.length"); | ||
this.set("colorIndex", newIndex); | ||
return newIndex; | ||
return [ | ||
date.getHours(), | ||
date.getMinutes(), | ||
date.getSeconds() | ||
].map(function(digit) { | ||
return (digit < 10 ? "0" : "") + digit; | ||
}).join(":"); | ||
} | ||
}); | ||
setInterval(this.toggleColor.bind(this), 500); | ||
}) | ||
// apply to DOM | ||
.paint(document.body); | ||
``` | ||
// render a new instance of clock in the body element | ||
new Clock().mount().paint("body"); | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
0
7
69
45555
14
1313
1
- Removeddebug@1.0.2
- Removedhogan.js@3.0.2
- Removedunderscore@1.6.0
- Removedabbrev@1.1.1(transitive)
- Removeddebug@1.0.2(transitive)
- Removedhogan.js@3.0.2(transitive)
- Removedmkdirp@0.3.0(transitive)
- Removedms@0.6.2(transitive)
- Removednopt@1.0.10(transitive)
- Removedunderscore@1.6.0(transitive)