Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

delete stray file

  • Loading branch information...
commit f3b6eb212a5d5c68a9270d9ad1ace7fb45ff3b3a 1 parent 5c44c01
@marcusphillips authored
Showing with 1 addition and 1,671 deletions.
  1. +0 −1,671 september_overhaul2.diff
  2. +1 −0  version_notes
View
1,671 september_overhaul2.diff
@@ -1,1671 +0,0 @@
-diff --git a/lib/js_dot_standin/js.js b/lib/js_dot_standin/js.js
-index 8f89a2f..a68ff30 100644
---- a/lib/js_dot_standin/js.js
-+++ b/lib/js_dot_standin/js.js
-@@ -26,6 +26,19 @@ var js = js || {
- }
- },
-
-+ hasKeys: function(object){
-+ for(var key in object){ return true; }
-+ return false;
-+ },
-+
-+ keys: function(object){
-+ var keys = [];
-+ for(key in object){
-+ keys.push(key);
-+ }
-+ return keys;
-+ },
-+
- map: function(collection, block){
- js.errorIf(typeof collection.length === 'undefined', 'js.map is currently written for arrays only');
- var result = [];
-@@ -35,6 +48,13 @@ var js = js || {
- return result;
- },
-
-+ reduce: function(collection, memo, callback){
-+ js.map(collection, function(which, item){
-+ memo = callback(which, item, memo);
-+ });
-+ return memo;
-+ },
-+
- matchers: {
- leadingOrTrailingSpace: /(^\s)|(\s$)/
- },
-@@ -70,6 +90,17 @@ var js = js || {
- }
- },
-
-+ debugIf: function(condition){
-+ if(condition){
-+ debugger;
-+ }
-+ },
-+
-+ errorAndDebugIf: function(condition, message){
-+ js.debuggerIf(condition);
-+ js.errorIf(condition, message);
-+ },
-+
- among: function(haystack, needle){
- if(haystack.indexOf){
- return haystack.indexOf(needle) !== -1;
-@@ -100,10 +131,14 @@ var js = js || {
- },
-
- copy: function(seed){
-- var copy = Object.prototype.toString.call(seed) === '[object Array]' ? [] : typeof seed === 'object' || typeof seed === 'function' ? {} : seed;
-+ var copy = js.isArray(seed) ? [] : typeof seed === 'object' || typeof seed === 'function' ? {} : seed;
- return js.extend(copy, seed);
- },
-
-+ isArray: function(input){
-+ return Object.prototype.toString.call(input) === '[object Array]';
-+ },
-+
- makeArrayLike: function(target){
- var arrayMethods = 'concat join pop push reverse shift slice sort splice unshift'.split(' ');
- for(var i = 0; i < arrayMethods.length; i++){
-diff --git a/react.js b/react.js
-index 25a2a8a..7b8ae5f 100644
---- a/react.js
-+++ b/react.js
-@@ -8,28 +8,31 @@
-
- (function () {
-
-+ /*
-+ * Library-wide helpers
-+ */
-+
- var debugging = true;
-
-+ var noop = function(){};
-+ var undefined; // safeguard, undefined can be overwritten in the global scope
-+
- var debug = function(){
-- if(debugging && typeof console !== 'undefined' && console.warn && console.warn.apply){
-+ if(debugging && console !== undefined && console.warn && console.warn.apply){
- console.warn.apply(console, arguments);
- }
- };
-
-- var doNotRecurse = {};
--
-- var undefined;
--
- var matchers = {
- directiveDelimiter: /\s*,\s*/,
- space: /\s+/,
-- isString: /(^'.*'$)|(^".*"$)/,
- negation: /!\s*/,
-- isNumber: /\d+/
-+ isString: /(^'.*'$)|(^".*"$)/,
-+ isNumber: /^\d+$/
- };
-
-+ // returns a unique, consistent key for every node
- var getNodeKey = function(node){
-- // todo: without using .data(), IE copies expando properties over, breaking loop behaviors and other cloning operations. disable .data() long enough to write a test for this.
- var key = $(node).data("reactKey");
- if(!key){
- key = js.util.unique('reactNode');
-@@ -42,16 +45,6 @@
- return (object.reactKey = object.reactKey || js.util.unique('reactObject'));
- };
-
-- var Fallthrough = function(key){ this.key = key; };
--
-- var updateNodes = function(nodes, updateContext){
-- for(var i = 0; i < nodes.length; i++){
-- makeRnode(nodes[i]).update(updateContext);
-- }
-- };
--
-- // todo: calling Array.prototype.slice.call on the results of a call to .querySelectorAll blows up in IE. revert this code to use that and write a test for it.
-- // also see if there's a more efficient way to build an array other than iterating over the array like object
- var makeArrayFromArrayLikeObject = function(arrayLikeObject){
- var array = [];
- for(var i = 0, length = arrayLikeObject.length; i < length ; i++){
-@@ -60,27 +53,37 @@
- return array;
- };
-
-+
-+ /*
-+ * Scope chains
-+ */
-+
-+ // Scope chains are used to model namespace lookup behavior in templates
-+ // all scope chains are built by calling emptyScopeChain.extend()
- var emptyScopeChain = (function(){
-
-+ // helper for creating the result (emptyScopeChain) and future scope chains returned from .extend()
- var makeScopeChain = function(type, previousLink, additionalScope, options){
- options = options || {};
-+
- var scopeChain = {
- parent: previousLink,
- scope: additionalScope,
- type: type,
- key: options.key,
- prefix: options.prefix || '',
-+// asdf this shouldn't need a prefix
- anchorKey: type === 'anchor' ? options.key : (previousLink||{}).anchorKey,
-
-- extend: function(type, additionalScope, options){
-- return makeScopeChain(type, scopeChain, additionalScope, options);
-- },
--
- contains: function(scope){
- return scopeChain.scope === scope || scopeChain.parent && scopeChain.parent.contains(scope);
- },
-
-- extendMany: function(type, scopes, options){
-+ extend: function(type, additionalScope, options){
-+ return makeScopeChain(type, scopeChain, additionalScope, options);
-+ },
-+
-+ extendWithMany: function(type, scopes, options){
- scopes = scopes || [];
- var lastLink = scopeChain;
- for(var which = 0; which < scopes.length; which++){
-@@ -89,91 +92,81 @@
- return lastLink;
- },
-
-- extendForAnchorNames: function(names){
-- names = names || [];
-- var scopes = [];
-- for(var whichToken = 0; whichToken < names.length; whichToken++){
-- var scopeKey = names[whichToken];
-- js.errorIf(!react.scopes[scopeKey], 'could not follow anchored directive, nothing found at react.scopes.'+scopeKey);
-- scopes.push(react.scopes[scopeKey]);
-- }
-- return scopeChain.extendMany('anchor', scopes, {key: scopeKey});
-- },
--
-- observe: function(key, directive){
-- var object = scopeChain.scope;
-- // todo: scope observers per node-object anchoring, for easy cleanup of memory references
-- react.nodes[directive.rnode.getKey()] = directive.rnode.node;
-- var observations = directive.rnode.node['directive ' + directive.index + ' observes'] = directive.rnode.node['directive ' + directive.index + ' observes'] || [];
-- observations.push({object: object, key: key});
-- object.observers = object.observers || {};
-- object.observers[key] = object.observers[key] || {};
-- object.observers[key][directive.rnode.getKey() + ' ' + directive.index + ' ' + scopeChain.prefix] = true;
-- },
--
-- lookup: function(key, options){
-- options = options || {};
-+ // provides the value at a given key by looking through the scope chain from this leaf up
-+ detailedLookup: function(key){
-+ key = key.toString();
- if(key[0] === '!'){
- var negate = true;
- key = key.slice(1);
- }
-- if (matchers.isString.test(key)) {
-- return key.slice(1, key.length-1);
-+ // the details object will contain all interesting aspects of this lookup
-+ // potentialObservers will hold the scopeChain/key pairs that may need to be bound for future updates
-+ var details = {potentialObservers: []};
-+ // extend details must be called on any return values, since it handles the final step of negation
-+ var extendDetails = function(moreDetails){
-+ for(var key in moreDetails||{}){
-+ details[key] = key === 'potentialObservers' ? details.potentialObservers.concat(moreDetails.potentialObservers || []) : moreDetails[key];
-+ }
-+ details.value = negate ? ! details.value : details.value;
-+ return details;
-+ };
-+
-+ // all lookups fail in the empty scope chain
-+ if(scopeChain === emptyScopeChain){
-+ return extendDetails({failed:true});
- }
-
-- // todo: clean up any pre-existing observers
-+ if (matchers.isString.test(key)) {
-+ js.errorIf(negate, 'You can\'t negate literals using the exlamation point');
-+ return extendDetails({value: key.slice(1, key.length-1)});
-+ }
-
- var path = key.split('.');
-+ // base key is the first segment of a path that uses dot access. It's is the only segment that will be taken from the current scope chain
- var baseKey = path.shift();
-- var details = {failed: true};
-- // todo: write a test to verify that responses to change events don't result in new observers
-- // todo: test that we don't observe binding objects
-- if(scopeChain.scope[baseKey] instanceof Fallthrough){
-- details = scopeChain.parent.lookup([scopeChain.scope[baseKey].key].concat(path).join('.'), js.extend({details:true}, options));
-- }else if(baseKey in scopeChain.scope){
-- if(scopeChain.anchorKey && options.listeningDirective && !options.suppressObservers){
-- scopeChain.observe(baseKey, options.listeningDirective);
-- }
-- var prefix = baseKey + '.';
-- var subObject = scopeChain.scope;
-- var value = subObject[baseKey];
-- while(path.length){ // one for each segment of the dot acess
-- subObject = value;
-- if(subObject === undefined || subObject === null){
-- debug('Could not find a valid subscope at '+prefix+' for '+key+' on ', scopeChain.scope);
-- return options.details ? details : subObject;
-- }
-- if(scopeChain.anchorKey && !options.suppressObservers && options.listeningDirective){
-- emptyScopeChain.extend('dotAccess', subObject, {
-- prefix: prefix,
-- anchorKey: scopeChain.anchorKey
-- }).observe(path[0], options.listeningDirective);
-- }
-- prefix = prefix + path[0] + '.';
-- value = subObject[path.shift()];
-+ var value = scopeChain.scope[baseKey];
-+
-+ // a Fallthrough object remaps the baseKey to a new baseKey in the previous scope
-+ if(value instanceof Fallthrough){
-+ return extendDetails(scopeChain.parent.detailedLookup( [value.key].concat(path).join('.') ));
-+ }
-+
-+ details.potentialObservers.push({scopeChain: scopeChain, key: baseKey});
-+
-+ // recurse onto the parent scopeChain if the lookup fails at this level
-+ if(! (baseKey in scopeChain.scope) ){
-+ return extendDetails(scopeChain.parent.detailedLookup(key));
-+ }
-+
-+ details.matchingBaseObject = scopeChain.scope;
-+
-+ // for dot access
-+ if(path.length){
-+ if(value === undefined || value === null){
-+ debug('Could not find '+key+' on a null or undefined object at path '+prefix+baseKey+' from', scopeChain.scope);
-+ return extendDetails();
- }
-- details = {
-- matchingScopeChain: scopeChain,
-- matchingBaseObject: subObject,
-- baseKey: baseKey,
-- negated: negate,
-- value: typeof value === 'function' ? value.call(subObject||{}) : value
-- };
-- details.value = (negate ? ! details.value : details.value);
-- }else if(!scopeChain.parent.isEmpty){
-- details = scopeChain.parent.lookup(key, js.extend({details:true}, options));
-+ return extendDetails(emptyScopeChain.extend('dotAccess', value, {
-+ prefix: scopeChain.prefix + baseKey + '.',
-+ anchorKey: scopeChain.anchorKey
-+ }).detailedLookup(path.join('.')));
- }
-- return options.details ? details : details.value;
-+
-+ // functions are called before being returned
-+ value = typeof value === 'function' ? value.call(scopeChain.scope) : value;
-+
-+ return extendDetails({value: value});
-+ },
-+
-+ lookup: function(){
-+ return scopeChain.detailedLookup.apply(scopeChain, arguments).value;
- },
-
-+ // provides a description of the scope chain in array format, optimized for viewing in the console
- describe: function(){
-- var link = scopeChain;
-- var scopeChainDescription = [];
-- while(link){
-- scopeChainDescription.push(['scope: ', link.scope, ', type of scope shift: ' + link.type + (link.key ? ' (key: '+link.key+')': '') + (link.anchorKey ? ', anchored to: '+link.anchorKey+')': '')]);
-- link = link.parent;
-- }
-- return scopeChainDescription;
-+ return [
-+ ['scope: ', scopeChain.scope, ', type of scope shift: ' + scopeChain.type + (scopeChain.key ? ' (key: '+scopeChain.key+')': '') + (scopeChain.anchorKey ? ', anchored to: '+scopeChain.anchorKey+')': '')]
-+ ].concat(scopeChain.parent ? scopeChain.parent.describe() : []);
- }
-
- };
-@@ -181,88 +174,54 @@
- return scopeChain;
- };
-
-- var emptyScopeChain = makeScopeChain(undefined, undefined, {type:'empty'});
-- emptyScopeChain.isEmpty = true;
-- emptyScopeChain.lookup = function(){ js.error('cannot lookup in the empty scope chain'); };
-+ var emptyScopeChain = makeScopeChain('empty');
- return emptyScopeChain;
- }());
-
-- var globalScope = {};
-- var globalScopeChain = emptyScopeChain.extend('global', globalScope);
-+ // Fallthroughs provide a mechanism for binding one key in a scope to the value at another key
-+ var Fallthrough = function(key){ this.key = key; };
-
-- var react = {
-
-- _enqueueNodes: function(newNodes){
-- this.nodesToUpdate.push.apply(this.nodesToUpdate, newNodes);
-- for(var whichNode = 0; whichNode < newNodes.length; whichNode++){
-- var nodeKey = getNodeKey(newNodes[whichNode]);
-- delete this.bequeathedScopeChains[nodeKey];
-- delete this.loopItemTemplates[nodeKey];
-- }
-- },
-+ /*
-+ * Library interface
-+ */
-
-- _removeNodes: function(newNodes){
-- var nodeKeysToRemove = {};
-- for(var whichNode = 0; whichNode < newNodes.length; whichNode++){
-- var nodeKey = getNodeKey(newNodes[whichNode]);
-- nodeKeysToRemove[nodeKey] = true;
-- delete this.bequeathedScopeChains[nodeKey];
-- delete this.loopItemTemplates[nodeKey];
-- }
-- var newNodesToUpdate = js.filter(this.nodesToUpdate, function(which, each){
-- return !nodeKeysToRemove[getNodeKey(each)];
-- });
-- this.nodesToUpdate.splice(0, this.nodesToUpdate.length);
-- this.nodesToUpdate.push.apply(this.nodesToUpdate, newNodesToUpdate);
-- },
-+ var react = {
-
- nodes: {},
- scopes: {},
-
-- debug: function(setting){
-- if(typeof setting === 'undefined'){ setting = true; }
-- debug = setting;
-+ setDebugging: function(setting){
-+ if(setting === undefined){ setting = true; }
-+ debugging = setting;
- },
-
-+ // for giving scope objects meaningful names, which appear in the anchor directives on nodes. not yet ready for external consumption
- name: function(name, object){
-+ js.errorIf(object.reactKey, 'You tried to name a scope object that already had a name');
-+ object.reactKey = name;
- this.scopes[name] = object;
- },
-
-+ // convenience method for setting object values and automatically calling changed on them
- set: function(object, key, value){
- object[key] = value;
- this.changed(object, key);
- },
-
-- changed: function(object, key){
-- // if no key is supplied, check every key
-- if(arguments.length < 2){
-- for(key in object){
-- this.changed(object, key);
-- }
-- // todo: this still won't work for arguments lists
-- if(Object.prototype.toString.call(object) === '[object Array]'){
-- this.changed(object, 'length');
-- }
-- return;
-- }
--
-- // if there are no observers for the supplied key, do nothing
-- if(!object || !object.observers || !object.observers[key]){ return; }
--
-- for(var listenerString in object.observers[key]){
-- makeListener(object, key, listenerString).check();
-- }
-+ // allows user to notify react that an object's property has changed, so the relevant nodes may be updated
-+ changed: function(){
-+ makeOperation().changed.apply({}, arguments).run();
- },
-
-- update: function(/*[node, scope],*/ options){
-+ update: function(/*[node, scope,]*/ options){
- options = options || {};
- if(options.nodeType || (options instanceof jQuery)){
- // detect argument signature of (node, scope)
-- options = {
-+ options = js.extend({
- node: arguments[0],
- scope: arguments[1]
-- };
-- js.extend(options, arguments[2] || {});
-+ }, arguments[2] || {});
- }
- if(options.node instanceof jQuery){
- if(options.node.length !== 1){
-@@ -270,7 +229,19 @@
- }
- options.node = options.node[0];
- }
-- return makeRnode(options.node).updateTree(options);
-+
-+ js.errorIf(options.scope && options.scopes, 'you must supply only one set of scopes');
-+
-+ var scopes = options.scope ? [options.scope] : options.scopes ? options.scopes : [];
-+ if(options.anchor){
-+ react.anchor({node: options.node, scopes: scopes});
-+ scopes = [];
-+ }
-+
-+ var operation = makeOperation();
-+ operation.$(options.node).directives[options.fromDirective||'before'].injectScopes('updateInputs', scopes).dirtyBranch();
-+ operation.run();
-+ return options.node;
- },
-
- anchor: function(options){
-@@ -286,7 +257,7 @@
-
- this.nodes[getNodeKey(node)] = node;
- // todo: clean up any links elsewhere (like listeners) that are left by potential existing anchors
-- makeRnode(node).directives.set('anchored', ['anchored'].concat(js.map(scopes, function(i, scope){
-+ makeOperation().$(node).directives.set('anchored', ['anchored'].concat(js.map(scopes, function(i, scope){
- var scopeKey = getScopeKey(scopes[i]);
- react.scopes[scopeKey] = scopes[i];
- return scopeKey;
-@@ -304,536 +275,660 @@
-
- };
-
-+ var commands = react.commands = {
-+ scopes: react.scopes
-+ };
-
-- var commands = react.commands = js.create(react, {
-
- /*
-- * when a command runs, it will have a 'this' scope like the following (arrows indicate prototype relationships)
-- *
-- * react {
-- * }
-- *
-- * ^
-- * |
-- * commands {
-- * command handler definitions
-- * lookup(key)
-- * }
-- *
-- * ^
-- * |
-- * // a new processing scope is created for each node to be updated
-- * nodeContext {
-- * node
-- * scopeChain
-- * }
-+ * Operation
- */
-
-- lookup: function(key, options){
-- options = options || {};
-- options.listeningDirective = makeRnode(this.node).directives[this.directiveIndex];
-- options.suppressObservers = 'suppressObservers' in options ? options.suppressObservers : this.suppressObservers;
-+ // An operation provides a shared context where complex interactions may rely upon shared state
-
-- return this.scopeChain.lookup(key, options);
-- },
-+ var makeOperation = function(){
-+ // within an operation, all $node objects are cached to maintain object-identicality across calls to $()
-+ var $nodes = {};
-+ var proxies = [];
-
-- anchored: function(token){
-- if(!this.scopes[token]){
-- debug('anchored directive failed to find a scope for the key "'+key+'"');
-- return;
-- }
-- this.pushScope('anchor', this.scopes[token], {key:token});
-- },
-+ // to ensure root-first processing order, we earmark each directive we plan to follow, then follow them all during the run() step
-+ var branchesToConsider = {};
-+ var directivesToConsider = {};
-+ var consideredDirectives = {};
-
-- within: function(key){
-- var scope = this.lookup(key);
-- if(!scope){
-- debug('within directive failed to find a scope for the key "'+key+'"');
-- return;
-- }
-- this.pushScope('within', scope, {key:key});
-- },
-+ /*
-+ * Proxy
-+ */
-
-- contain: function(key){
-- // using innerHTML to clear the node because the jQuery convenience functions unbind event handlers. This would be an unexpected side effect for most React user consumption cases.
-- this.node.innerHTML = '';
-- var insertion = this.lookup(key);
-- // if the insertion is a node, use the dom appending method, but insert other items as text
-- if(insertion && insertion.nodeType){
-- jQuery(this.node).append(insertion);
-- this._removeNodes(makeRnode(insertion).getReactNodes());
-- } else {
-- jQuery(this.node).text(insertion);
-- }
-- },
-+ // A proxy provides an interface for the observer relationship between any JS object and the nodes/directives observing it's properties
-
-- classIf: function(conditionKey, nameKey){
-- this.node.classIfs = this.node.classIfs || {};
-- var condition = this.lookup(conditionKey);
-- var className;
-- var persistence = conditionKey + ' ' + nameKey;
-- if(condition){
-- className = this.lookup(nameKey);
-- if(className){
-- $(this.node).addClass(className);
-- this.node.classIfs[persistence] = className;
-+ var makeProxy = function(object){
-+
-+ var proxy = {
-+ // writes an association between a directive and a property on an object by annotating the object
-+ observe: function(key, directive, prefix){
-+ directive.$node.store();
-+ makeObserver(key, directive.$node.key, directive.index, prefix).write();
-+ },
-+
-+ changed: function(keys){
-+ // if no key is supplied, check every key
-+ if(!object || !object.observers){ return; }
-+ keys = (
-+ js.isArray(keys) ? keys :
-+ keys !== undefined ? [keys] :
-+ js.keys(object).concat('length' in object && !object.propertyIsEnumerable('length') ? ['length'] : [])
-+ );
-+
-+ // we first need to collect all the observers of the changed keys
-+ for(var whichKey = 0; whichKey < keys.length; whichKey++){
-+ key = keys[whichKey];
-+ if(!object.observers[key]){ continue; } // if there are no observers for the supplied key, do nothing
-+ for(var keyObserverString in object.observers[key]){
-+ makeObserver(key, keyObserverString).dirty();
-+ }
-+ }
- }
-- } else {
-- className = this.node.classIfs[persistence] || this.lookup(nameKey);
-- if(className){
-- $(this.node).removeClass(className);
-- delete this.node.classIfs[persistence];
-+ };
-+
-+ var cachedObservers = {};
-+ var makeObserver = function(propertyKey, nodeKey, directiveIndex, prefix){
-+ if(arguments.length === 2){
-+ var tokens = arguments[1].split(matchers.space);
-+ nodeKey = tokens[0];
-+ directiveIndex = tokens[1];
-+ prefix = tokens[2];
- }
-- }
-- },
-
-- _createItemNodes: function(directiveMaker){
-- var $loopChildren = jQuery(this.node).children();
-- js.errorIf($loopChildren.length < 2, 'looping nodes must contain at least 2 children - one item template and one results container');
-- var $itemTemplate = $loopChildren.first();
-- //js.errorIf(makeRnode($itemTemplate[0]).directives[0].join(' ') !== 'itemTemplate', 'the item template must declare itself with an item directive');
-- $itemTemplate.addClass('reactItemTemplate');
-- this.loopItemTemplates[getNodeKey($itemTemplate[0])] = $itemTemplate[0];
-- var $resultsContainer = $($loopChildren[1]);
-- var $resultsContents = $resultsContainer.children();
-+ var observerDetailsString = nodeKey+' '+directiveIndex+' '+prefix;
-+ var observerKey = propertyKey+' '+observerDetailsString;
-+ if(cachedObservers[observerKey]){ return cachedObservers[observerKey]; }
-
-- // todo: ignore binding scopes when looking for scope to iterate over
-- var collection = this.scopeChain.scope;
-- // todo: don't allow looping over static native objects (like strings - this is almost certainly an error)
-- js.errorIf(collection === null || collection === undefined, 'The loop command expected a collection, but instead encountered '+collection);
-- if(this.scopeChain.anchorKey && !this.suppressObservers){
-- // todo: optimize. this is a shortcut - it simply puts a listener on the length property that results in a complete re-render of the looping directive if ever a change in length is noticed
-- this.scopeChain.observe('length', makeRnode(this.node).directives[this.directiveIndex]);
-- }
-+ var observer = {
-+ object: object,
-
-- var itemNodes = [];
-- for(var i = 0; i < collection.length; i++){
-- var itemNode = $resultsContents[i];
-- if(!itemNode){
-- itemNode = $itemTemplate.clone().removeClass('reactItemTemplate')[0];
-- // todo: implement bindings as key aliases
-- js.errorIf(matchers.space.test(i), 'looping not currently supported over colletions with space-filled keys'); // todo: make this even more restrictive - just alphanumerics
-- var itemDirective = directiveMaker(i);
-- makeRnode(itemNode).directives.prepend(itemDirective);
-- this._enqueueNodes(makeRnode(itemNode).getReactNodes());
-- }
-- itemNodes.push(itemNode);
-- }
-- if(collection.length !== $resultsContents.length){
-- // we set innerHTML here to prevent jQuery fron detaching all event handlers (automatic in an .html() call)
-- $resultsContainer[0].innerHTML = '';
-- $resultsContainer.html(itemNodes);
-- }
-- },
-+ directive: $(react.nodes[nodeKey]).directives[directiveIndex],
-
-- withinEach: function(){
-- // todo: return here (and everywhere else) if collection is undefined. test for this
-- this._createItemNodes(function(index){
-- return ['withinItem', index];
-- });
-- },
-+ key: observerKey,
-
-- withinItem: function(key){
-- // todo: add a rule to only allow getting items from last scope (check if key < scope.length?)
-- // todo: add a rule to make sure the last scope object is an array
-- js.errorIf(this.scopeChain.scope.length-1 < +key, 'Tried to re-render a node for an index the no longer exists');
-- // todo: want to raise an error including link to this.scopeChain.scope - write an error helper
-- js.errorIf(!this.scopeChain.scope[key], 'Could not find anything at key '+key+' on the scope object');
-- // todo: might be a problem that using the within() as a helper will give the scope a type of 'within'
-- this.within(key);
-- },
-+ write: function(){
-+ object.observers = object.observers || {};
-+ object.observers[propertyKey] = object.observers[propertyKey] || {};
-+ object.observers[propertyKey][observerDetailsString] = true;
-+ },
-
-- 'for': function(keyAlias, valueAlias){
-- var aliases = arguments;
-- // todo: return here (and everywhere else) if collection is undefined. test for this
-- this._createItemNodes(function(index){
-- return ['bindItem', index].concat(Array.prototype.slice.call(aliases));
-- });
-- },
-+ dirty: function(){
-+ if(observer.isDirty){ return; }
-+ observer.isDirty = true;
-+ observer.directive.dirtyObserver(observer);
-+ },
-
-- bindItem: function(key, keyAlias, valueAlias){
-- if(valueAlias === undefined){
-- valueAlias = keyAlias;
-- keyAlias = undefined;
-- }
-+ pertains: function(){
-+ var details = observer.directive.getScopeChain().detailedLookup(prefix + propertyKey);
-+ // ignore the object if it's not in the same path that lead to registration of the observer
-+ return details.matchingBaseObject === object || details.failed && observer.directive.getScopeChain().contains(object);
-+ }
-+ };
-
-- // set up an item scope to be applied for each item
-- // a new scope will be created with bindings for valueAlias and optionally for keyAlias
-- var itemBindings = {};
-- if(keyAlias !== undefined){
-- itemBindings[keyAlias] = key;
-- }
-- // todo: don't make this a fallthrough - create an explicit binding to the previous array scope object
-- itemBindings[valueAlias] = new Fallthrough(key);
-+ return cachedObservers[observerKey] = observer;
-+ };
-
-- this.pushScope('bindItem', itemBindings, {key:key});
-- },
-+ proxies.push(proxy);
-+ return proxy;
-+ };
-
-- _conditionalShow: function(conditional){
-- jQuery(this.node)[conditional ? 'show' : 'hide']();
-- },
-+ // Overriding jQuery to provide supplemental functionality to DOM node wrappers
-+ // Within the scope of makeOperation, all calls to $() return a customized jQuery object. For access to the original, use jQuery()
-+ var $ = function(node){
-+ js.errorIf(arguments.length !== 1 || !node || node.nodeType !== 1 || js.isArray[arguments[0]] || arguments[0] instanceof jQuery, 'overridden $ can only accept one input, which must be a DOM node');
-+ if($nodes[getNodeKey(node)]){ return $nodes[getNodeKey(node)]; }
-
-- 'if': function(condition){
-- var conditional = this.lookup(condition);
-- // this is technical debt, but will go away in the refactor. the suppressObservers flag happens to be useful in this case to detect when we are only building a scope chain. when that happens, we also don't want to have these side effects on the classes either
-- if(!this.suppressObservers){
-- $(this.node)[conditional ? 'removeClass' : 'addClass']('reactConditionallyHidden');
-- }
-- if(!conditional){
-- this.pushScope('doNotRecurse', doNotRecurse);
-- }
-- this._conditionalShow(conditional);
-- },
-+ var $node = js.create(jQuery(node), {
-+ key: getNodeKey(node),
-
-- showIf: function(condition){
-- this._conditionalShow(this.lookup(condition));
-- },
-+ getDirectiveStrings: function(){
-+ return ($node.attr('react')||'').split(matchers.directiveDelimiter);
-+ },
-
-- visIf: function(condition){
-- jQuery(this.node).css('visibility', this.lookup(condition) ? 'visible' : 'hidden');
-- },
-+ getDirectiveArrays: function(){
-+ return js.reduce($node.getDirectiveStrings(), [], function(which, string, memo){
-+ return string ? memo.concat([js.trim(string).replace(matchers.negation, '!').split(matchers.space)]) : memo;
-+ });
-+ },
-
-- attr: function(name, value){
-- js.errorIf(arguments.length !== 2, 'the attr directive requires 2 arguments');
-+ wrappedParent: function(){
-+ return (
-+ ! $node.parent()[0] ? null :
-+ $node.parent()[0] === document ? null :
-+ $($node.parent()[0])
-+ );
-+ },
-
-- name = this.lookup(name);
-- value = this.lookup(value);
-+ store: function(){
-+ react.nodes[$node.key] = node;
-+ },
-
-- if(!js.among(['string', 'number', 'undefined'], typeof name)){
-- js.log('bad attr name: ', name);
-- js.error('expected attr name token ' + name + ' to resolve to a string, a number, null, or undefined, not ' + typeof name);
-- }else if(!js.among(['string', 'number', 'undefined'], typeof value)){
-- js.log('bad attr value: ', value);
-- js.error('expected attr value token ' + value + ' to resolve to a string, a number, null, or undefined, not ' + typeof value);
-- }
-+ // note: getReactNodes() only returns the operative node and nodes that have a 'react' attribute on them. any other nodes of interest to react (such as item templates that lack a 'react' attr) will not be included
-+ getSelfAndReactDescendants: function(){
-+ return js.map([node].concat(makeArrayFromArrayLikeObject(node.querySelectorAll('[react]'))), function(which, node){
-+ return $(node);
-+ });
-+ }
-
-- jQuery(this.node).attr(name, value);
-- },
-+ });
-
-- attrIf: function(condition, name, value){
-- if(this.lookup(condition)){
-- $(this.node).attr(this.lookup(name), this.lookup(value));
-- } else {
-- $(this.node).removeAttr(this.lookup(name));
-- }
-- },
-+ // provides an object representing the directive itself (for example, "contain user.name")
-+ var makeDirective = function(index, tokens){
-+ var parent;
-+ var dirtyObservers = {};
-+ var reportedAsClean;
-+ var isDirty;
-+ var isContageous;
-+ var isDead;
-+ var isFollowed;
-+ var scopeChain;
-+ var scopeInjectionArgLists = [];
-+
-+ var directive = js.create(commands, {
-+ $: $,
-+ $node: $node,
-+ node: node,
-+ isDirective: true,
-+ command: tokens[0],
-+ inputs: tokens.slice(1),
-+
-+ setIndex: function(newIndex){
-+ index = directive.index = newIndex;
-+ directive.key = $node.key+' '+index;
-+ },
-+
-+ lookup: function(key){
-+ var details = directive.getScopeChain().detailedLookup(key);
-+ for(var i = 0; i < details.potentialObservers.length; i++){
-+ var potentialObserver = details.potentialObservers[i];
-+ if(directive.isDirty() && potentialObserver.scopeChain.anchorKey){
-+ makeProxy(potentialObserver.scopeChain.scope).observe(potentialObserver.key, directive, scopeChain.prefix);
-+ }
-+ }
-+ return details.value;
-+ },
-
-- checkedIf: function(condition){
-- $(this.node).attr('checked', this.lookup(condition));
-- }
-+ resetScopeChain: function(){
-+ scopeChain = emptyScopeChain;
-+ },
-
-- });
-+ injectScopes: function(type, scopes){
-+ scopeInjectionArgLists = scopeInjectionArgLists.concat(js.map(scopes, function(which, scope){
-+ return [type, scope];
-+ }));
-+ return directive;
-+ },
-
-- // a reactnode (rnode) is a wrapper for dom nodes that provides supplemental functionality
-- var makeRnode = function(node){
-+ pushScope: function(type, scope, options){
-+ scopeChain = directive.getScopeChain().extend(type, scope, options);
-+ },
-+
-+ getScopeChain: function(){
-+ return scopeChain = js.reduce(scopeInjectionArgLists, scopeChain || directive.getParent().getScopeChain(), function(which, scopeChainArguments, memo){
-+ return memo.extend.apply(memo, scopeChainArguments);
-+ });
-+ },
-+
-+ getScope: function(){
-+ return directive.getScopeChain().scope;
-+ },
-+
-+ // a branch of the directive tree is 'dead' if no child directives (and itself) should be followed
-+ dead: function(){
-+ isDead = true;
-+ return directive;
-+ },
-+
-+ // a directive is 'dirty' if it the currently rendered output needs to be updated based on the new inputs
-+ dirty: function(){
-+ js.errorIf(isDirty === false, 'internal error: tried to set \'dirty\' property after isDirty() returned false');
-+ isDirty = true;
-+ directive.consider();
-+ return directive;
-+ },
-+
-+ // a directive is contageous if its 'dirty' value propagates to children
-+ contageous: function(){
-+ isContageous = true;
-+ return directive;
-+ },
-+
-+ // todo: this might not be necessary - contagous should perhaps just do this
-+ dirtyBranch: function(){
-+ directive.dirty().contageous().considerBranch();
-+ },
-+
-+ dirtyObserver: function(observer){
-+ js.errorIf(isDirty === false, 'internal error: tried to add a dirty observer after isDirty() returned false');
-+ dirtyObservers[observer.key] = observer;
-+ directive.consider();
-+ },
-+
-+ wasReportedAsClean: function(){
-+ return reportedAsClean;
-+ },
-+
-+ isDead: function(){
-+ return isDead = isDead === undefined ? directive.getParent().isDead() : directive.isDead();
-+ },
-+
-+ isDirty: function(){
-+ return isDirty = isDirty === undefined ? directive.getParent().doesInfect() || js.reduce(dirtyObservers, false, function(which, observer, memo){
-+ memo || dirtyObservers[which].pertains();
-+ }) : isDirty;
-+ },
-+
-+ doesInfect: function(){
-+ return directive.follow(), isDirty && isContageous;
-+ },
-+
-+ consider: function(){
-+ directivesToConsider[directive.key] = directive;
-+ },
-+
-+ considerBranch: function(){
-+ branchesToConsider[directive.key] = directive;
-+ },
-+
-+ ifDirty: function(callback){
-+ didCallIfDirty = true;
-+ if(directive.isDirty() && callback){
-+ callback.apply(directive);
-+ }
-+ },
-+
-+ // the directive's command (for example, 'contain') will be executed with a 'this' context of that directive
-+ follow: function(){
-+ js.errorIf(!isRunning, 'An internal error occurred: someone tried to .follow() a directive outside of operation.run()');
-+ if(isFollowed){ return; }
-+ isFollowed = true;
-+ directive.getParent().follow();
-+ describeErrors(function(){
-+ js.errorIf(!commands[directive.command], directive.command+' is not a valid react command');
-+ commands[directive.command].apply(directive, directive.inputs);
-+ js.errorIf(!didCallIfDirty, 'all directives must run a section of their code in a block passed to this.ifDirty(), even if the block is empty. Put any code that will mutate state there, and leave code that manipulates scope chains, etc outside of it.');
-+ if(branchesToConsider[directive.key]){
-+ var $nodes = $node.getSelfAndReactDescendants();
-+ for(var which = 0; which < $nodes.length; which++){
-+ $nodes[which].directives.after.consider();
-+ }
-+ }
-+ });
-+ },
-+
-+ getParent: function(){
-+ if(parent){ return parent; }
-+ var previousParent = 'unmatchable', repeatLimit = 10000;
-+ while(parent !== previousParent){
-+ previousParent = parent;
-+ parent && parent.follow();
-+ js.errorIf(!repeatLimit--, 'You\'ve done something really crazy in your directives. Probably mutated state in a way that changes the dom structure. Don\'t do that.');
-+ parent = (
-+ directive.index === 'before' ? $node.wrappedParent() ? $node.wrappedParent().directives.after : nullDirective :
-+ directive.index === 'anchored' ? directives.before :
-+ directive.index.toString() === '0' ? directives.anchored :
-+ directive.index.toString().match(matchers.isNumber) ? directives[directive.index-1] :
-+ directive.index === 'after' ? directives.length ? directives[directives.length-1] : directives.anchored :
-+ js.error('invalid directive key')
-+ );
-+ }
-+ return parent;
-+ },
-
-- var rnode = {
-+ setParent: function(directive){
-+ parent = directive;
-+ },
-
-- node: node,
-+ toString: function(){
-+ return [directive.command].concat(directive.inputs).join(' ');
-+ }
-
-- getKey: function(){
-- return getNodeKey(node);
-- },
-+ });
-
-- // note: getReactNodes() only returns the operative node and nodes that have a 'react' attribute on them. any other nodes of interest to react (such as item templates that lack a 'react' attr) will not be included
-- getReactNodes: function(){
-- return [node].concat(makeArrayFromArrayLikeObject(node.querySelectorAll('[react]')));
-- },
-+ directive.setIndex(index);
-
-- getParent: function(updateContext){
-- var ancestor = $(node).parent()[0];
-- var repeatLimit = 1000;
-- while(repeatLimit--){
-- if(!ancestor || ancestor === document){
-- return false;
-- } else if (
-- ancestor === updateContext.root ||
-- ancestor.getAttribute('react') ||
-- updateContext.bequeathedScopeChains[getNodeKey(ancestor)] || // todo: what's this cover?
-- updateContext.loopItemTemplates[getNodeKey(ancestor)] // todo: I don't think we need this now that it gets a special class attached to it
-- ){
-- return ancestor;
-+ var describeErrors = function(callback){
-+ try{ callback(); } catch (error){
-+ js.log('Failure during React update: ', {
-+ 'original error': error,
-+ 'original stack': error.stack && error.stack.split ? error.stack.split('\n') : error.stack,
-+ 'while processing node': node,
-+ 'index of failed directive': directive.index,
-+ 'directive call': directive.command+'('+directive.inputs.join(', ')+')',
-+ 'scope chain description': directive.getScopeChain().describe(),
-+ '(internal scope chain object) ': directive.getScopeChain()
-+ });
-+ throw error;
- }
-- ancestor = $(ancestor).parent()[0];
-- }
-- js.error('rnode.getParent() broke');
-- },
-+ };
-
-- updateGivenScopeChain: function(scopeChain, updateContext, fromDirective){
-- var nodeKey = rnode.getKey();
-- var directives = rnode.directives;
-+ return directive;
-+ };
-
-- if(directives.anchored){
-- scopeChain = scopeChain.extendForAnchorNames(directives.anchored.inputs);
-- }
-+ var nullDirective = makeDirective(null, [], null);
-+ nullDirective.follow = noop;
-+ nullDirective.getParent = function(){ js.error('internal error: cannot get the parent of a null directive'); };
-+ nullDirective.getScopeChain = function(){ return emptyScopeChain; };
-+
-+ // build up directives
-+ var directives = js.reduce($node.getDirectiveArrays(), [], function(which, tokens, memo){
-+ which === 0 && tokens[0] === 'anchored' ?
-+ memo.anchored = makeDirective('anchored', tokens) :
-+ memo.push(makeDirective((memo.anchored ? which-1 : which).toString(), tokens));
-+ return memo;
-+ });
-
-- var directiveContext = {scopeChain: scopeChain};
-- for(var i = fromDirective || 0; i < directives.length; i++){
-- var directiveContext = updateContext.makeDirectiveContext(node, i, directiveContext.scopeChain);
-- directives[i].follow(directiveContext);
-- }
-+ directives.anchored = directives.anchored || makeDirective('anchored', ['anchored']);
-
-- return directiveContext.scopeChain;
-- },
-+ $node.directives = js.extend(directives,{
-
-- update: function(updateContext){
-- //todo: test that you never revisit a node
-- var nodeKey = getNodeKey(node);
-- if(
-- typeof updateContext.bequeathedScopeChains[nodeKey] !== 'undefined' ||
-- node === updateContext.root // this is to prevent an undefined scope chain for the root getting overwritten with false. don't like it.
-- ){
-- // node has already been visited
-- return;
-- }
-+ before: makeDirective('before', ['before']),
-+ after: makeDirective('after', ['after']),
-
-- if(updateContext.loopItemTemplates[getNodeKey(node)]){ // todo: get rid of all these references to 'loop item templates', use custom class instead
-- updateContext.bequeathedScopeChains[nodeKey] = false;
-- return;
-- }
-- var previousParent = 'unmatchable';
-- var parent = rnode.getParent(updateContext);
-- // if processing the parent leads to this node having a new parent, repeat
-- while(parent !== previousParent){
-- if(!parent){
-- updateContext.bequeathedScopeChains[nodeKey] = false;
-- return;
-- }
-- makeRnode(parent).update(updateContext);
-- if(updateContext.bequeathedScopeChains[getNodeKey(parent)] === false){
-- updateContext.bequeathedScopeChains[nodeKey] = false;
-- return;
-- }
-- previousParent = parent;
-- parent = rnode.getParent(updateContext);
-- }
-+ // todo: this takes an array, rather than a directive object. that seems odd, but directive objects aren't makable outside this scope
-+ set: function(key, directive){
-+ directives[key] = makeDirective(''+key, directive);
-+ directives.write();
-+ },
-
-- var scopeChain = updateContext.bequeathedScopeChains[getNodeKey(parent)] || globalScopeChain;
-- updateContext.bequeathedScopeChains[nodeKey] = rnode.updateGivenScopeChain(scopeChain, updateContext);
-- },
-+ write: function(){
-+ node.setAttribute('react', directives);
-+ },
-
-- makeUpdateContext: function(){
-- return makeUpdateContext(node);
-- },
-+ orderedForString: function(){
-+ return (directives.anchored.inputs.length ? [directives.anchored] : []).concat(directives);
-+ },
-
-- updateTree: function(options){
-- //todo: test these
-- //js.errorIf(!root, 'no root supplied to update()');
-- //js.errorIf(react.isNode(root), 'first argument supplied to react.update() must be a dom node');
-- js.errorIf(options.scope && options.scopes, 'you must supply only one set of scopes');
-+ toString: function(){
-+ return js.map(directives.orderedForString(), function(which, directive){
-+ if(!directive.isDirective && console){ console.log('oops - something\'s wrong with your directives'); }
-+ return directive.toString();
-+ }).join(', ');
-+ },
-
-- var scopes = options.scope ? [options.scope] : options.scopes ? options.scopes : undefined;
-- if(options.anchor){
-- react.anchor({node: node, scopes:scopes});
-- scopes = [];
-+ prepend: function(directive){
-+ directive = directive.isDirective ? directive : makeDirective('0', directive);
-+ directives.unshift(directive);
-+ js.map(directives, function(which, directive){
-+ directive.setIndex(which.toString());
-+ });
-+ directives.write();
- }
-- var baseScopeChain = rnode.buildParentScopeChain(options.fromDirective || 0).extendMany('updateInputs', scopes);
-- var updateContext = rnode.makeUpdateContext();
-- updateContext.bequeathedScopeChains[rnode.getKey()] = rnode.updateGivenScopeChain(baseScopeChain, updateContext, options.fromDirective);
-
-- updateNodes(updateContext.nodesToUpdate, updateContext);
-+ });
-
-- return node;
-- },
-+ return $nodes[$node.key] = $node;
-+ };
-
-- buildParentScopeChain: function(directiveIndex){
-- var ancestors = $(Array.prototype.reverse.apply($(node).parents())).add(node);
-- var scopeBuildingContext = /*makeUpdateContext(node);*/js.create(react.commands, {
-- //todo: deprecate the suppressObservers flag
-- suppressObservers: true,
-- scopeChain: globalScopeChain
-- });
-- for(var whichAncestor = 0; whichAncestor < ancestors.length; whichAncestor++){
-- scopeBuildingContext.node = ancestors[whichAncestor];
-- var directives = makeRnode(scopeBuildingContext.node).directives;
-- scopeBuildingContext.scopeChain = scopeBuildingContext.scopeChain.extendForAnchorNames(directives.anchored && directives.anchored.inputs);
-
-- var pushScope = function(type, scope, options){
-- scopeBuildingContext.scopeChain = scopeBuildingContext.scopeChain.extend(type, scope, options);
-- };
-+ var hasRun = false, isRunning = false;
-+ var operation = {
-+ $: $,
-
-- for(var whichDirective = 0; whichDirective < directives.length; whichDirective++){
-- if(scopeBuildingContext.node === node && (directiveIndex||0) <= whichDirective){ break; }
-- // todo: this scopeChain will never be falsey, so what this used to do is broken. write a test
-- if(!scopeBuildingContext.scopeChain){ continue; }
-+ updateBranch: function(node, fromDirective){
-+ $(node).directives[fromDirective||'after'].dirtyBranch();
-+ },
-
-- if(directives[whichDirective].command === 'contain'){
-- scopeBuildingContext.scopeChain = emptyScopeChain;
-- continue;
-- }
-- if(js.among(['within', 'withinItem', 'bindItem'], directives[whichDirective].command)){
-- var directiveContext = js.create(scopeBuildingContext, {
-- directiveIndex: whichDirective,
-- pushScope: pushScope
-- });
-- directives[whichDirective].follow(directiveContext);
-- }
-+ run: function(){
-+ js.errorIf(hasRun, 'An operation cannot be run twice');
-+ isRunning = true;
-+ var repeatLimit = 100000;
-+ // iterating over the directivesToConsider list once isn't sufficient. Since considering a directive might extend the list, and order of elements in a hash is not guarenteed
-+ while(js.hasKeys(directivesToConsider)){
-+ js.errorIf(!repeatLimit--, 'we seem to be checking quite a few directives here...');
-+ for(var key in directivesToConsider){
-+ js.errorIf(consideredDirectives[key], 'internal error: react tried to consider the same directive twice');
-+ directivesToConsider[key].follow();
-+ consideredDirectives[key] = directivesToConsider[key];
-+ delete directivesToConsider[key];
- }
- }
-- return scopeBuildingContext.scopeChain;
-+ isRunning = false, hasRun = true;
-+ },
-+
-+ changed: function(object, keys){
-+ makeProxy(object).changed(keys);
-+ return operation;
- }
-+
- };
-
-- var makeDirective = function(index, tokens){
-+ return operation;
-+ };
-
-- var directive = {
-
-- isDirective: true,
-- rnode: rnode,
-- index: index,
-- command: tokens[0],
-- inputs: tokens.slice(1),
-+ /*
-+ * commands
-+ */
-
-- toString: function(){
-- return [directive.command].concat(directive.inputs).join(' ');
-- },
-+ js.extend(react.commands, {
-
-- follow: function(context){
-- try{
-- if(context.scopeChain.scope === doNotRecurse){
-- return doNotRecurse;
-- }
-- js.errorIf(!commands[directive.command], directive.command+' is not a valid react command');
-- commands[directive.command].apply(context, directive.inputs);
-- }catch (error){
-- js.errorIf(typeof context.directiveIndex !== 'number', 'You tried to follow a directive without supplying a directive index in the execution context');
-- js.log('Failure during React update: ', {
-- 'original error': error,
-- 'original stack': error.stack && error.stack.split ? error.stack.split('\n') : error.stack,
-- 'while processing node': context.node,
-- 'index of failed directive': context.directiveIndex,
-- 'directive call': directive.command+'('+directive.inputs.join(', ')+')',
-- 'scope chain description': context && context.scopeChain && context.scopeChain.describe(),
-- '(internal scope chain object) ': context.scopeChain
-- });
-- throw error;
-- }
-- }
-+ before: function(){
-+ if(this.$node.hasClass('reactItemTemplate')){
-+ this.dead();
-+ }
-+ this.ifDirty();
-+ },
-
-- };
-+ after: function(){ this.ifDirty(); },
-
-- return directive;
-- };
-+ anchored: function(/*token1, ...tokenN */){
-+ for(var i = 0; i < arguments.length; i++){
-+ var token = arguments[i];
-+ this.scopes[token] ? this.pushScope('anchor', this.scopes[token], {key:token}) : (this.dead(), debug('anchored directive failed to find a scope for the key "'+key+'"'));
-+ }
-+ this.ifDirty(function(){
-+ this.dirtyBranch();
-+ });
-+ },
-
-- var directiveStrings = (node.getAttribute('react')||'').split(matchers.directiveDelimiter);
-- var directiveArrays = js.map(directiveStrings, function(which, string){
-- return js.extend(js.trim(string).replace(matchers.negation, '!').split(matchers.space), {rnode: rnode});
-- });
-- if(directiveArrays[0] && directiveArrays[0][0] === 'anchored'){
-- var anchored = makeDirective('anchored', directiveArrays.shift());
-- }
-- directiveArrays = js.filter(directiveArrays, function(which, directiveArray){
-- return !!directiveArray[0];
-- });
-+ within: function(key){
-+ this._withScope('within', key);
-+ },
-
-- var directives = js.map(directiveArrays, function(which, directive){
-- return makeDirective(''+which, directive);
-- });
-+ _withScope: function(type, key){
-+ var scope = this.lookup(key);
-+ if(!scope){
-+ this.lookup(key);
-+ }
-+ scope ? this.pushScope(type, scope, {key:key}) : (this.dead(), debug('within directive failed to find a scope for the key "'+key+'"'));
-+ this.ifDirty(function(){
-+ this.dirtyBranch();
-+ });
-+ },
-
-- rnode.directives = js.extend(directives,{
-+ withinItem: function(key){
-+ // todo: add a rule to only allow getting items from last scope (check if key < scope.length?)
-+ // todo: add a rule to make sure the last scope object is an array
-+ js.errorIf(!js.isArray(this.getScope()), 'withinItem requires that the top-most scope in the scope chain be an array');
-+ js.errorIf(this.getScope().length-1 < +key, 'Tried to re-render a node for an index that no longer exists');
-+ js.errorIf(!this.getScope()[key], 'Could not find anything at key '+key+' on the scope object');
-+ this._withScope('withinItem', key);
-+ },
-
-- anchored: anchored,
-+ withinEach: function(){
-+ this._createItemNodes(function(index, itemNode){
-+ this.$(itemNode).directives.prepend(['withinItem', index]);
-+ });
-+ this.ifDirty(function(){
-+ this.dirtyBranch();
-+ });
-+ },
-
-- // todo: this takes an array, rather than a directive object. that seems odd, but directive objects aren't makable outside this scope
-- set: function(key, directive){
-- rnode.directives[key] = makeDirective(''+key, directive);
-- rnode.directives.write();
-- },
-+ bindItem: function(key, keyAlias, valueAlias){
-+ if(valueAlias === undefined){
-+ valueAlias = keyAlias;
-+ keyAlias = undefined;
-+ }
-
-- write: function(){
-- node.setAttribute('react', rnode.directives);
-- },
-+ // set up an item scope to be applied for each item
-+ // a new scope will be created with bindings for valueAlias and optionally for keyAlias
-+ var itemBindings = {};
-+ if(keyAlias !== undefined){
-+ itemBindings[keyAlias] = key;
-+ }
-+ itemBindings[valueAlias] = new Fallthrough(key);
-
-- toString: function(){
-- var directiveStrings = js.map(rnode.directives, function(which, directive){
-- if(!directive.isDirective && console){ console.log('oops - something\'s wrong with your directives'); }
-- return directive.toString();
-- });
-- if(rnode.directives.anchored){
-- if(!rnode.directives.anchored.isDirective && console){ console.log('oops - something\'s wrong with your directives'); }
-- directiveStrings.unshift(rnode.directives.anchored.toString());
-- }
-- return directiveStrings.join(', ');
-- },
-+ this.pushScope('bindItem', itemBindings, {key:key});
-
-- prepend: function(directive){
-- rnode.directives.unshift(directive.isDirective ? directive : makeDirective('0', directive));
-- js.map(rnode.directives, function(which, directive){
-- directive.index = ''+which;
-+ this.ifDirty(function(){
-+ this.dirtyBranch();
-+ });
-+ },
-+
-+ 'for': function(keyAlias, valueAlias){
-+ var aliases = Array.prototype.slice.call(arguments);
-+ this.ifDirty(function(){
-+ // todo: return here (and everywhere else) if collection is undefined. test for this
-+ this._createItemNodes(function(index, itemNode){
-+ this.$(itemNode).directives.prepend( ['bindItem', index].concat(aliases) );
- });
-- rnode.directives.write();
-+ this.dirtyBranch();
-+ });
-+ },
-+
-+ _createItemNodes: function(callback){
-+ var $loopChildren = jQuery(this.node).children();
-+ js.errorIf($loopChildren.length < 2, 'looping nodes must contain at least 2 children - one item template and one results container');
-+ var $itemTemplate = $loopChildren.first();
-+ //js.errorIf(this.$($itemTemplate[0]).directives[0].join(' ') !== 'itemTemplate', 'the item template must declare itself with an item directive');
-+ $itemTemplate.addClass('reactItemTemplate');
-+ var $resultsContainer = $($loopChildren[1]);
-+ var $resultsContents = $resultsContainer.children();
-+
-+ // todo: ignore binding scopes when looking for scope to iterate over
-+ var collection = this.getScope();
-+ if(!js.isArray(collection)){
-+ this.dead();
-+ debug('no collection found');
-+ return;
-+ }
-+ if(this.getScopeChain().anchorKey){
-+ this.lookup('length');
- }
-
-- });
-+ var itemNodes = [];
-+ for(var i = 0; i < collection.length; i++){
-+ var itemNode = $resultsContents[i];
-+ if(!itemNode){
-+ itemNode = $itemTemplate.clone().removeClass('reactItemTemplate')[0];
-+ callback.call(this, i, itemNode);
-+ this.$(itemNode).directives.before.dirtyBranch();
-+ }
-+ itemNodes.push(itemNode);
-+ }
-+ if(collection.length !== $resultsContents.length){
-+ // we set innerHTML here to prevent jQuery fron detaching all event handlers (automatic in an .html() call)
-+ $resultsContainer[0].innerHTML = '';
-+ $resultsContainer.html(itemNodes);
-+ }
-+ },
-
-- return rnode;
-- };
-+ contain: function(key){
-+ // using innerHTML to clear the node because the jQuery convenience functions unbind event handlers. This would be an unexpected side effect for most React user consumption cases.
-
-- var makeUpdateContext = function(node){
-- var updateContext = js.create(react.commands, {
-- root: node,
-- nodesToUpdate: makeArrayFromArrayLikeObject(node.querySelectorAll('[react]')),
-- bequeathedScopeChains: {},
-- loopItemTemplates: {},
-+ this.ifDirty(function(){
-+ this.node.innerHTML = '';
-+ var insertion = this.lookup(key);
-+ // if the insertion is a node, use the dom appending method, but insert other items as text
-+ jQuery(this.node)[insertion && insertion.nodeType ? 'append' : 'text'](insertion);
-+ // note: .dead() can't happen outside ifDirty() because disabling mutation should only happen when the branch is inserted, not when building an initial scope chain
-+ this.$node.directives.after.dead();
-+ });
-+ this.$node.directives.after.resetScopeChain();
-+ },
-
-- makeDirectiveContext: function(node, directiveIndex, scopeChain){
-- var directiveContext = js.create(updateContext, {
-- node: node,
-- directiveIndex: directiveIndex,
-- scopeChain: scopeChain,
-- pushScope: function(type, scope, options){
-- directiveContext.scopeChain = scopeChain.extend(type, scope, options);
-+ 'if': function(condition){
-+ var conditional = this.lookup(condition);
-+ if(!conditional){ this.dead(); }
-+ this.ifDirty(function(){
-+ $(this.node)[conditional ? 'removeClass' : 'addClass']('reactConditionallyHidden');
-+ this._conditionalShow(conditional);
-+ this.dirtyBranch();
-+ });
-+ },
-+
-+ _conditionalShow: function(conditional){
-+ jQuery(this.node)[conditional ? 'show' : 'hide']();
-+ },
-+
-+ showIf: function(condition){
-+ this.ifDirty(function(){
-+ this._conditionalShow(this.lookup(condition));
-+ });
-+ },
-+
-+ visIf: function(condition){
-+ this.ifDirty(function(){
-+ jQuery(this.node).css('visibility', this.lookup(condition) ? 'visible' : 'hidden');
-+ });
-+ },
-+
-+ classIf: function(conditionKey, nameKey){
-+ this.ifDirty(function(){
-+ this.node.classIfs = this.node.classIfs || {};
-+ var condition = this.lookup(conditionKey);
-+ var className;
-+ var persistence = conditionKey + ' ' + nameKey;
-+ if(condition){
-+ className = this.lookup(nameKey);
-+ if(className){
-+ $(this.node).addClass(className);
-+ this.node.classIfs[persistence] = className;
- }
-- });
-- return directiveContext;
-- }
-- });
-- return updateContext;
-- };
-+ } else {
-+ className = this.node.classIfs[persistence] || this.lookup(nameKey);
-+ if(className){
-+ $(this.node).removeClass(className);
-+ delete this.node.classIfs[persistence];
-+ }
-+ }
-+ });
-+ },
-
-- var makeListener = function(object, key, listenerString){
-- var listener = listenerString.split(' ');
-- var rnode = makeRnode(react.nodes[listener[0]]);
-- var directiveIndex = +listener[1];
--
-- return {
-- object: object,
-- key: key,
-- rnode: rnode,
-- node: rnode.node,
-- directiveIndex: directiveIndex,
-- prefix: listener[2],
-- directive: rnode.directives[directiveIndex],
-- scopeChain: rnode.buildParentScopeChain(directiveIndex),
-- isValid: function(){
-- // ignore the object if it's not in the same path that lead to registration of a listener
-- var details = this.scopeChain.lookup(this.prefix+this.key, {details: true, suppressObservers: true});
-- return details.matchingBaseObject === this.object || details.failed && this.scopeChain.contains(this.object);
-- },
-- check: function(){
-- if(!this.isValid()){ return; }
--
-- // todo: bindItem is needed here but won't work until the registration is made on the array element it's bound to. something like
-- js.errorIf(this.directive.command === 'bindItem', 'you need recalculations for bindItem (when the key was an itemAlias), but those aren\'t implemented yet');
-- if(js.among(['within', 'withinEach', 'withinItem', 'for', 'if'], this.directive.command)){
-- // todo: loopKey probably won't work, and maybe withinEach either
-- rnode.updateTree({
-- node: this.node,
-- fromDirective: this.directiveIndex
-- });
-- return;
-+ attr: function(name, value){
-+ js.errorIf(arguments.length !== 2, 'the attr directive requires 2 arguments');
-+ this.ifDirty(function(){
-+
-+ name = this.lookup(name);
-+ value = this.lookup(value);
-+
-+ if(!js.among(['string', 'number', 'undefined'], typeof name)){
-+ js.log('bad attr name: ', name);
-+ js.error('expected attr name token ' + name + ' to resolve to a string, a number, null, or undefined, not ' + typeof name);
-+ }else if(!js.among(['string', 'number', 'undefined'], typeof value)){
-+ js.log('bad attr value: ', value);
-+ js.error('expected attr value token ' + value + ' to resolve to a string, a number, null, or undefined, not ' + typeof value);
- }
-
-- var updateContext = makeUpdateContext(this.node);
--// todo: consolidate all these updateContext object creations
--// todo: these last two probably don't belong here. they were added to keep .enqueueNodes() from erroring.
--// todo: this shouldn't need a pushscope method. it only uses it because directive functions access it. factor this out
-- var directiveContext = updateContext.makeDirectiveContext(this.node, this.directiveIndex, this.scopeChain);
-- this.directive.follow(directiveContext);
-- updateNodes(updateContext.nodesToUpdate, updateContext);
-- }
-- };
-- };
-+ jQuery(this.node).attr(name, value);
-+ });
-+ },
-+
-+ attrIf: function(condition, name, value){
-+ this.ifDirty(function(){
-+ if(this.lookup(condition)){
-+ $(this.node).attr(this.lookup(name), this.lookup(value));
-+ } else {
-+ $(this.node).removeAttr(this.lookup(name));
-+ }
-+ });
-+ },
-+
-+ checkedIf: function(condition){
-+ $(this.node).attr('checked', this.lookup(condition));
-+ }
-+
-+ });
-+
-+ /*
-+ * Exporting library
-+ */
-
- window.react = react;
-
-diff --git a/tests/.#tests.js b/tests/.#tests.js
-deleted file mode 120000
-index 2948cff..0000000
---- a/tests/.#tests.js
-+++ /dev/null
-@@ -1 +0,0 @@
--marcus@Sylvie.local.12259
-\ No newline at end of file
-diff --git a/todo b/todo
-index b773f88..e7923d3 100644
---- a/todo
-+++ b/todo
-@@ -7,9 +7,19 @@
- - loop item template must be labeled
- - no loop items container node (all results get appended after template node)
- - must declare a node as a react widget
-+- deprecate .set()
-+
-+- data-bound="rootScope"
-
- --- random todos ---
--- change queryselectorall to $.find() for compatibility
-+- get rid of _conditionalShow(), rely on classes
-+- get rid of 'do not recurse'
-+- switch from 'pre/postmutate' object to multiple functions
-+- get rid of .scopes and .nodes
-+- get rid of getScopeKey()
-+- change queryselectorall to $.find() for compatibility (calling Array.prototype.slice.call on the results of a call to .querySelectorAll blows up in IE)
-+ - also see if there's a more efficient way to build an array other than iterating over the array like object
-+- todo: without using .data(), IE copies expando properties over, breaking loop behaviors and other cloning operations. write a test for the failing behavior and implement replacement to .data()
- - Make lookups fail when property isn't defined on the object ('prop' in obj), rather than when it has a value of undefined
- - Make a changed event on any numeric property of an array result in a changed event on the associated loop directives
- - make every node in a loop listen to it's associated key
-@@ -18,8 +28,62 @@
- - what if a listened-to property is stored somewhere up the prototype chain? how can listeners be registered there?
- - add a jsonified output of any object in debug mode
- - attrIf needs the same persistence behavior as classIf, write test for it first
-+- make inherited scope chains materialize only when used by a call to lookup()
-+- keep track of which directive shave already had all subtrees queued for visiting, so visitBranch() can pass for repeat calls
-+- after an if directive evaluates to false, setting it to true and activating the listener should update subtree
-+- scope observers per node-object anchoring, for easy cleanup of memory references
-+- clean up any pre-existing observers (lookup time? observation time?)
-+- give a warning when dot access lookup fails
-+- treat lookup failures on dot access and within directives as if they were if blocks (by turning them off)? Case: if you negate a failed lookup, do you want it to be truthy?
-+- make all observers write to a single string in some serialization format
-+
-+--- bugs ---
-+- if you pass scope objects into update(), and the target node is anchored, even the (unanchored) input objects get observers set up
-+- negation breaks for dot access
-+
-+--- safeguards ---
-+- in createItemNodes, don't allow looping over static native objects (like strings - this is almost certainly an error)
-+
-+--- optimizations ---
-+- possible: when selecting react nodes to visit, generate a :not() selector that excludes children of other react nodes. works in all but IE<=8 (http://www.quirksmode.org/css/contents.html), and is no slower there (since we have to visit all nodes anyway). have to check that selection behavior is same as jquery though, and doesn't consider parents of the selection context.
-+- earmark any nodes that have been found in a querySelectorAll call, so we don't have to add their children again when they show up in the 'branchesToConsider' list
-+
-+--- shortcuts ---
-+- in createItemNodes, we simply put an observer on the length property that results in a complete re-render of the looping directive if ever a change in length is noticed
-+
-+--- improvements ---
-+- clean up a directive's observers when it gets bound to new properties
-+- in pre-lookups, restrict all keys to valid alphanumerics
-+- allow a containEach directive?
-+
-+--- refactors ---
-+- remove .anchorKey from scope chains
-+- factor .lookup() into .lookup() and .lookupDetails(), then deprecate options object
-+- don't think we need matchingBaseObject in the return details of a lookup, maybe just check if it's contained in the scope chain
-+- prefix probably doesn't need to be stored in scopeChain, it can be passed via potentialObservers. this would allow .observe() to be taken off the scopeChain object
-+- deprecate options from makeScopeChain
-+- automatically lookup the tokens provided to a command. offer the option of defining a resolve() function when users want to do the lookup work manually
-+- resolve ambiguous language around 'observer' and 'listener'
-+- make listener objects a universal property of each directive, and give them a 'verified' or 'checked' status to indicate the validity of their object/key path
-
- --- write tests ---
--- empty react attribute string doesnt break univers
-+- when a directive is marked for consideration, then removed from the tree by a contain or withinEach directive, the node still works if another directive swapps it in. probably doesnt matter since substituted nodes don't get recursed onto
-+- empty react attribute string doesnt break universe
- - attributes should be removed if property is undefined, even after deletion
- - when a within command fails lookup, the new scope is not added to the chain
-+- cannot pass jquery objects to react.update() that have more than 1 element
-+- test that every directive can be run on an undefined object
-+- 'anchored', 'within', 'withinItem', and 'bindItem' all need their subtrees rerendered on change
-+- nodes within a contain directive don't inherit a scope chain
-+- ideally, lookups dont fall through above a contain or anchored directive
-+- updates to properties that had been listened to with bindItem might not recalculate (since they're listening to the wrong scope object). maybe this just applies when the name being bound to changes
-+- within can be run on undefined objects (should this turn the branch off like 'if', or just not add the scope to the chain?)
-+- can anchor one node to multiple scopes
-+- verify that responses to change events don't result in new observers
-+- can use dot access on bound items
-+- updates to items in a list (associated with the key or value of a bindItem) change appropriately. consider listener.check(), which might not have updated unless the fallthrough registration just worked
-+
-+--- asdf ---
-+! make observers lazy-register, and only if in mutation mode
-+- multiple calls to follow() probably follow over and over
-+- directive.getParent() seems to run its while loop no less than twice
View
1  version_notes
@@ -6,6 +6,7 @@ local
- (internal) js.* iterators now run in the context of the input collection, unless you pass a context argument in after the block.
- (internal) js.* iterators can accept strings and arrays to be transformed into a block
- (internal) anchors now stored on node proxies, rather than in central hash
+- (internal) annotating objects via bound.prototype closure scope, rather than with string serializations
1.3
---
Please sign in to comment.
Something went wrong with that request. Please try again.