Created
October 3, 2012 09:11
-
-
Save mpj/3825942 to your computer and use it in GitHub Desktop.
Reactive Hlepers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require([ 'scripts/lib/responsive/context#ResponsiveContext' ], | |
function(ResponsiveContext) { | |
exports.bindSetter = function(target, targetSetter, source, sourceGetter) { | |
var ctx = new ResponsiveContext(); | |
ctx.onInvalidate(function() { | |
exports.bindSetter(target, targetSetter, source, sourceGetter); | |
}); | |
ctx.run(function () { | |
var result = source[sourceGetter](); | |
target[targetSetter](result); | |
}); | |
}; | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
/** | |
A responsive context, inspired by Meteor. | |
A responsive context is intended as a replacement for | |
events as a tool for invalidating databindings. Instead of | |
dispatching events that the dependent code listens to, the | |
dependent code instantiates a context and runs code inside of | |
it. The code then checks if it is running inside a context, | |
stores a reference to that context, and calls invalidate() on | |
it once it is stale. The dependent code will hear about this | |
since it has added callbacks to the context with onInvalidate(). | |
@constructor | |
*/ | |
var ResponsiveContext = function () { | |
this.id = next_context_id++; | |
this._callbacks = []; | |
this._invalidated = false; | |
}; | |
/** | |
Array of contexts that are pending invalidation. | |
Private, Static | |
*/ | |
var pending_invalidate = []; | |
/** | |
A counter to generate unique IDs for ResponsiveContexts. | |
Private, Static | |
*/ | |
var next_context_id = 1; | |
/** | |
Flush is called to actually do the invalidation | |
(i.e. call the onInvalidate callbacks) of the | |
contexts that reside in pending_invalidate. | |
Private, Static | |
*/ | |
var flush = function() { | |
// It's possible that more contexts | |
// are invalidated and added to pending_invalidate | |
// while flush is running, so we need to run this | |
// until it's empty. | |
while (pending_invalidate.length) { | |
// Move the contexts pending invalidation | |
// to a separate array and assign an empty array | |
// to pending invalidate so that more contexts | |
// can be invalidated while flush is working. | |
var contexts = pending_invalidate; | |
pending_invalidate = []; | |
for(var i=0;i<contexts.length;i++) { | |
var callbacks = contexts[i]._callbacks; | |
for(var k=0;k<callbacks.length;k++) { | |
callbacks[k](); | |
} | |
} | |
} | |
}; | |
/** | |
A reference to the current context. | |
This is to make the the context available to the | |
code that runs within the context, so that it can | |
call invalidate() once the result becomes stale. | |
Public, static | |
*/ | |
ResponsiveContext.current = null; | |
ResponsiveContext.prototype = { | |
/** | |
Run a function within the context. This sets | |
ResponsiveContext.current so that the executed | |
code can access it, and call invalidate() on | |
it once whatever it returns has become stale. | |
@param {Function} f The function to run. No arguments | |
will be provided. | |
@returns {Object} Whatever f returns. | |
*/ | |
run: function (f) { | |
// Store the previous context so that | |
// contexts can be created and executed within | |
// contexts. | |
var previous = ResponsiveContext.current; | |
ResponsiveContext.current = this; | |
try { return f(); } | |
finally { | |
ResponsiveContext.current = previous; | |
} | |
}, | |
/** | |
Invalidates the context. | |
Called from code executing inside "run", | |
which tells the context that it has become stale | |
and that the result needs to be fetched again. | |
@returns void. | |
*/ | |
invalidate: function () { | |
if (!this._invalidated) { | |
this._invalidated = true; | |
var isFlushing = !!pending_invalidate.length; | |
if (!isFlushing) { | |
setTimeout(flush, 0); | |
// We don't want invalidate to block execution, | |
// so we defer the flush using setTimeout. | |
} | |
pending_invalidate.push(this); | |
} | |
}, | |
/** | |
Attach a callback that listens for whenever | |
the context is invalidated. | |
@param {Function} f Callback that will be | |
executed when context is invalidated. The | |
context will be provided as only argument. | |
@returns void. | |
*/ | |
onInvalidate: function (f) { | |
if (this._invalidated) | |
f(this); | |
else | |
this._callbacks.push(f); | |
} | |
}; | |
exports.ResponsiveContext = ResponsiveContext; | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require([ 'scripts/lib/responsive/context#ResponsiveContext' ], | |
function(ResponsiveContext) { | |
(function() { | |
/** | |
Responsive, in-memory, key-value store for use with | |
ResponsiveContext. Loosely inspired by the | |
Session store in Meteor. | |
@constructor | |
*/ | |
function ResponsiveStore() { | |
this._data = {}; | |
this.dependent_contexts = {}; | |
} | |
/** | |
Retrieves a value from the store. | |
@param {string} key The key for which to retrieve a value. | |
@returns {Object} The value for the key. | |
*/ | |
ResponsiveStore.prototype.get = function(key) { | |
if (ResponsiveContext.current) { | |
// Make sure we have somewhere to store | |
// dependent contexts. | |
if (!this.dependent_contexts[key]) { | |
this.dependent_contexts[key] = {}; | |
} | |
// Shorthand | |
var context = ResponsiveContext.current; | |
var dependents = this.dependent_contexts[key]; | |
if (!dependents[context.id]) { | |
// Ok, we are inside a context, but the context is | |
// not yet assigned as dependent on the key. Let's do it! | |
dependents[context.id] = context; | |
// Whenever this context is invalidated, | |
// it's no longer dependent, so delete it | |
// from the dependent contexts. | |
context.onInvalidate(function () { | |
delete dependents[context.id]; | |
}); | |
} | |
} | |
return this._data[key]; | |
}; | |
/** | |
Stores a value into the store on a given key. | |
@param {string} key The key on which to store the value. | |
@param {Object} value The value to store. | |
@returns void | |
*/ | |
ResponsiveStore.prototype.set = function(key, value) { | |
var old_value = this._data[key]; | |
if (value === old_value) { | |
return; | |
} | |
this._data[key] = value; | |
var dependents = this.dependent_contexts[key]; // Shorthand | |
if(!dependents) { | |
// No dependents. This will happen if set is called | |
// before any gets have gotten in and attached their | |
// contexts. | |
return; | |
} | |
// Invalidate all dependent contexts. | |
for (var ctxId in dependents) { | |
dependents[ctxId].invalidate(); | |
} | |
}; | |
exports.ResponsiveStore = ResponsiveStore; | |
})(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment