ractive
Version:
Next-generation DOM manipulation
270 lines (206 loc) • 6.96 kB
JavaScript
import types from 'config/types';
import isArray from 'utils/isArray';
import isObject from 'utils/isObject';
import runloop from 'global/runloop';
import circular from 'circular';
var Fragment;
circular.push( function () {
Fragment = circular.Fragment;
});
export default function Section$setValue ( value ) {
var wrapper, fragmentOptions;
if ( this.updating ) {
// If a child of this section causes a re-evaluation - for example, an
// expression refers to a function that mutates the array that this
// section depends on - we'll end up with a double rendering bug (see
// https://github.com/ractivejs/ractive/issues/748). This prevents it.
return;
}
this.updating = true;
// with sections, we need to get the fake value if we have a wrapped object
if ( wrapper = this.root.viewmodel.wrapped[ this.keypath ] ) {
value = wrapper.get();
}
// If any fragments are awaiting creation after a splice,
// this is the place to do it
if ( this.fragmentsToCreate.length ) {
fragmentOptions = {
template: this.template.f,
root: this.root,
pElement: this.pElement,
owner: this,
indexRef: this.template.i
};
this.fragmentsToCreate.forEach( index => {
var fragment;
fragmentOptions.context = this.keypath + '.' + index;
fragmentOptions.index = index;
fragment = new Fragment( fragmentOptions );
this.fragmentsToRender.push( this.fragments[ index ] = fragment );
});
this.fragmentsToCreate.length = 0;
}
else if ( reevaluateSection( this, value ) ) {
this.bubble();
if ( this.rendered ) {
runloop.addView( this );
}
}
this.value = value;
this.updating = false;
}
function reevaluateSection ( section, value ) {
var fragmentOptions = {
template: section.template.f,
root: section.root,
pElement: section.parentFragment.pElement,
owner: section
};
// If we already know the section type, great
// TODO can this be optimised? i.e. pick an reevaluateSection function during init
// and avoid doing this each time?
if ( section.template.n ) {
switch ( section.template.n ) {
case types.SECTION_IF:
return reevaluateConditionalSection( section, value, false, fragmentOptions );
case types.SECTION_UNLESS:
return reevaluateConditionalSection( section, value, true, fragmentOptions );
case types.SECTION_WITH:
return reevaluateContextSection( section, fragmentOptions );
case types.SECTION_EACH:
if ( isObject( value ) ) {
return reevaluateListObjectSection( section, value, fragmentOptions );
}
// Fallthrough - if it's a conditional or an array we need to continue
}
}
// Otherwise we need to work out what sort of section we're dealing with
section.ordered = !!isArray( value );
// Ordered list section
if ( section.ordered ) {
return reevaluateListSection( section, value, fragmentOptions );
}
// Unordered list, or context
if ( isObject( value ) || typeof value === 'function' ) {
// Index reference indicates section should be treated as a list
if ( section.template.i ) {
return reevaluateListObjectSection( section, value, fragmentOptions );
}
// Otherwise, object provides context for contents
return reevaluateContextSection( section, fragmentOptions );
}
// Conditional section
return reevaluateConditionalSection( section, value, false, fragmentOptions );
}
function reevaluateListSection ( section, value, fragmentOptions ) {
var i, length, fragment;
length = value.length;
if ( length === section.length ) {
// Nothing to do
return false;
}
// if the array is shorter than it was previously, remove items
if ( length < section.length ) {
section.fragmentsToUnrender = section.fragments.splice( length, section.length - length );
section.fragmentsToUnrender.forEach( unbind );
}
// otherwise...
else {
if ( length > section.length ) {
// add any new ones
for ( i = section.length; i < length; i += 1 ) {
// append list item to context stack
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 );
}
}
}
section.length = length;
return true;
}
function reevaluateListObjectSection ( section, value, fragmentOptions ) {
var id, i, hasKey, fragment, changed;
hasKey = section.hasKey || ( section.hasKey = {} );
// remove any fragments that should no longer exist
i = section.fragments.length;
while ( i-- ) {
fragment = section.fragments[i];
if ( !( fragment.index in value ) ) {
changed = true;
fragment.unbind();
section.fragmentsToUnrender.push( fragment );
section.fragments.splice( i, 1 );
hasKey[ fragment.index ] = false;
}
}
// add any that haven't been created yet
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;
}
fragment = new Fragment( fragmentOptions );
section.fragmentsToRender.push( fragment );
section.fragments.push( fragment );
hasKey[ id ] = true;
}
}
section.length = section.fragments.length;
return changed;
}
function reevaluateContextSection ( section, fragmentOptions ) {
var fragment;
// ...then if it isn't rendered, render it, adding section.keypath to the context stack
// (if it is already rendered, then any children dependent on the context stack
// will update themselves without any prompting)
if ( !section.length ) {
// append this section to the context stack
fragmentOptions.context = section.keypath;
fragmentOptions.index = 0;
fragment = new Fragment( fragmentOptions );
section.fragmentsToRender.push( section.fragments[0] = fragment );
section.length = 1;
return true;
}
}
function reevaluateConditionalSection ( section, value, inverted, fragmentOptions ) {
var doRender, emptyArray, fragment;
emptyArray = ( isArray( value ) && value.length === 0 );
if ( inverted ) {
doRender = emptyArray || !value;
} else {
doRender = value && !emptyArray;
}
if ( doRender ) {
if ( !section.length ) {
// no change to context stack
fragmentOptions.index = 0;
fragment = new Fragment( fragmentOptions );
section.fragmentsToRender.push( section.fragments[0] = fragment );
section.length = 1;
return true;
}
if ( section.length > 1 ) {
section.fragmentsToUnrender = section.fragments.splice( 1 );
section.fragmentsToUnrender.forEach( unbind );
return true;
}
}
else if ( section.length ) {
section.fragmentsToUnrender = section.fragments.splice( 0, section.fragments.length );
section.fragmentsToUnrender.forEach( unbind );
section.length = 0;
return true;
}
}
function unbind ( fragment ) {
fragment.unbind();
}