diff --git a/src/parse/converters/mustache/content.js b/src/parse/converters/mustache/content.js
index 365fab658f..2e29e8a8c6 100644
--- a/src/parse/converters/mustache/content.js
+++ b/src/parse/converters/mustache/content.js
@@ -4,6 +4,7 @@ import handlebarsBlockCodes from 'parse/converters/mustache/handlebarsBlockCodes
import 'legacy';
var indexRefPattern = /^\s*:\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/,
+ keyIndexRefPattern = /^\s*,\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/,
arrayMemberPattern = /^[0-9][1-9]*$/,
handlebarsBlockPattern = new RegExp( '^(' + Object.keys( handlebarsBlockCodes ).join( '|' ) + ')\\b' ),
legalReference;
@@ -181,9 +182,15 @@ export default function ( parser, delimiterType ) {
];
}
- // optional index reference
+ // optional index and key references
if ( i = parser.matchPattern( indexRefPattern ) ) {
- mustache.i = i;
+ let extra;
+
+ if ( extra = parser.matchPattern( keyIndexRefPattern ) ) {
+ mustache.i = i + ',' + extra;
+ } else {
+ mustache.i = i;
+ }
}
return mustache;
diff --git a/src/shared/keypaths/decode.js b/src/shared/keypaths/decode.js
index bdf036fadd..38371b4ec7 100644
--- a/src/shared/keypaths/decode.js
+++ b/src/shared/keypaths/decode.js
@@ -1,6 +1,11 @@
import isNumeric from 'utils/isNumeric';
export default function decodeKeypath ( keypath ) {
- var value = keypath.slice( 1 );
- return isNumeric( value ) ? +value : value;
-}
\ No newline at end of file
+ var value = keypath.slice( 2 );
+
+ if ( keypath[1] === 'i' ) {
+ return isNumeric( value ) ? +value : value;
+ } else {
+ return value;
+ }
+}
diff --git a/src/shared/parameters/ComplexParameter.js b/src/shared/parameters/ComplexParameter.js
index 35e7df1875..e93a49ddbd 100644
--- a/src/shared/parameters/ComplexParameter.js
+++ b/src/shared/parameters/ComplexParameter.js
@@ -40,8 +40,8 @@ ComplexParameter.prototype = {
this.dirty = false;
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ rebind: function ( oldKeypath, newKeypath ) {
+ this.fragment.rebind( oldKeypath, newKeypath );
},
unbind: function () {
diff --git a/src/shared/resolveRef.js b/src/shared/resolveRef.js
index 62b2a476f4..f70fb89977 100644
--- a/src/shared/resolveRef.js
+++ b/src/shared/resolveRef.js
@@ -5,7 +5,6 @@ import resolveAncestorRef from 'shared/resolveAncestorRef';
export default function resolveRef ( ractive, ref, fragment, isParentLookup ) {
var context,
key,
- index,
keypath,
parentValue,
hasContextChain,
@@ -61,15 +60,6 @@ export default function resolveRef ( ractive, ref, fragment, isParentLookup ) {
hasContextChain = true;
fragment = ractive.component.parentFragment;
- // Special case - index refs
- if ( fragment.indexRefs && ( index = fragment.indexRefs[ ref ] ) !== undefined ) {
- // Create an index ref binding, so that it can be rebound letter if necessary.
- // It doesn't have an alias since it's an implicit binding, hence `...[ ref ] = ref`
- ractive.component.indexRefBindings[ ref ] = ref;
- ractive.viewmodel.set( ref, index, { silent: true } );
- return;
- }
-
keypath = resolveRef( ractive.parent, ref, fragment, true );
if ( keypath ) {
diff --git a/src/virtualdom/Fragment.js b/src/virtualdom/Fragment.js
index 22b67ba453..ae299182f2 100644
--- a/src/virtualdom/Fragment.js
+++ b/src/virtualdom/Fragment.js
@@ -34,9 +34,19 @@ Fragment.prototype = {
getValue: getValue,
init: init,
rebind: rebind,
+ registerIndexRef: function( idx ) {
+ var idxs = this.registeredIndexRefs;
+ if ( idxs.indexOf( idx ) === -1 ) {
+ idxs.push( idx );
+ }
+ },
render: render,
toString: toString,
unbind: unbind,
+ unregisterIndexRef: function( idx ) {
+ var idxs = this.registeredIndexRefs;
+ idxs.splice( idxs.indexOf( idx ), 1 );
+ },
unrender: unrender
};
diff --git a/src/virtualdom/Fragment/prototype/init.js b/src/virtualdom/Fragment/prototype/init.js
index da930cbb62..ccb3e3c372 100644
--- a/src/virtualdom/Fragment/prototype/init.js
+++ b/src/virtualdom/Fragment/prototype/init.js
@@ -1,5 +1,3 @@
-import types from 'config/types';
-import create from 'utils/create';
import createItem from 'virtualdom/Fragment/prototype/init/createItem';
export default function Fragment$init ( options ) {
@@ -14,19 +12,9 @@ export default function Fragment$init ( options ) {
this.root = options.root;
this.pElement = options.pElement;
this.context = options.context;
-
- // If parent item is a section, this may not be the only fragment
- // that belongs to it - we need to make a note of the index
- if ( this.owner.type === types.SECTION ) {
- this.index = options.index;
- }
-
- // index references (the 'i' in {{#section:i}}...{{/section}}) need to cascade
- // down the tree
- this.indexRefs = create( parentFragment ? parentFragment.indexRefs : null );
- if ( options.indexRef ) {
- this.indexRefs[ options.indexRef ] = options.index;
- }
+ this.index = options.index;
+ this.key = options.key;
+ this.registeredIndexRefs = [];
// Time to create this fragment's child items
diff --git a/src/virtualdom/Fragment/prototype/rebind.js b/src/virtualdom/Fragment/prototype/rebind.js
index 81447e8eca..8a957422f5 100644
--- a/src/virtualdom/Fragment/prototype/rebind.js
+++ b/src/virtualdom/Fragment/prototype/rebind.js
@@ -1,21 +1,13 @@
import assignNewKeypath from 'shared/keypaths/assignNew';
-export default function Fragment$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
-
- if ( newIndex !== undefined ) {
- this.index = newIndex;
- }
+export default function Fragment$rebind ( oldKeypath, newKeypath ) {
// assign new context keypath if needed
assignNewKeypath( this, 'context', oldKeypath, newKeypath );
- if ( this.indexRefs && this.indexRefs[ indexRef ] !== undefined ) {
- this.indexRefs[ indexRef ] = newIndex;
- }
-
this.items.forEach( item => {
if ( item.rebind ) {
- item.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ item.rebind( oldKeypath, newKeypath );
}
});
}
diff --git a/src/virtualdom/items/Component/prototype/rebind.js b/src/virtualdom/items/Component/prototype/rebind.js
index 730ff099a1..cb9b868f16 100644
--- a/src/virtualdom/items/Component/prototype/rebind.js
+++ b/src/virtualdom/items/Component/prototype/rebind.js
@@ -1,9 +1,5 @@
-import runloop from 'global/runloop';
-
-export default function Component$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
- var childInstance = this.instance,
- indexRefAlias,
- query;
+export default function Component$rebind ( oldKeypath, newKeypath ) {
+ var query;
this.resolvers.forEach( rebind );
@@ -13,16 +9,11 @@ export default function Component$rebind ( indexRef, newIndex, oldKeypath, newKe
}
}
- if ( indexRefAlias = this.indexRefBindings[ indexRef ] ) {
- runloop.addViewmodel( childInstance.viewmodel );
- childInstance.viewmodel.set( indexRefAlias, newIndex );
- }
-
if ( query = this.root._liveComponentQueries[ '_' + this.name ] ) {
query._makeDirty();
}
function rebind ( x ) {
- x.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ x.rebind( oldKeypath, newKeypath );
}
}
diff --git a/src/virtualdom/items/Element/Attribute/prototype/rebind.js b/src/virtualdom/items/Element/Attribute/prototype/rebind.js
index a8fd1cd30d..84a48b0c17 100644
--- a/src/virtualdom/items/Element/Attribute/prototype/rebind.js
+++ b/src/virtualdom/items/Element/Attribute/prototype/rebind.js
@@ -1,5 +1,5 @@
-export default function Attribute$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
+export default function Attribute$rebind ( oldKeypath, newKeypath ) {
if ( this.fragment ) {
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ this.fragment.rebind( oldKeypath, newKeypath );
}
}
diff --git a/src/virtualdom/items/Element/Binding/RadioNameBinding.js b/src/virtualdom/items/Element/Binding/RadioNameBinding.js
index 4243f8a263..1a585fea88 100644
--- a/src/virtualdom/items/Element/Binding/RadioNameBinding.js
+++ b/src/virtualdom/items/Element/Binding/RadioNameBinding.js
@@ -53,10 +53,10 @@ var RadioNameBinding = Binding.extend({
}
},
- rebound: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebound: function ( oldKeypath, newKeypath ) {
var node;
- Binding.prototype.rebound.call( this, indexRef, newIndex, oldKeypath, newKeypath );
+ Binding.prototype.rebound.call( this, oldKeypath, newKeypath );
if ( node = this.element.node ) {
node.name = '{{' + this.keypath + '}}';
diff --git a/src/virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js b/src/virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js
index 1f6ea8614f..3f81d4b995 100644
--- a/src/virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js
+++ b/src/virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js
@@ -36,8 +36,8 @@ ConditionalAttribute.prototype = {
this.element.bubble();
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ rebind: function ( oldKeypath, newKeypath ) {
+ this.fragment.rebind( oldKeypath, newKeypath );
},
render: function ( node ) {
diff --git a/src/virtualdom/items/Element/Decorator/_Decorator.js b/src/virtualdom/items/Element/Decorator/_Decorator.js
index 797d70336c..b20d22a69a 100644
--- a/src/virtualdom/items/Element/Decorator/_Decorator.js
+++ b/src/virtualdom/items/Element/Decorator/_Decorator.js
@@ -104,9 +104,9 @@ Decorator.prototype = {
}
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
if ( this.fragment ) {
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ this.fragment.rebind( oldKeypath, newKeypath );
}
},
diff --git a/src/virtualdom/items/Element/EventHandler/prototype/rebind.js b/src/virtualdom/items/Element/EventHandler/prototype/rebind.js
index 2d61345861..a5a2b85757 100644
--- a/src/virtualdom/items/Element/EventHandler/prototype/rebind.js
+++ b/src/virtualdom/items/Element/EventHandler/prototype/rebind.js
@@ -1,4 +1,4 @@
-export default function EventHandler$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
+export default function EventHandler$rebind ( oldKeypath, newKeypath ) {
var fragment;
if ( this.method ) {
fragment = this.element.parentFragment;
@@ -16,6 +16,6 @@ export default function EventHandler$rebind ( indexRef, newIndex, oldKeypath, ne
}
function rebind ( thing ) {
- thing && thing.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ thing && thing.rebind( oldKeypath, newKeypath );
}
}
diff --git a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js
index e8334b9ffd..6b879371ee 100644
--- a/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js
+++ b/src/virtualdom/items/Element/EventHandler/shared/genericHandler.js
@@ -1,13 +1,23 @@
+import findIndexRefs from 'virtualdom/items/shared/Resolvers/findIndexRefs';
+
export default function genericHandler ( event ) {
- var storage, handler;
+ var storage, handler, indices, index = {};
storage = this._ractive;
handler = storage.events[ event.type ];
+ if ( indices = findIndexRefs( handler.element.parentFragment ) ) {
+ let k, ref;
+ for ( k in indices.refs ) {
+ ref = indices.refs[k];
+ index[ ref.ref.n ] = ref.ref.t === 'k' ? ref.fragment.key : ref.fragment.index;
+ }
+ }
+
handler.fire({
node: this,
original: event,
- index: storage.index,
+ index: index,
keypath: storage.keypath,
context: storage.root.get( storage.keypath )
});
diff --git a/src/virtualdom/items/Element/prototype/init.js b/src/virtualdom/items/Element/prototype/init.js
index baa73037c8..842689b256 100644
--- a/src/virtualdom/items/Element/prototype/init.js
+++ b/src/virtualdom/items/Element/prototype/init.js
@@ -35,6 +35,7 @@ export default function Element$init ( options ) {
this.root = ractive = parentFragment.root;
this.index = options.index;
+ this.key = options.key;
this.name = enforceCase( template.e );
diff --git a/src/virtualdom/items/Element/prototype/rebind.js b/src/virtualdom/items/Element/prototype/rebind.js
index c408281a41..bfb01b3e09 100644
--- a/src/virtualdom/items/Element/prototype/rebind.js
+++ b/src/virtualdom/items/Element/prototype/rebind.js
@@ -1,6 +1,6 @@
import assignNewKeypath from 'shared/keypaths/assignNew';
-export default function Element$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
+export default function Element$rebind ( oldKeypath, newKeypath ) {
var i, storage, liveQueries, ractive;
if ( this.attributes ) {
@@ -38,13 +38,9 @@ export default function Element$rebind ( indexRef, newIndex, oldKeypath, newKeyp
// adjust keypath if needed
assignNewKeypath( storage, 'keypath', oldKeypath, newKeypath );
-
- if ( indexRef != undefined ) {
- storage.index[ indexRef ] = newIndex;
- }
}
function rebind ( thing ) {
- thing.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ thing.rebind( oldKeypath, newKeypath );
}
}
diff --git a/src/virtualdom/items/Element/prototype/render.js b/src/virtualdom/items/Element/prototype/render.js
index 5a4d242d40..9d48c472f0 100644
--- a/src/virtualdom/items/Element/prototype/render.js
+++ b/src/virtualdom/items/Element/prototype/render.js
@@ -64,7 +64,6 @@ export default function Element$render () {
value: {
proxy: this,
keypath: getInnerContext( this.parentFragment ),
- index: create( this.parentFragment.indexRefs ),
events: create( null ),
root: root
}
diff --git a/src/virtualdom/items/Partial/_Partial.js b/src/virtualdom/items/Partial/_Partial.js
index 08b5ec7528..01044640ac 100644
--- a/src/virtualdom/items/Partial/_Partial.js
+++ b/src/virtualdom/items/Partial/_Partial.js
@@ -82,13 +82,13 @@ Partial.prototype = {
return this.fragment.getValue();
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
// named partials aren't bound, so don't rebind
if ( !this.isNamed ) {
- rebind.call( this, indexRef, newIndex, oldKeypath, newKeypath );
+ rebind.call( this, oldKeypath, newKeypath );
}
-
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+
+ this.fragment.rebind( oldKeypath, newKeypath );
},
render: function () {
diff --git a/src/virtualdom/items/Section/_Section.js b/src/virtualdom/items/Section/_Section.js
index 68989fa91e..f747044d82 100644
--- a/src/virtualdom/items/Section/_Section.js
+++ b/src/virtualdom/items/Section/_Section.js
@@ -20,7 +20,7 @@ import update from 'virtualdom/items/Section/prototype/update';
var Section = function ( options ) {
this.type = types.SECTION;
- this.subtype = options.template.n;
+ this.subtype = this.currentSubtype = options.template.n;
this.inverted = this.subtype === types.SECTION_UNLESS;
@@ -31,6 +31,12 @@ var Section = function ( options ) {
this.fragmentsToRender = [];
this.fragmentsToUnrender = [];
+ if ( options.template.i ) {
+ this.indexRefs = options.template.i.split(',').map( ( k, i ) => {
+ return { n: k, t: i === 0 ? 'k' : 'i' };
+ });
+ }
+
this.renderedFragments = [];
this.length = 0; // number of times this section is rendered
@@ -47,6 +53,15 @@ Section.prototype = {
findComponent: findComponent,
findNextNode: findNextNode,
firstNode: firstNode,
+ getIndexRef: function( name ) {
+ if ( this.indexRefs ) {
+ for ( let ref of this.indexRefs ) {
+ if ( ref.n === name ) {
+ return ref;
+ }
+ }
+ }
+ },
getValue: Mustache.getValue,
shuffle: shuffle,
rebind: rebind,
diff --git a/src/virtualdom/items/Section/prototype/rebind.js b/src/virtualdom/items/Section/prototype/rebind.js
index 6d18120276..77df626965 100644
--- a/src/virtualdom/items/Section/prototype/rebind.js
+++ b/src/virtualdom/items/Section/prototype/rebind.js
@@ -1,14 +1,5 @@
import Mustache from 'virtualdom/items/shared/Mustache/_Mustache';
-import types from 'config/types';
-export default function( indexRef, newIndex, oldKeypath, newKeypath ) {
- var ref, idx;
-
- if ( indexRef !== undefined || this.currentSubtype !== types.SECTION_EACH ) {
- ref = indexRef;
- idx = newIndex;
- }
-
- // If the new index belonged to us, we'd be shuffling instead
- Mustache.rebind.call( this, ref, idx, oldKeypath, newKeypath );
+export default function( oldKeypath, newKeypath ) {
+ Mustache.rebind.call( this, oldKeypath, newKeypath );
}
diff --git a/src/virtualdom/items/Section/prototype/setValue.js b/src/virtualdom/items/Section/prototype/setValue.js
index f026df38b5..a7f17aae46 100644
--- a/src/virtualdom/items/Section/prototype/setValue.js
+++ b/src/virtualdom/items/Section/prototype/setValue.js
@@ -36,8 +36,7 @@ export default function Section$setValue ( value ) {
template: this.template.f,
root: this.root,
pElement: this.pElement,
- owner: this,
- indexRef: this.template.i
+ owner: this
};
this.fragmentsToCreate.forEach( index => {
@@ -65,6 +64,28 @@ export default function Section$setValue ( value ) {
this.updating = false;
}
+function changeCurrentSubtype ( section, value, obj ) {
+ if ( value === types.SECTION_EACH ) {
+ // make sure ref type is up to date for key or value indices
+ if ( section.indexRefs && section.indexRefs[0] ) {
+ let ref = section.indexRefs[0];
+
+ // when switching flavors, make sure the section gets updated
+ if ( ( obj && ref.t === 'i' ) || ( !obj && ref.t === 'k' ) ) {
+ // if switching from object to list, unbind all of the old fragments
+ if ( !obj ) {
+ section.length = 0;
+ section.fragmentsToUnrender = section.fragments.slice( 0 );
+ section.fragmentsToUnrender.forEach( f => f.unbind() );
+ }
+ }
+
+ ref.t = obj ? 'k' : 'i';
+ }
+ }
+
+ section.currentSubtype = value;
+}
function reevaluateSection ( section, value ) {
var fragmentOptions = {
@@ -78,8 +99,6 @@ function reevaluateSection ( section, value ) {
// TODO can this be optimised? i.e. pick an reevaluateSection function during init
// and avoid doing this each time?
if ( section.subtype ) {
- section.currentSubtype = section.subtype;
-
switch ( section.subtype ) {
case types.SECTION_IF:
return reevaluateConditionalSection( section, value, false, fragmentOptions );
@@ -95,6 +114,7 @@ function reevaluateSection ( section, value ) {
case types.SECTION_EACH:
if ( isObject( value ) ) {
+ changeCurrentSubtype( section, section.subtype, true );
return reevaluateListObjectSection( section, value, fragmentOptions );
}
@@ -107,7 +127,7 @@ function reevaluateSection ( section, value ) {
// Ordered list section
if ( section.ordered ) {
- section.currentSubtype = types.SECTION_EACH;
+ changeCurrentSubtype( section, types.SECTION_EACH, false );
return reevaluateListSection( section, value, fragmentOptions );
}
@@ -115,17 +135,17 @@ function reevaluateSection ( section, value ) {
if ( isObject( value ) || typeof value === 'function' ) {
// Index reference indicates section should be treated as a list
if ( section.template.i ) {
- section.currentSubtype = types.SECTION_EACH;
+ changeCurrentSubtype( section, types.SECTION_EACH, true );
return reevaluateListObjectSection( section, value, fragmentOptions );
}
// Otherwise, object provides context for contents
- section.currentSubtype = types.SECTION_WITH;
+ changeCurrentSubtype( section, types.SECTION_WITH, false );
return reevaluateContextSection( section, fragmentOptions );
}
// Conditional section
- section.currentSubtype = types.SECTION_IF;
+ changeCurrentSubtype( section, types.SECTION_IF, false );
return reevaluateConditionalSection( section, value, false, fragmentOptions );
}
@@ -154,10 +174,6 @@ function reevaluateListSection ( section, value, fragmentOptions ) {
fragmentOptions.context = section.keypath + '.' + i;
fragmentOptions.index = i;
- if ( section.template.i ) {
- fragmentOptions.indexRef = section.template.i;
- }
-
fragment = new Fragment( fragmentOptions );
section.fragmentsToRender.push( section.fragments[i] = fragment );
}
@@ -169,7 +185,7 @@ function reevaluateListSection ( section, value, fragmentOptions ) {
}
function reevaluateListObjectSection ( section, value, fragmentOptions ) {
- var id, i, hasKey, fragment, changed;
+ var id, i, hasKey, fragment, changed, deps;
hasKey = section.hasKey || ( section.hasKey = {} );
@@ -178,28 +194,42 @@ function reevaluateListObjectSection ( section, value, fragmentOptions ) {
while ( i-- ) {
fragment = section.fragments[i];
- if ( !( fragment.index in value ) ) {
+ if ( !( fragment.key in value ) ) {
changed = true;
fragment.unbind();
section.fragmentsToUnrender.push( fragment );
section.fragments.splice( i, 1 );
- hasKey[ fragment.index ] = false;
+ hasKey[ fragment.key ] = false;
+ }
+ }
+
+ // notify any dependents about changed indices
+ i = section.fragments.length;
+ while ( i-- ) {
+ fragment = section.fragments[i];
+
+ if ( fragment.index !== i ){
+ fragment.index = i;
+ if ( deps = fragment.registeredIndexRefs ) {
+ for ( let d of deps ) {
+ // the keypath doesn't actually matter here as it won't have changed
+ d.rebind( '', '' );
+ }
+ }
}
}
// add any that haven't been created yet
+ i = section.fragments.length;
for ( id in value ) {
if ( !hasKey[ id ] ) {
changed = true;
fragmentOptions.context = section.keypath + '.' + id;
- fragmentOptions.index = id;
-
- if ( section.template.i ) {
- fragmentOptions.indexRef = section.template.i;
- }
+ fragmentOptions.key = id;
+ fragmentOptions.index = i++;
fragment = new Fragment( fragmentOptions );
@@ -214,7 +244,7 @@ function reevaluateListObjectSection ( section, value, fragmentOptions ) {
}
function reevaluateConditionalContextSection ( section, value, fragmentOptions ) {
- if(value){
+ if ( value ) {
return reevaluateContextSection( section, fragmentOptions );
} else {
return removeSectionFragments( section );
@@ -263,7 +293,7 @@ function reevaluateConditionalSection ( section, value, inverted, fragmentOption
if ( doRender ) {
if ( !section.length ) {
// no change to context stack
- fragmentOptions.index = undefined;
+ fragmentOptions.index = 0;
fragment = new Fragment( fragmentOptions );
section.fragmentsToRender.push( section.fragments[0] = fragment );
diff --git a/src/virtualdom/items/Section/prototype/shuffle.js b/src/virtualdom/items/Section/prototype/shuffle.js
index 3d3ad0e7fb..557bcb911a 100644
--- a/src/virtualdom/items/Section/prototype/shuffle.js
+++ b/src/virtualdom/items/Section/prototype/shuffle.js
@@ -19,7 +19,7 @@ export default function Section$shuffle ( newIndices ) {
// short circuit any double-updates, and ensure that this isn't applied to
// non-list sections
- if ( this.shuffling || this.unbound || ( this.subtype && this.subtype !== types.SECTION_EACH ) ) {
+ if ( this.shuffling || this.unbound || ( this.currentSubtype !== types.SECTION_EACH ) ) {
return;
}
@@ -30,9 +30,10 @@ export default function Section$shuffle ( newIndices ) {
reboundFragments = [];
+ // TODO: need to update this
// first, rebind existing fragments
newIndices.forEach( ( newIndex, oldIndex ) => {
- var fragment, by, oldKeypath, newKeypath;
+ var fragment, by, oldKeypath, newKeypath, deps;
if ( newIndex === oldIndex ) {
reboundFragments[ newIndex ] = this.fragments[ oldIndex ];
@@ -57,7 +58,17 @@ export default function Section$shuffle ( newIndices ) {
oldKeypath = this.keypath + '.' + oldIndex;
newKeypath = this.keypath + '.' + newIndex;
- fragment.rebind( this.template.i, newIndex, oldKeypath, newKeypath );
+ fragment.index = newIndex;
+
+ // notify any registered index refs directly
+ if ( deps = fragment.registeredIndexRefs ) {
+ for ( let d of deps ) {
+ // the keypath doesn't actually matter here
+ d.rebind( '', '' );
+ }
+ }
+
+ fragment.rebind( oldKeypath, newKeypath );
reboundFragments[ newIndex ] = fragment;
});
@@ -87,10 +98,6 @@ export default function Section$shuffle ( newIndices ) {
owner: this
};
- if ( this.template.i ) {
- fragmentOptions.indexRef = this.template.i;
- }
-
// Add as many new fragments as we need to, or add back existing
// (detached) fragments
for ( i = firstChange; i < newLength; i += 1 ) {
diff --git a/src/virtualdom/items/Yielder.js b/src/virtualdom/items/Yielder.js
index 94fc87efb9..d8655be1af 100644
--- a/src/virtualdom/items/Yielder.js
+++ b/src/virtualdom/items/Yielder.js
@@ -93,8 +93,8 @@ Yielder.prototype = {
removeFromArray( this.component.yielders[ this.name ], this );
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
- this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ rebind: function ( oldKeypath, newKeypath ) {
+ this.fragment.rebind( oldKeypath, newKeypath );
},
toString: function () {
diff --git a/src/virtualdom/items/shared/Mustache/initialise.js b/src/virtualdom/items/shared/Mustache/initialise.js
index 8d8f4c392d..224a8f1cc4 100644
--- a/src/virtualdom/items/shared/Mustache/initialise.js
+++ b/src/virtualdom/items/shared/Mustache/initialise.js
@@ -16,6 +16,7 @@ export default function Mustache$init ( mustache, options ) {
mustache.template = options.template;
mustache.index = options.index || 0;
+ mustache.key = options.key;
mustache.isStatic = options.template.s;
mustache.type = options.template.t;
@@ -54,7 +55,7 @@ export default function Mustache$init ( mustache, options ) {
if ( oldKeypath !== undefined ) {
mustache.fragments && mustache.fragments.forEach( f => {
- f.rebind( null, null, oldKeypath, newKeypath );
+ f.rebind( oldKeypath, newKeypath );
});
}
}
diff --git a/src/virtualdom/items/shared/Mustache/rebind.js b/src/virtualdom/items/shared/Mustache/rebind.js
index 8e68390f85..2d9b730c8c 100644
--- a/src/virtualdom/items/shared/Mustache/rebind.js
+++ b/src/virtualdom/items/shared/Mustache/rebind.js
@@ -1,11 +1,11 @@
-export default function Mustache$rebind ( indexRef, newIndex, oldKeypath, newKeypath ) {
+export default function Mustache$rebind ( oldKeypath, newKeypath ) {
// Children first
if ( this.fragments ) {
- this.fragments.forEach( f => f.rebind( indexRef, newIndex, oldKeypath, newKeypath ) );
+ this.fragments.forEach( f => f.rebind( oldKeypath, newKeypath ) );
}
// Expression mustache?
if ( this.resolver ) {
- this.resolver.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ this.resolver.rebind( oldKeypath, newKeypath );
}
}
diff --git a/src/virtualdom/items/shared/Resolvers/ExpressionResolver.js b/src/virtualdom/items/shared/Resolvers/ExpressionResolver.js
index 4372165033..0989a4fcd9 100644
--- a/src/virtualdom/items/shared/Resolvers/ExpressionResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/ExpressionResolver.js
@@ -9,7 +9,7 @@ var ExpressionResolver, bind = Function.prototype.bind;
ExpressionResolver = function ( owner, parentFragment, expression, callback ) {
- var ractive, indexRefs;
+ var ractive;
ractive = owner.root;
@@ -20,8 +20,6 @@ ExpressionResolver = function ( owner, parentFragment, expression, callback ) {
this.str = expression.s;
this.keypaths = [];
- indexRefs = parentFragment.indexRefs;
-
// Create resolvers for each reference
this.pending = expression.r.length;
this.refResolvers = expression.r.map( ( ref, i ) => {
@@ -105,9 +103,9 @@ ExpressionResolver.prototype = {
}
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
// TODO only bubble once, no matter how many references are affected by the rebind
- this.refResolvers.forEach( r => r.rebind( indexRef, newIndex, oldKeypath, newKeypath ) );
+ this.refResolvers.forEach( r => r.rebind( oldKeypath, newKeypath ) );
}
};
diff --git a/src/virtualdom/items/shared/Resolvers/IndexResolver.js b/src/virtualdom/items/shared/Resolvers/IndexResolver.js
index e67a5be5f3..22e3e1e533 100644
--- a/src/virtualdom/items/shared/Resolvers/IndexResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/IndexResolver.js
@@ -3,21 +3,29 @@ var IndexResolver = function ( owner, ref, callback ) {
this.ref = ref;
this.callback = callback;
+ ref.ref.fragment.registerIndexRef( this );
+
this.rebind();
};
IndexResolver.prototype = {
rebind: function () {
- var ref = this.ref,
- indexRefs = this.parentFragment.indexRefs,
- index = indexRefs[ ref ];
+ var index, ref = this.ref.ref;
+
+ if ( ref.ref.t === 'k' ) {
+ index = 'k' + ref.fragment.key;
+ } else {
+ index = 'i' + ref.fragment.index;
+ }
if ( index !== undefined ) {
this.callback( '@' + index );
}
},
- unbind: function () {} // noop
+ unbind: function () {
+ this.ref.ref.fragment.unregisterIndexRef( this );
+ }
};
-export default IndexResolver;
\ No newline at end of file
+export default IndexResolver;
diff --git a/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js b/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js
index 6b323a7688..95cc31fcb7 100644
--- a/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js
@@ -47,9 +47,9 @@ MemberResolver.prototype = {
this.viewmodel.register( this.keypath, this );
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
if ( this.refResolver ) {
- this.refResolver.rebind( indexRef, newIndex, oldKeypath, newKeypath );
+ this.refResolver.rebind( oldKeypath, newKeypath );
}
},
diff --git a/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/ReferenceExpressionResolver.js b/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/ReferenceExpressionResolver.js
index 3653691f13..3c8e6efc8b 100644
--- a/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/ReferenceExpressionResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/ReferenceExpressionResolver.js
@@ -55,11 +55,11 @@ ReferenceExpressionResolver.prototype = {
this.members.forEach( unbind );
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
var changed;
this.members.forEach( members => {
- if ( members.rebind( indexRef, newIndex, oldKeypath, newKeypath ) ) {
+ if ( members.rebind( oldKeypath, newKeypath ) ) {
changed = true;
}
});
diff --git a/src/virtualdom/items/shared/Resolvers/ReferenceResolver.js b/src/virtualdom/items/shared/Resolvers/ReferenceResolver.js
index 754a19a950..49cb0bf83c 100644
--- a/src/virtualdom/items/shared/Resolvers/ReferenceResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/ReferenceResolver.js
@@ -34,7 +34,7 @@ ReferenceResolver.prototype = {
this.resolve( this.ref );
},
- rebind: function ( indexRef, newIndex, oldKeypath, newKeypath ) {
+ rebind: function ( oldKeypath, newKeypath ) {
var keypath;
if ( this.keypath !== undefined ) {
diff --git a/src/virtualdom/items/shared/Resolvers/SpecialResolver.js b/src/virtualdom/items/shared/Resolvers/SpecialResolver.js
index 753720f9bc..bef7cd5843 100644
--- a/src/virtualdom/items/shared/Resolvers/SpecialResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/SpecialResolver.js
@@ -1,3 +1,5 @@
+import types from 'config/types';
+
var SpecialResolver = function ( owner, ref, callback ) {
this.parentFragment = owner.parentFragment;
this.ref = ref;
@@ -7,29 +9,71 @@ var SpecialResolver = function ( owner, ref, callback ) {
};
var props = {
- '@keypath': 'context',
- '@index': 'index',
- '@key': 'index'
+ '@keypath': { prefix: 'c', prop: [ 'context' ] },
+ '@index': { prefix: 'i', prop: [ 'index' ] },
+ '@key': { prefix: 'k', prop: [ 'key', 'index' ] }
};
+function getProp( target, prop ) {
+ var value;
+ for ( let i = 0; i < prop.prop.length; i++ ) {
+ if ( ( value = target[prop.prop[i]] ) !== undefined ) {
+ return value;
+ }
+ }
+}
+
SpecialResolver.prototype = {
rebind: function () {
- var ref = this.ref, fragment = this.parentFragment, prop = props[ref];
+ var ref = this.ref, fragment = this.parentFragment, prop = props[ref], value;
if ( !prop ) {
throw new Error( 'Unknown special reference "' + ref + '" - valid references are @index, @key and @keypath' );
}
- while ( fragment ) {
- if ( fragment[prop] !== undefined ) {
- return this.callback( '@' + fragment[prop] );
+ // have we already found the nearest parent?
+ if ( this.cached ) {
+ return this.callback( '@' + prop.prefix + getProp( this.cached, prop ) );
+ }
+
+ // special case for indices, which may cross component boundaries
+ if ( prop.prop.indexOf( 'index' ) !== -1 || prop.prop.indexOf( 'key' ) !== -1 ) {
+ while ( fragment ) {
+ if ( fragment.owner.currentSubtype === types.SECTION_EACH && ( value = getProp( fragment, prop ) ) !== undefined ) {
+ this.cached = fragment;
+
+ fragment.registerIndexRef( this );
+
+ return this.callback( '@' + prop.prefix + value );
+ }
+
+ // watch for component boundaries
+ if ( !fragment.parent && fragment.owner &&
+ fragment.owner.component && fragment.owner.component.parentFragment &&
+ !fragment.owner.component.instance.isolated ) {
+ fragment = fragment.owner.component.parentFragment;
+ } else {
+ fragment = fragment.parent;
+ }
}
+ }
- fragment = fragment.parent;
+ else {
+ while ( fragment ) {
+ if ( ( value = getProp( fragment, prop ) ) !== undefined ) {
+ return this.callback( '@' + prop.prefix + value );
+ }
+
+ fragment = fragment.parent;
+ }
}
},
- unbind: function () {} // noop
+ unbind: function () {
+ if ( this.cached ) {
+ this.cached.unregisterIndexRef( this );
+ }
+ }
};
export default SpecialResolver;
diff --git a/src/virtualdom/items/shared/Resolvers/createReferenceResolver.js b/src/virtualdom/items/shared/Resolvers/createReferenceResolver.js
index e676feaec2..9713d94cfb 100644
--- a/src/virtualdom/items/shared/Resolvers/createReferenceResolver.js
+++ b/src/virtualdom/items/shared/Resolvers/createReferenceResolver.js
@@ -1,18 +1,18 @@
import ReferenceResolver from 'virtualdom/items/shared/Resolvers/ReferenceResolver';
import SpecialResolver from 'virtualdom/items/shared/Resolvers/SpecialResolver';
import IndexResolver from 'virtualdom/items/shared/Resolvers/IndexResolver';
+import findIndexRefs from 'virtualdom/items/shared/Resolvers/findIndexRefs';
export default function createReferenceResolver ( owner, ref, callback ) {
- var indexRefs, index;
+ var indexRef;
if ( ref.charAt( 0 ) === '@' ) {
return new SpecialResolver( owner, ref, callback );
}
- indexRefs = owner.parentFragment.indexRefs;
- if ( indexRefs && ( index = indexRefs[ ref ] ) !== undefined ) {
- return new IndexResolver( owner, ref, callback );
+ if ( indexRef = findIndexRefs( owner.parentFragment, ref ) ) {
+ return new IndexResolver( owner, indexRef, callback );
}
return new ReferenceResolver( owner, ref, callback );
-}
\ No newline at end of file
+}
diff --git a/src/virtualdom/items/shared/Resolvers/findIndexRefs.js b/src/virtualdom/items/shared/Resolvers/findIndexRefs.js
new file mode 100644
index 0000000000..c5438f43db
--- /dev/null
+++ b/src/virtualdom/items/shared/Resolvers/findIndexRefs.js
@@ -0,0 +1,53 @@
+export default function findIndexRefs( fragment, refName ) {
+ var result = {}, refs, fragRefs, ref, i, owner, hit = false;
+
+ if ( !refName ) {
+ result.refs = refs = {};
+ }
+
+ while ( fragment ) {
+ if ( ( owner = fragment.owner ) && ( fragRefs = owner.indexRefs ) ) {
+
+ // we're looking for a particular ref, and it's here
+ if ( refName && ( ref = owner.getIndexRef( refName ) ) ) {
+ result.ref = {
+ fragment: fragment,
+ ref: ref
+ };
+ return result;
+ }
+
+ // we're collecting refs up-tree
+ else if ( !refName ) {
+ for ( i in fragRefs ) {
+ ref = fragRefs[i];
+
+ // don't overwrite existing refs - they should shadow parents
+ if ( !refs[ref.n] ) {
+ hit = true;
+ refs[ref.n] = {
+ fragment: fragment,
+ ref: ref
+ };
+ }
+ }
+ }
+ }
+
+ // watch for component boundaries
+ if ( !fragment.parent && fragment.owner &&
+ fragment.owner.component && fragment.owner.component.parentFragment &&
+ !fragment.owner.component.instance.isolated ) {
+ result.componentBoundary = true;
+ fragment = fragment.owner.component.parentFragment;
+ } else {
+ fragment = fragment.parent;
+ }
+ }
+
+ if ( !hit ) {
+ return undefined;
+ } else {
+ return result;
+ }
+}
diff --git a/test/modules/rebind.js b/test/modules/rebind.js
index 2d6ba12391..7e395d4653 100644
--- a/test/modules/rebind.js
+++ b/test/modules/rebind.js
@@ -74,7 +74,8 @@ define([
fragment.findNextNode = function () { return null; };
fragment.render();
- fragment.rebind( 'i', opt.newKeypath.replace('items.',''), opt.oldKeypath, opt.newKeypath);
+ fragment.index = opt.newKeypath.replace( 'items.', '' );
+ fragment.rebind( opt.oldKeypath, opt.newKeypath );
t.equal( fragment.context, opt.expected );
t.equal( fragment.items[0].node._ractive.keypath, opt.expected );
@@ -405,7 +406,7 @@ define([
test( 'index rebinds do not go past new index providers (#1457)', function ( t ) {
var ractive = new Ractive({
el: fixture,
- template: '{{#each foo}}{{@index}}{{#each .bar}}{{@index}}{{/each}}{{/each}}',
+ template: '{{#each foo}}{{@index}}{{#each .bar}}{{@index}}{{/each}}
{{/each}}',
data: {
foo: [
{ bar: [ 1, 2 ] },
@@ -415,17 +416,17 @@ define([
}
});
- t.htmlEqual( fixture.innerHTML, '0011020123' );
+ t.htmlEqual( fixture.innerHTML, '001
10
20123
' );
ractive.splice( 'foo', 1, 1 );
- t.htmlEqual( fixture.innerHTML, '00110123' );
+ t.htmlEqual( fixture.innerHTML, '001
10123
' );
});
test( 'index rebinds get passed through conditional sections correctly', t => {
var ractive = new Ractive({
el: fixture,
- template: '{{#each foo}}{{@index}}{{#.bar}}{{@index}}{{/}}{{/each}}',
+ template: '{{#each foo}}{{@index}}{{#.bar}}{{@index}}{{/}}
{{/each}}',
data: {
foo: [
{ bar: true },
@@ -436,11 +437,11 @@ define([
}
});
- t.htmlEqual( fixture.innerHTML, '0011233' );
+ t.htmlEqual( fixture.innerHTML, '00
11
2
33
' );
ractive.splice( 'foo', 1, 1 );
- t.htmlEqual( fixture.innerHTML, '00122' );
+ t.htmlEqual( fixture.innerHTML, '00
1
22
' );
});
};
diff --git a/test/modules/render.js b/test/modules/render.js
index 86c0e91409..7b7c98b49d 100644
--- a/test/modules/render.js
+++ b/test/modules/render.js
@@ -225,6 +225,32 @@ define([ 'ractive', 'samples/render' ], function ( Ractive, tests ) {
t.htmlEqual( fixture.innerHTML, '
2
4
5
' }, + { + name: 'two indices in an #each with object give access to the key and index', + handlebars: true, + template: '{{#object:k,i}}{{k}} {{i}} {{.}}
{{/each}}', + data: { object: { foo: 1, bar: 2, baz: 3 } }, + result: 'foo 0 1
bar 1 2
baz 2 3
' + }, + { + name: 'the key ref in an #each switches to index if the value turns into an array', + handlebars: true, + template: '{{#object:k,i}}{{k}} {{i}} {{.}}
{{/each}}', + data: { object: { foo: 1, bar: 2, baz: 3 } }, + result: 'foo 0 1
bar 1 2
baz 2 3
', + new_data: { object: [ 1, 2, 3 ] }, + new_result: '0 0 1
1 1 2
2 2 3
' + }, + { + name: 'the key ref in an #each switches to key if the value turns into an object', + handlebars: true, + template: '{{#object:k,i}}{{k}} {{i}} {{.}}
{{/each}}', + data: { object: [ 1, 2, 3 ] }, + result: '0 0 1
1 1 2
2 2 3
', + new_data: { object: { foo: 1, bar: 2, baz: 3 } }, + new_result: 'foo 0 1
bar 1 2
baz 2 3
' + }, { name: '@index can be used as an index reference', handlebars: true, @@ -625,12 +650,37 @@ var renderTests = [ result: '0: a
1: b
2: c
' }, { - name: '@key can be used as an key reference', + name: '@key can be used as a key reference', handlebars: true, template: '{{#each object}}{{@key}}: {{this}}
{{/each}}', data: { object: { foo: 1, bar: 2, baz: 3 } }, result: 'foo: 1
bar: 2
baz: 3
' }, + { + name: '@key can be used as an index reference for arrays', + handlebars: true, + template: '{{#each array}}{{@key}}: {{this}}
{{/each}}', + data: { array: [ 'foo', 'bar', 'baz' ] }, + result: '0: foo
1: bar
2: baz
' + }, + { + name: '@index can be used as an index reference with object sections', + template: '{{#each object}}{{@key}} {{@index}} {{.}}
{{/each}}', + data: { object: { foo: 1, bar: 2, baz: 3 } }, + result: 'foo 0 1
bar 1 2
baz 2 3
' + }, + { + name: '@key and @index can be used in an expression with object sections', + template: '{{#each object}}{{@key + "!"}} {{@index + 1}} {{.}}
{{/each}}', + data: { object: { foo: 1, bar: 2, baz: 3 } }, + result: 'foo! 1 1
bar! 2 2
baz! 3 3
' + }, + { + name: 'key and index refs can be used in an expression with object sections', + template: '{{#each object: k, i }}{{k + "!"}} {{i + 1}} {{.}}
{{/each}}', + data: { object: { foo: 1, bar: 2, baz: 3 } }, + result: 'foo! 1 1
bar! 2 2
baz! 3 3
' + }, { name: '@index can be used in an expression', handlebars: true, @@ -942,12 +992,17 @@ var renderTests = [ template: '', result: '' }, - { name: '@keypath may be used to refer to the current context', template: `