ractive
Version:
Next-generation DOM manipulation
141 lines (112 loc) • 3.2 kB
JavaScript
import circular from 'circular';
import removeFromArray from 'utils/removeFromArray';
import Promise from 'utils/Promise';
import resolveRef from 'shared/resolveRef';
import TransitionManager from 'global/TransitionManager';
var batch, runloop, unresolved = [];
runloop = {
start: function ( instance, returnPromise ) {
var promise, fulfilPromise;
if ( returnPromise ) {
promise = new Promise( f => ( fulfilPromise = f ) );
}
batch = {
previousBatch: batch,
transitionManager: new TransitionManager( fulfilPromise, batch && batch.transitionManager ),
views: [],
tasks: [],
viewmodels: []
};
if ( instance ) {
batch.viewmodels.push( instance.viewmodel );
}
return promise;
},
end: function () {
flushChanges();
batch.transitionManager.init();
batch = batch.previousBatch;
},
addViewmodel: function ( viewmodel ) {
if ( batch ) {
if ( batch.viewmodels.indexOf( viewmodel ) === -1 ) {
batch.viewmodels.push( viewmodel );
}
} else {
viewmodel.applyChanges();
}
},
registerTransition: function ( transition ) {
transition._manager = batch.transitionManager;
batch.transitionManager.add( transition );
},
addView: function ( view ) {
batch.views.push( view );
},
addUnresolved: function ( thing ) {
unresolved.push( thing );
},
removeUnresolved: function ( thing ) {
removeFromArray( unresolved, thing );
},
// synchronise node detachments with transition ends
detachWhenReady: function ( thing ) {
batch.transitionManager.detachQueue.push( thing );
},
scheduleTask: function ( task ) {
if ( !batch ) {
task();
} else {
batch.tasks.push( task );
}
}
};
circular.runloop = runloop;
export default runloop;
function flushChanges () {
var i, thing, changeHash;
for ( i = 0; i < batch.viewmodels.length; i += 1 ) {
thing = batch.viewmodels[i];
changeHash = thing.applyChanges();
if ( changeHash ) {
thing.ractive.fire( 'change', changeHash );
}
}
batch.viewmodels.length = 0;
attemptKeypathResolution();
// Now that changes have been fully propagated, we can update the DOM
// and complete other tasks
for ( i = 0; i < batch.views.length; i += 1 ) {
batch.views[i].update();
}
batch.views.length = 0;
for ( i = 0; i < batch.tasks.length; i += 1 ) {
batch.tasks[i]();
}
batch.tasks.length = 0;
// If updating the view caused some model blowback - e.g. a triple
// containing <option> elements caused the binding on the <select>
// to update - then we start over
if ( batch.viewmodels.length ) return flushChanges();
}
function attemptKeypathResolution () {
var array, thing, keypath;
if ( !unresolved.length ) {
return;
}
// see if we can resolve any unresolved references
array = unresolved.splice( 0, unresolved.length );
while ( thing = array.pop() ) {
if ( thing.keypath ) {
continue; // it did resolve after all
}
keypath = resolveRef( thing.root, thing.ref, thing.parentFragment );
if ( keypath !== undefined ) {
// If we've resolved the keypath, we can initialise this item
thing.resolve( keypath );
} else {
// If we can't resolve the reference, try again next time
unresolved.push( thing );
}
}
}