ractive
Version:
Next-generation DOM manipulation
1,778 lines (1,659 loc) • 406 kB
JavaScript
/*
ractive.js v0.5.0
2014-07-05 - commit 1c20c21b
http://ractivejs.org
http://twitter.com/RactiveJS
Released under the MIT License.
*/
( function( global ) {
'use strict';
var noConflict = global.Ractive;
/* config/defaults/options.js */
var options = function() {
// These are both the values for Ractive.defaults
// as well as the determination for whether an option
// value will be placed on Component.defaults
// (versus directly on Component) during an extend operation
var defaultOptions = {
// render placement:
el: void 0,
append: false,
// template:
template: {
v: 1,
t: []
},
// parse:
preserveWhitespace: false,
sanitize: false,
stripComments: true,
delimiters: [
'{{',
'}}'
],
tripleDelimiters: [
'{{{',
'}}}'
],
// data & binding:
data: {},
computed: {},
magic: false,
modifyArrays: true,
adapt: [],
isolated: false,
twoway: true,
lazy: false,
// transitions:
noIntro: false,
transitionsEnabled: true,
complete: void 0,
// css:
noCssTransform: false,
// debug:
debug: false
};
return defaultOptions;
}();
/* config/defaults/easing.js */
var easing = {
linear: function( pos ) {
return pos;
},
easeIn: function( pos ) {
return Math.pow( pos, 3 );
},
easeOut: function( pos ) {
return Math.pow( pos - 1, 3 ) + 1;
},
easeInOut: function( pos ) {
if ( ( pos /= 0.5 ) < 1 ) {
return 0.5 * Math.pow( pos, 3 );
}
return 0.5 * ( Math.pow( pos - 2, 3 ) + 2 );
}
};
/* circular.js */
var circular = [];
/* utils/hasOwnProperty.js */
var hasOwn = Object.prototype.hasOwnProperty;
/* utils/isArray.js */
var isArray = function() {
var toString = Object.prototype.toString;
// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
return function( thing ) {
return toString.call( thing ) === '[object Array]';
};
}();
/* utils/isObject.js */
var isObject = function() {
var toString = Object.prototype.toString;
return function( thing ) {
return thing && toString.call( thing ) === '[object Object]';
};
}();
/* utils/isNumeric.js */
var isNumeric = function( thing ) {
return !isNaN( parseFloat( thing ) ) && isFinite( thing );
};
/* config/defaults/interpolators.js */
var interpolators = function( circular, hasOwnProperty, isArray, isObject, isNumeric ) {
var interpolators, interpolate, cssLengthPattern;
circular.push( function() {
interpolate = circular.interpolate;
} );
cssLengthPattern = /^([+-]?[0-9]+\.?(?:[0-9]+)?)(px|em|ex|%|in|cm|mm|pt|pc)$/;
interpolators = {
number: function( from, to ) {
var delta;
if ( !isNumeric( from ) || !isNumeric( to ) ) {
return null;
}
from = +from;
to = +to;
delta = to - from;
if ( !delta ) {
return function() {
return from;
};
}
return function( t ) {
return from + t * delta;
};
},
array: function( from, to ) {
var intermediate, interpolators, len, i;
if ( !isArray( from ) || !isArray( to ) ) {
return null;
}
intermediate = [];
interpolators = [];
i = len = Math.min( from.length, to.length );
while ( i-- ) {
interpolators[ i ] = interpolate( from[ i ], to[ i ] );
}
// surplus values - don't interpolate, but don't exclude them either
for ( i = len; i < from.length; i += 1 ) {
intermediate[ i ] = from[ i ];
}
for ( i = len; i < to.length; i += 1 ) {
intermediate[ i ] = to[ i ];
}
return function( t ) {
var i = len;
while ( i-- ) {
intermediate[ i ] = interpolators[ i ]( t );
}
return intermediate;
};
},
object: function( from, to ) {
var properties, len, interpolators, intermediate, prop;
if ( !isObject( from ) || !isObject( to ) ) {
return null;
}
properties = [];
intermediate = {};
interpolators = {};
for ( prop in from ) {
if ( hasOwnProperty.call( from, prop ) ) {
if ( hasOwnProperty.call( to, prop ) ) {
properties.push( prop );
interpolators[ prop ] = interpolate( from[ prop ], to[ prop ] );
} else {
intermediate[ prop ] = from[ prop ];
}
}
}
for ( prop in to ) {
if ( hasOwnProperty.call( to, prop ) && !hasOwnProperty.call( from, prop ) ) {
intermediate[ prop ] = to[ prop ];
}
}
len = properties.length;
return function( t ) {
var i = len,
prop;
while ( i-- ) {
prop = properties[ i ];
intermediate[ prop ] = interpolators[ prop ]( t );
}
return intermediate;
};
},
cssLength: function( from, to ) {
var fromMatch, toMatch, fromUnit, toUnit, fromValue, toValue, unit, delta;
if ( from !== 0 && typeof from !== 'string' || to !== 0 && typeof to !== 'string' ) {
return null;
}
fromMatch = cssLengthPattern.exec( from );
toMatch = cssLengthPattern.exec( to );
fromUnit = fromMatch ? fromMatch[ 2 ] : '';
toUnit = toMatch ? toMatch[ 2 ] : '';
if ( fromUnit && toUnit && fromUnit !== toUnit ) {
return null;
}
unit = fromUnit || toUnit;
fromValue = fromMatch ? +fromMatch[ 1 ] : 0;
toValue = toMatch ? +toMatch[ 1 ] : 0;
delta = toValue - fromValue;
if ( !delta ) {
return function() {
return fromValue + unit;
};
}
return function( t ) {
return fromValue + t * delta + unit;
};
}
};
return interpolators;
}( circular, hasOwn, isArray, isObject, isNumeric );
/* config/svg.js */
var svg = function() {
var svg;
if ( typeof document === 'undefined' ) {
svg = false;
} else {
svg = document && document.implementation.hasFeature( 'http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1' );
}
return svg;
}();
/* utils/removeFromArray.js */
var removeFromArray = function( array, member ) {
var index = array.indexOf( member );
if ( index !== -1 ) {
array.splice( index, 1 );
}
};
/* utils/Promise.js */
var Promise = function() {
var _Promise, PENDING = {},
FULFILLED = {},
REJECTED = {};
if ( typeof Promise === 'function' ) {
// use native Promise
_Promise = Promise;
} else {
_Promise = function( callback ) {
var fulfilledHandlers = [],
rejectedHandlers = [],
state = PENDING,
result, dispatchHandlers, makeResolver, fulfil, reject, promise;
makeResolver = function( newState ) {
return function( value ) {
if ( state !== PENDING ) {
return;
}
result = value;
state = newState;
dispatchHandlers = makeDispatcher( state === FULFILLED ? fulfilledHandlers : rejectedHandlers, result );
// dispatch onFulfilled and onRejected handlers asynchronously
wait( dispatchHandlers );
};
};
fulfil = makeResolver( FULFILLED );
reject = makeResolver( REJECTED );
try {
callback( fulfil, reject );
} catch ( err ) {
reject( err );
}
promise = {
// `then()` returns a Promise - 2.2.7
then: function( onFulfilled, onRejected ) {
var promise2 = new _Promise( function( fulfil, reject ) {
var processResolutionHandler = function( handler, handlers, forward ) {
// 2.2.1.1
if ( typeof handler === 'function' ) {
handlers.push( function( p1result ) {
var x;
try {
x = handler( p1result );
resolve( promise2, x, fulfil, reject );
} catch ( err ) {
reject( err );
}
} );
} else {
// Forward the result of promise1 to promise2, if resolution handlers
// are not given
handlers.push( forward );
}
};
// 2.2
processResolutionHandler( onFulfilled, fulfilledHandlers, fulfil );
processResolutionHandler( onRejected, rejectedHandlers, reject );
if ( state !== PENDING ) {
// If the promise has resolved already, dispatch the appropriate handlers asynchronously
wait( dispatchHandlers );
}
} );
return promise2;
}
};
promise[ 'catch' ] = function( onRejected ) {
return this.then( null, onRejected );
};
return promise;
};
_Promise.all = function( promises ) {
return new _Promise( function( fulfil, reject ) {
var result = [],
pending, i, processPromise;
if ( !promises.length ) {
fulfil( result );
return;
}
processPromise = function( i ) {
promises[ i ].then( function( value ) {
result[ i ] = value;
if ( !--pending ) {
fulfil( result );
}
}, reject );
};
pending = i = promises.length;
while ( i-- ) {
processPromise( i );
}
} );
};
_Promise.resolve = function( value ) {
return new _Promise( function( fulfil ) {
fulfil( value );
} );
};
_Promise.reject = function( reason ) {
return new _Promise( function( fulfil, reject ) {
reject( reason );
} );
};
}
return _Promise;
// TODO use MutationObservers or something to simulate setImmediate
function wait( callback ) {
setTimeout( callback, 0 );
}
function makeDispatcher( handlers, result ) {
return function() {
var handler;
while ( handler = handlers.shift() ) {
handler( result );
}
};
}
function resolve( promise, x, fulfil, reject ) {
// Promise Resolution Procedure
var then;
// 2.3.1
if ( x === promise ) {
throw new TypeError( 'A promise\'s fulfillment handler cannot return the same promise' );
}
// 2.3.2
if ( x instanceof _Promise ) {
x.then( fulfil, reject );
} else if ( x && ( typeof x === 'object' || typeof x === 'function' ) ) {
try {
then = x.then;
} catch ( e ) {
reject( e );
// 2.3.3.2
return;
}
// 2.3.3.3
if ( typeof then === 'function' ) {
var called, resolvePromise, rejectPromise;
resolvePromise = function( y ) {
if ( called ) {
return;
}
called = true;
resolve( promise, y, fulfil, reject );
};
rejectPromise = function( r ) {
if ( called ) {
return;
}
called = true;
reject( r );
};
try {
then.call( x, resolvePromise, rejectPromise );
} catch ( e ) {
if ( !called ) {
// 2.3.3.3.4.1
reject( e );
// 2.3.3.3.4.2
called = true;
return;
}
}
} else {
fulfil( x );
}
} else {
fulfil( x );
}
}
}();
/* utils/normaliseRef.js */
var normaliseRef = function() {
var regex = /\[\s*(\*|[0-9]|[1-9][0-9]+)\s*\]/g;
return function normaliseRef( ref ) {
return ( ref || '' ).replace( regex, '.$1' );
};
}();
/* shared/getInnerContext.js */
var getInnerContext = function( fragment ) {
do {
if ( fragment.context ) {
return fragment.context;
}
} while ( fragment = fragment.parent );
return '';
};
/* utils/isEqual.js */
var isEqual = function( a, b ) {
if ( a === null && b === null ) {
return true;
}
if ( typeof a === 'object' || typeof b === 'object' ) {
return false;
}
return a === b;
};
/* shared/createComponentBinding.js */
var createComponentBinding = function( circular, isArray, isEqual ) {
var runloop;
circular.push( function() {
return runloop = circular.runloop;
} );
var Binding = function( ractive, keypath, otherInstance, otherKeypath, priority ) {
this.root = ractive;
this.keypath = keypath;
this.priority = priority;
this.otherInstance = otherInstance;
this.otherKeypath = otherKeypath;
this.bind();
this.value = this.root.viewmodel.get( this.keypath );
};
Binding.prototype = {
setValue: function( value ) {
var this$0 = this;
// Only *you* can prevent infinite loops
if ( this.updating || this.counterpart && this.counterpart.updating ) {
this.value = value;
return;
}
// Is this a smart array update? If so, it'll update on its
// own, we shouldn't do anything
if ( isArray( value ) && value._ractive && value._ractive.setting ) {
return;
}
if ( !isEqual( value, this.value ) ) {
this.updating = true;
// TODO maybe the case that `value === this.value` - should that result
// in an update rather than a set?
runloop.addViewmodel( this.otherInstance.viewmodel );
this.otherInstance.viewmodel.set( this.otherKeypath, value );
this.value = value;
// TODO will the counterpart update after this line, during
// the runloop end cycle? may be a problem...
runloop.scheduleTask( function() {
return this$0.updating = false;
} );
}
},
bind: function() {
this.root.viewmodel.register( this.keypath, this );
},
rebind: function( newKeypath ) {
this.unbind();
this.keypath = newKeypath;
this.counterpart.otherKeypath = newKeypath;
this.bind();
},
unbind: function() {
this.root.viewmodel.unregister( this.keypath, this );
}
};
return function createComponentBinding( component, parentInstance, parentKeypath, childKeypath ) {
var hash, childInstance, bindings, priority, parentToChildBinding, childToParentBinding;
hash = parentKeypath + '=' + childKeypath;
bindings = component.bindings;
if ( bindings[ hash ] ) {
// TODO does this ever happen?
return;
}
bindings[ hash ] = true;
childInstance = component.instance;
priority = component.parentFragment.priority;
parentToChildBinding = new Binding( parentInstance, parentKeypath, childInstance, childKeypath, priority );
bindings.push( parentToChildBinding );
if ( childInstance.twoway ) {
childToParentBinding = new Binding( childInstance, childKeypath, parentInstance, parentKeypath, 1 );
bindings.push( childToParentBinding );
parentToChildBinding.counterpart = childToParentBinding;
childToParentBinding.counterpart = parentToChildBinding;
}
};
}( circular, isArray, isEqual );
/* shared/resolveRef.js */
var resolveRef = function( normaliseRef, getInnerContext, createComponentBinding ) {
var ancestorErrorMessage, getOptions;
ancestorErrorMessage = 'Could not resolve reference - too many "../" prefixes';
getOptions = {
evaluateWrapped: true
};
return function resolveRef( ractive, ref, fragment ) {
var context, key, index, keypath, parentValue, hasContextChain;
ref = normaliseRef( ref );
// If a reference begins '~/', it's a top-level reference
if ( ref.substr( 0, 2 ) === '~/' ) {
return ref.substring( 2 );
}
// If a reference begins with '.', it's either a restricted reference or
// an ancestor reference...
if ( ref.charAt( 0 ) === '.' ) {
return resolveAncestorReference( getInnerContext( fragment ), ref );
}
// ...otherwise we need to find the keypath
key = ref.split( '.' )[ 0 ];
do {
context = fragment.context;
if ( !context ) {
continue;
}
hasContextChain = true;
parentValue = ractive.viewmodel.get( context, getOptions );
if ( parentValue && ( typeof parentValue === 'object' || typeof parentValue === 'function' ) && key in parentValue ) {
return context + '.' + ref;
}
} while ( fragment = fragment.parent );
// Root/computed property?
if ( key in ractive.data || key in ractive.viewmodel.computations ) {
return ref;
}
// If this is an inline component, and it's not isolated, we
// can try going up the scope chain
if ( ractive._parent && !ractive.isolated ) {
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, true );
return;
}
keypath = resolveRef( ractive._parent, ref, fragment );
if ( keypath ) {
// Need to create an inter-component binding
ractive.viewmodel.set( ref, ractive._parent.viewmodel.get( keypath ), true );
createComponentBinding( ractive.component, ractive._parent, keypath, ref );
}
}
// If there's no context chain, and the instance is either a) isolated or
// b) an orphan, then we know that the keypath is identical to the reference
if ( !hasContextChain ) {
return ref;
}
if ( ractive.viewmodel.get( ref ) !== undefined ) {
return ref;
}
};
function resolveAncestorReference( baseContext, ref ) {
var contextKeys;
// {{.}} means 'current context'
if ( ref === '.' )
return baseContext;
contextKeys = baseContext ? baseContext.split( '.' ) : [];
// ancestor references (starting "../") go up the tree
if ( ref.substr( 0, 3 ) === '../' ) {
while ( ref.substr( 0, 3 ) === '../' ) {
if ( !contextKeys.length ) {
throw new Error( ancestorErrorMessage );
}
contextKeys.pop();
ref = ref.substring( 3 );
}
contextKeys.push( ref );
return contextKeys.join( '.' );
}
// not an ancestor reference - must be a restricted reference (prepended with "." or "./")
if ( !baseContext ) {
return ref.replace( /^\.\/?/, '' );
}
return baseContext + ref.replace( /^\.\//, '.' );
}
}( normaliseRef, getInnerContext, createComponentBinding );
/* global/TransitionManager.js */
var TransitionManager = function( removeFromArray ) {
var TransitionManager = function( callback, parent ) {
this.callback = callback;
this.parent = parent;
this.intros = [];
this.outros = [];
this.children = [];
this.totalChildren = this.outroChildren = 0;
this.detachQueue = [];
this.outrosComplete = false;
if ( parent ) {
parent.addChild( this );
}
};
TransitionManager.prototype = {
addChild: function( child ) {
this.children.push( child );
this.totalChildren += 1;
this.outroChildren += 1;
},
decrementOutros: function() {
this.outroChildren -= 1;
check( this );
},
decrementTotal: function() {
this.totalChildren -= 1;
check( this );
},
add: function( transition ) {
var list = transition.isIntro ? this.intros : this.outros;
list.push( transition );
},
remove: function( transition ) {
var list = transition.isIntro ? this.intros : this.outros;
removeFromArray( list, transition );
check( this );
},
init: function() {
this.ready = true;
check( this );
},
detachNodes: function() {
this.detachQueue.forEach( detach );
this.children.forEach( detachNodes );
}
};
function detach( element ) {
element.detach();
}
function detachNodes( tm ) {
tm.detachNodes();
}
function check( tm ) {
if ( !tm.ready || tm.outros.length || tm.outroChildren )
return;
// If all outros are complete, and we haven't already done this,
// we notify the parent if there is one, otherwise
// start detaching nodes
if ( !tm.outrosComplete ) {
if ( tm.parent ) {
tm.parent.decrementOutros( tm );
} else {
tm.detachNodes();
}
tm.outrosComplete = true;
}
// Once everything is done, we can notify parent transition
// manager and call the callback
if ( !tm.intros.length && !tm.totalChildren ) {
if ( typeof tm.callback === 'function' ) {
tm.callback();
}
if ( tm.parent ) {
tm.parent.decrementTotal();
}
}
}
return TransitionManager;
}( removeFromArray );
/* global/runloop.js */
var runloop = function( circular, removeFromArray, Promise, resolveRef, TransitionManager ) {
var batch, runloop, unresolved = [];
runloop = {
start: function( instance, returnPromise ) {
var promise, fulfilPromise;
if ( returnPromise ) {
promise = new Promise( function( f ) {
return 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;
return 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;
}
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 );
}
}
}
}( circular, removeFromArray, Promise, resolveRef, TransitionManager );
/* utils/createBranch.js */
var createBranch = function() {
var numeric = /^\s*[0-9]+\s*$/;
return function( key ) {
return numeric.test( key ) ? [] : {};
};
}();
/* viewmodel/prototype/get/magicAdaptor.js */
var viewmodel$get_magicAdaptor = function( runloop, createBranch, isArray ) {
var magicAdaptor, MagicWrapper;
try {
Object.defineProperty( {}, 'test', {
value: 0
} );
magicAdaptor = {
filter: function( object, keypath, ractive ) {
var keys, key, parentKeypath, parentWrapper, parentValue;
if ( !keypath ) {
return false;
}
keys = keypath.split( '.' );
key = keys.pop();
parentKeypath = keys.join( '.' );
// If the parent value is a wrapper, other than a magic wrapper,
// we shouldn't wrap this property
if ( ( parentWrapper = ractive.viewmodel.wrapped[ parentKeypath ] ) && !parentWrapper.magic ) {
return false;
}
parentValue = ractive.get( parentKeypath );
// if parentValue is an array that doesn't include this member,
// we should return false otherwise lengths will get messed up
if ( isArray( parentValue ) && /^[0-9]+$/.test( key ) ) {
return false;
}
return parentValue && ( typeof parentValue === 'object' || typeof parentValue === 'function' );
},
wrap: function( ractive, property, keypath ) {
return new MagicWrapper( ractive, property, keypath );
}
};
MagicWrapper = function( ractive, value, keypath ) {
var keys, objKeypath, template, siblings;
this.magic = true;
this.ractive = ractive;
this.keypath = keypath;
this.value = value;
keys = keypath.split( '.' );
this.prop = keys.pop();
objKeypath = keys.join( '.' );
this.obj = objKeypath ? ractive.get( objKeypath ) : ractive.data;
template = this.originalDescriptor = Object.getOwnPropertyDescriptor( this.obj, this.prop );
// Has this property already been wrapped?
if ( template && template.set && ( siblings = template.set._ractiveWrappers ) ) {
// Yes. Register this wrapper to this property, if it hasn't been already
if ( siblings.indexOf( this ) === -1 ) {
siblings.push( this );
}
return;
}
// No, it hasn't been wrapped
createAccessors( this, value, template );
};
MagicWrapper.prototype = {
get: function() {
return this.value;
},
reset: function( value ) {
if ( this.updating ) {
return;
}
this.updating = true;
this.obj[ this.prop ] = value;
// trigger set() accessor
runloop.addViewmodel( this.ractive.viewmodel );
this.ractive.viewmodel.mark( this.keypath );
this.updating = false;
},
set: function( key, value ) {
if ( this.updating ) {
return;
}
if ( !this.obj[ this.prop ] ) {
this.updating = true;
this.obj[ this.prop ] = createBranch( key );
this.updating = false;
}
this.obj[ this.prop ][ key ] = value;
},
teardown: function() {
var template, set, value, wrappers, index;
// If this method was called because the cache was being cleared as a
// result of a set()/update() call made by this wrapper, we return false
// so that it doesn't get torn down
if ( this.updating ) {
return false;
}
template = Object.getOwnPropertyDescriptor( this.obj, this.prop );
set = template && template.set;
if ( !set ) {
// most likely, this was an array member that was spliced out
return;
}
wrappers = set._ractiveWrappers;
index = wrappers.indexOf( this );
if ( index !== -1 ) {
wrappers.splice( index, 1 );
}
// Last one out, turn off the lights
if ( !wrappers.length ) {
value = this.obj[ this.prop ];
Object.defineProperty( this.obj, this.prop, this.originalDescriptor || {
writable: true,
enumerable: true,
configurable: true
} );
this.obj[ this.prop ] = value;
}
}
};
} catch ( err ) {
magicAdaptor = false;
}
return magicAdaptor;
function createAccessors( originalWrapper, value, template ) {
var object, property, oldGet, oldSet, get, set;
object = originalWrapper.obj;
property = originalWrapper.prop;
// Is this template configurable?
if ( template && !template.configurable ) {
// Special case - array length
if ( property === 'length' ) {
return;
}
throw new Error( 'Cannot use magic mode with property "' + property + '" - object is not configurable' );
}
// Time to wrap this property
if ( template ) {
oldGet = template.get;
oldSet = template.set;
}
get = oldGet || function() {
return value;
};
set = function( v ) {
if ( oldSet ) {
oldSet( v );
}
value = oldGet ? oldGet() : v;
set._ractiveWrappers.forEach( updateWrapper );
};
function updateWrapper( wrapper ) {
var keypath, ractive;
wrapper.value = value;
if ( wrapper.updating ) {
return;
}
ractive = wrapper.ractive;
keypath = wrapper.keypath;
wrapper.updating = true;
runloop.start( ractive );
ractive.viewmodel.mark( keypath );
runloop.end();
wrapper.updating = false;
}
// Create an array of wrappers, in case other keypaths/ractives depend on this property.
// Handily, we can store them as a property of the set function. Yay JavaScript.
set._ractiveWrappers = [ originalWrapper ];
Object.defineProperty( object, property, {
get: get,
set: set,
enumerable: true,
configurable: true
} );
}
}( runloop, createBranch, isArray );
/* config/magic.js */
var magic = function( magicAdaptor ) {
return !!magicAdaptor;
}( viewmodel$get_magicAdaptor );
/* config/namespaces.js */
var namespaces = {
html: 'http://www.w3.org/1999/xhtml',
mathml: 'http://www.w3.org/1998/Math/MathML',
svg: 'http://www.w3.org/2000/svg',
xlink: 'http://www.w3.org/1999/xlink',
xml: 'http://www.w3.org/XML/1998/namespace',
xmlns: 'http://www.w3.org/2000/xmlns/'
};
/* utils/createElement.js */
var createElement = function( svg, namespaces ) {
var createElement;
// Test for SVG support
if ( !svg ) {
createElement = function( type, ns ) {
if ( ns && ns !== namespaces.html ) {
throw 'This browser does not support namespaces other than http://www.w3.org/1999/xhtml. The most likely cause of this error is that you\'re trying to render SVG in an older browser. See http://docs.ractivejs.org/latest/svg-and-older-browsers for more information';
}
return document.createElement( type );
};
} else {
createElement = function( type, ns ) {
if ( !ns || ns === namespaces.html ) {
return document.createElement( type );
}
return document.createElementNS( ns, type );
};
}
return createElement;
}( svg, namespaces );
/* config/isClient.js */
var isClient = function() {
var isClient = typeof document === 'object';
return isClient;
}();
/* utils/defineProperty.js */
var defineProperty = function( isClient ) {
var defineProperty;
try {
Object.defineProperty( {}, 'test', {
value: 0
} );
if ( isClient ) {
Object.defineProperty( document.createElement( 'div' ), 'test', {
value: 0
} );
}
defineProperty = Object.defineProperty;
} catch ( err ) {
// Object.defineProperty doesn't exist, or we're in IE8 where you can
// only use it with DOM objects (what the fuck were you smoking, MSFT?)
defineProperty = function( obj, prop, desc ) {
obj[ prop ] = desc.value;
};
}
return defineProperty;
}( isClient );
/* utils/defineProperties.js */
var defineProperties = function( createElement, defineProperty, isClient ) {
var defineProperties;
try {
try {
Object.defineProperties( {}, {
test: {
value: 0
}
} );
} catch ( err ) {
// TODO how do we account for this? noMagic = true;
throw err;
}
if ( isClient ) {
Object.defineProperties( createElement( 'div' ), {
test: {
value: 0
}
} );
}
defineProperties = Object.defineProperties;
} catch ( err ) {
defineProperties = function( obj, props ) {
var prop;
for ( prop in props ) {
if ( props.hasOwnProperty( prop ) ) {
defineProperty( obj, prop, props[ prop ] );
}
}
};
}
return defineProperties;
}( createElement, defineProperty, isClient );
/* Ractive/prototype/shared/add.js */
var Ractive$shared_add = function( isNumeric ) {
return function add( root, keypath, d ) {
var value;
if ( typeof keypath !== 'string' || !isNumeric( d ) ) {
throw new Error( 'Bad arguments' );
}
value = +root.get( keypath ) || 0;
if ( !isNumeric( value ) ) {
throw new Error( 'Cannot add to a non-numeric value' );
}
return root.set( keypath, value + d );
};
}( isNumeric );
/* Ractive/prototype/add.js */
var Ractive$add = function( add ) {
return function Ractive$add( keypath, d ) {
return add( this, keypath, d === undefined ? 1 : +d );
};
}( Ractive$shared_add );
/* utils/normaliseKeypath.js */
var normaliseKeypath = function( normaliseRef ) {
var leadingDot = /^\.+/;
return function normaliseKeypath( keypath ) {
return normaliseRef( keypath ).replace( leadingDot, '' );
};
}( normaliseRef );
/* config/vendors.js */
var vendors = [
'o',
'ms',
'moz',
'webkit'
];
/* utils/requestAnimationFrame.js */
var requestAnimationFrame = function( vendors ) {
var requestAnimationFrame;
// If window doesn't exist, we don't need requestAnimationFrame
if ( typeof window === 'undefined' ) {
requestAnimationFrame = null;
} else {
// https://gist.github.com/paulirish/1579671
( function( vendors, lastTime, window ) {
var x, setTimeout;
if ( window.requestAnimationFrame ) {
return;
}
for ( x = 0; x < vendors.length && !window.requestAnimationFrame; ++x ) {
window.requestAnimationFrame = window[ vendors[ x ] + 'RequestAnimationFrame' ];
}
if ( !window.requestAnimationFrame ) {
setTimeout = window.setTimeout;
window.requestAnimationFrame = function( callback ) {
var currTime, timeToCall, id;
currTime = Date.now();
timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
id = setTimeout( function() {
callback( currTime + timeToCall );
}, timeToCall );
lastTime = currTime + timeToCall;
return id;
};
}
}( vendors, 0, window ) );
requestAnimationFrame = window.requestAnimationFrame;
}
return requestAnimationFrame;
}( vendors );
/* utils/getTime.js */
var getTime = function() {
var getTime;
if ( typeof window !== 'undefined' && window.performance && typeof window.performance.now === 'function' ) {
getTime = function() {
return window.performance.now();
};
} else {
getTime = function() {
return Date.now();
};
}
return getTime;
}();
/* shared/animations.js */
var animations = function( rAF, getTime, runloop ) {
var queue = [];
var animations = {
tick: function() {
var i, animation, now;
now = getTime();
runloop.start();
for ( i = 0; i < queue.length; i += 1 ) {
animation = queue[ i ];
if ( !animation.tick( now ) ) {
// animation is complete, remove it from the stack, and decrement i so we don't miss one
queue.splice( i--, 1 );
}
}
runloop.end();
if ( queue.length ) {
rAF( animations.tick );
} else {
animations.running = false;
}
},
add: function( animation ) {
queue.push( animation );
if ( !animations.running ) {
animations.running = true;
rAF( animations.tick );
}
},
// TODO optimise this
abort: function( keypath, root ) {
var i = queue.length,
animation;
while ( i-- ) {
animation = queue[ i ];
if ( animation.root === root && animation.keypath === keypath ) {
animation.stop();
}
}
}
};
return animations;
}( requestAnimationFrame, getTime, runloop );
/* utils/warn.js */
var warn = function() {
/* global console */
var warn, warned = {};
if ( typeof console !== 'undefined' && typeof console.warn === 'function' && typeof console.warn.apply === 'function' ) {
warn = function( message, allowDuplicates ) {
if ( !allowDuplicates ) {
if ( warned[ message ] ) {
return;
}
warned[ message ] = true;
}
console.warn( message );
};
} else {
warn = function() {};
}
return warn;
}();
/* config/options/css/transform.js */
var transform = function() {
var selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g,
commentsPattern = /\/\*.*?\*\//g,
selectorUnitPattern = /((?:(?:\[[^\]+]\])|(?:[^\s\+\>\~:]))+)((?::[^\s\+\>\~]+)?\s*[\s\+\>\~]?)\s*/g,
mediaQueryPattern = /^@media/,
dataRvcGuidPattern = /\[data-rvcguid="[a-z0-9-]+"]/g;
return function transformCss( css, guid ) {
var transformed, addGuid;
addGuid = function( selector ) {
var selectorUnits, match, unit, dataAttr, base, prepended, appended, i, transformed = [];
selectorUnits = [];
while ( match = selectorUnitPattern.exec( selector ) ) {
selectorUnits.push( {
str: match[ 0 ],
base: match[ 1 ],
modifiers: match[ 2 ]
} );
}
// For each simple selector within the selector, we need to create a version
// that a) combines with the guid, and b) is inside the guid
dataAttr = '[data-rvcguid="' + guid + '"]';
base = selectorUnits.map( extractString );
i = selectorUnits.length;
while ( i-- ) {
appended = base.slice();
// Pseudo-selectors should go after the attribute selector
unit = selectorUnits[ i ];
appended[ i ] = unit.base + dataAttr + unit.modifiers || '';
prepended = base.slice();
prepended[ i ] = dataAttr + ' ' + prepended[ i ];
transformed.push( appended.join( ' ' ), prepended.join( ' ' ) );
}
return transformed.join( ', ' );
};
if ( dataRvcGuidPattern.test( css ) ) {
transformed = css.replace( dataRvcGuidPattern, '[data-rvcguid="' + guid + '"]' );
} else {
transformed = css.replace( commentsPattern, '' ).replace( selectorsPattern, function( match, $1 ) {
var selectors, transformed;
// don't transform media queries!
if ( mediaQueryPattern.test( $1 ) )
return match;
selectors = $1.split( ',' ).map( trim );
transformed = selectors.map( addGuid ).join( ', ' ) + ' ';
return match.replace( $1, transformed );
} );
}
return transformed;
};
function trim( str ) {
if ( str.trim ) {
return str.trim();
}
return str.replace( /^\s+/, '' ).replace( /\s+$/, '' );
}
function extractString( unit ) {
return unit.str;
}
}();
/* config/options/css/css.js */
var css = function( transformCss ) {
var cssConfig = {
name: 'css',
extend: extend,
init: function() {}
};
function extend( Parent, proto, options ) {
var guid = proto.constructor._guid,
css;
if ( css = getCss( options.css, options, guid ) || getCss( Parent.css, Parent, guid ) ) {
proto.constructor.css = css;
}
}
function getCss( css, target, guid ) {
if ( !css ) {
return;
}
return target.noCssTransform ? css : transformCss( css, guid );
}
return cssConfig;
}( transform );
/* utils/wrapMethod.js */
var wrapMethod = function() {
return function( method, superMethod, force ) {
if ( force || needsSuper( method, superMethod ) ) {
return function() {
var hasSuper = '_super' in this,
_super = this._super,
result;
this._super = superMethod;
result = method.apply( this, arguments );
if ( hasSuper ) {
this._super = _super;
}
return result;
};
} else {
return method;
}
};
function needsSuper( method, superMethod ) {
return typeof superMethod === 'function' && /_super/.test( method );
}
}();
/* config/options/data.js */
var data = function( wrap ) {
var dataConfig = {
name: 'data',
extend: extend,
init: init,
reset: reset
};
return dataConfig;
function combine( Parent, target, options ) {
var value = options.data || {},
parentValue = getAddedKeys( Parent.prototype.data );
return dispatch( parentValue, value );
}
function extend( Parent, proto, options ) {
proto.data = combine( Parent, proto, options );
}
function init( Parent, ractive, options ) {
var value = options.data,
result = combine( Parent, ractive, options );
if ( typeof result === 'function' ) {
result = result.call( ractive, value ) || value;
}
return ractive.data = result || {};
}
function reset( ractive ) {
var result = this.init( ractive.constructor, ractive, ractive );
if ( result ) {
ractive.data = result;
return true;
}
}
function getAddedKeys( parent ) {
// only for functions that had keys added
if ( typeof parent !== 'function' || !Object.keys( parent ).length ) {
return parent;
}
// copy the added keys to temp 'object', otherwise
// parent would be interpreted as 'function' by dispatch
var temp = {};
copy( parent, temp );
// roll in added keys
return dispatch( parent, temp );
}
function dispatch( parent, child ) {
if ( typeof child === 'function' ) {
return extendFn( child, parent );
} else if ( typeof parent === 'function' ) {
return fromFn( child, parent );
} else {
return fromProperties( child, parent );
}
}
function copy( from, to, fillOnly ) {
for ( var key in from ) {
if ( fillOnly && key in to ) {
continue;
}
to[ key ] = from[ key ];
}
}
function fromProperties( child, parent ) {
child = child || {};
if ( !parent ) {
return child;
}
copy( parent, child, true );
return child;
}
function fromFn( child, parentFn ) {
return function( data ) {
var keys;
if ( child ) {
// Track the keys that our on the child,
// but not on the data. We'll need to apply these
// after the parent function returns.
keys = [];
for ( var key in child ) {
if ( !data || !( key in data ) ) {
keys.push( key );
}
}
}
// call the parent fn, use data if no return value
data = parentFn.call( this, data ) || data;
// Copy child keys back onto data. The child keys
// should take precedence over whatever the
// parent did with the data.
if ( keys && keys.length ) {
data = data || {};
keys.forEach( function( key ) {
data[ key ] = child[ key ];
} );
}
return data;
};
}
function extendFn( childFn, parent ) {
var parentFn;
if ( typeof parent !== 'function' ) {
// copy props to data
parentFn = function( data ) {
fromProperties( data, parent );
};
} else {
parentFn = function( data ) {
// give parent function it's own this._super context,
// otherwise this._super is from child and
// causes infinite loop
parent = wrap( parent, function() {}, true );
return parent.call( this, data ) || data;
};
}
return wrap( childFn, parentFn );
}
}( wrapMethod );
/* config/errors.js */
var errors = {
missingParser: 'Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser',
mergeComparisonFail: 'Merge operation: comparison failed. Falling back to identity checking',
noComponentEventArguments: 'Components currently only support simple events - you cannot include arguments. Sorry!',
noTemplateForPartial: 'Could not find template for partial "{name}"',
noNestedPartials: 'Partials ({{>{name}}}) cannot contain nested inline partials',
evaluationError: 'Error evaluating "{uniqueString}": {err}',
badArguments: 'Bad arguments "{arguments}". I\'m not allowed to argue unless you\'ve paid.',
failedComputation: 'Failed to compute "{key}": {err}',
missingPlugin: 'Missing "{name}" {plugin} plugin. You may need to download a {plugin} via http://docs.ractivejs.org/latest/plugins#{plugin}s',
badRadioInputBinding: 'A radio input can have two-way binding on its name attribute, or its checked attribute - not both',
noRegistryFunctionReturn: 'A function was specified for "{name}" {registry}, but no {registry} was returned'
};
/* config/types.js */
var types = {
TEXT: 1,
INTERPOLATOR: 2,
TRIPLE: 3,
SECTION: 4,
INVERTED: 5,
CLOSING: 6,
ELEMENT: 7,
PARTIAL: 8,
COMMENT: 9,
DELIMCHANGE: 10,
MUSTACHE: 11,
TAG: 12,
ATTRIBUTE: 13,
CLOSING_TAG: 14,
COMPONENT: 15,
NUMBER_LITERAL: 20,
STRING_LITERAL: 21,
ARRAY_LITERAL: 22,
OBJECT_LITERAL: 23,
BOOLEAN_LITERAL: 24,
GLOBAL: 26,
KEY_VALUE_PAIR: 27,
REFERENCE: 30,
REFINEMENT: 31,
MEMBER: 32,
PREFIX_OPERATOR: 33,
BRACKETED: 34,
CONDITIONAL: 35,
INFIX_OPERATOR: 36,
INVOCATION: 40,
SECTION_IF: 50,
SECTION_UNLESS: 51,
SECTION_EACH: 52,
SECTION_WITH: 53
};
/* utils/create.js */
var create = function() {
var create;
try {
Object.create( null );
create = Object.create;
} catch ( err ) {
// sigh
create = function() {
var F = function() {};
return function( proto, props ) {
var obj;
if ( proto === null ) {
return {};
}
F.prototype = proto;
obj = new F();
if ( props ) {
Object.defineProperties( obj, props );
}
return obj;
};
}();
}
return create;
}();
/* parse/Parser/expressions/shared/errors.js */
var parse_Parser_expressions_shared_errors = {
expectedExpression: 'Expected a JavaScript expression',
expectedParen: 'Expected closing paren'
};
/* parse/Parser/expressions/primary/literal/numberLiteral.js */
var numberLiteral = function( types ) {
// bulletproof number regex from https://gist.github.com/Rich-Harris/7544330
var numberPattern = /^(?:[+-]?)(?:(?:(?:0|[1-9]\d*)?\.\d+)|(?:(?:0|[1-9]\d*)\.)|(?:0|[1-9]\d*))(?:[eE][+-]?\d+)?/;
return function( parser ) {
var result;
if ( result = parser.matchPattern( numberPattern ) ) {
return {
t: types.NUMBER_LITERAL,
v: result
};
}
return null;
};
}( types );
/* parse/Parser/expressions/primary/literal/booleanLiteral.js */
var booleanLiteral = function( types ) {
return function( parser ) {
var remaining = parser.remaining();
if ( remaining.substr( 0, 4 ) === 'true' ) {
parser.pos += 4;
return {
t: types.BOOLEAN_LITERAL,
v: 'true'
};
}
if ( remaining.substr( 0, 5 ) === 'false' ) {
parser.pos += 5;
return {
t: types.BOOLEAN_LITERAL,
v: 'false'
};
}
return null;
};
}( types );
/* parse/Parser/expressions/primary/literal/stringLiteral/makeQuotedStringMatcher.js */
var makeQuotedStringMatcher = function() {
var stringMiddlePattern, escapeSequencePattern, lineContinuationPattern;
// Match one or more characters until: ", ', \, or EOL/EOF.
// EOL/EOF is written as (?!.) (meaning there's no non-newline char next).
stringMiddlePattern = /^(?=.)[^"'\\]+?(?:(?!.)|(?=["'\\]))/;
// Match one escape sequence, including the backslash.
escapeSequencePattern = /^\\(?:['"\\bfnrt]|0(?![0-9])|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|(?=.)[^ux0-9])/;
// Match one ES5 line continuation (backslash + line terminator).
lineContinuationPattern = /^\\(?:\r\n|[\u000A\u000D\u2028\u2029])/;
// Helper for defining getDoubleQuotedString and getSingleQuotedString.
return function( okQuote ) {
return function( parser ) {
var start, literal, done, next;
start = parser.pos;
literal = '"';
done = false;
while ( !done ) {
next = parser.matchPattern( stringMiddlePattern ) || parser.matchPattern( escapeSequencePattern ) || parser.matchString( okQuote );
if ( next ) {
if ( next === '"' ) {
literal += '\\"';
} else if ( next === '\\\'' ) {
literal += '\'';
} else {
literal += next;
}
} else {
next = parser.matchPattern( lineContinuationPattern );
if ( next ) {
// convert \(newline-like) into a \u escape, which is allowed in JSON
literal += '\\u' + ( '000' + next.charCodeAt( 1 ).toString( 16 ) ).slice( -4 );
} else {
done = true;
}
}
}
literal += '"';
// use JSON.parse to interpret escapes
return JSON.parse( literal );
};
};
}();
/* parse/Parser/expressions/primary/literal/stringLiteral/singleQuotedString.js */
var singleQuotedString = function( makeQuotedStringMatcher ) {
return makeQuotedStringMatcher( '"' );
}( makeQuotedStringMatcher );
/* parse/Parser/expressions/primary/literal/stringLiteral/doubleQuotedString.js */
var doubleQuotedString = function( makeQuotedStringMatcher ) {
return makeQuotedStringMatcher( '\'' );
}( makeQuotedStringMatcher );
/* parse/Parser/expressions/primary/literal/stringLiteral/_stringLiteral.js */
var stringLiteral = function( types, getSingleQuotedString, getDoubleQuotedString ) {
return function( parser ) {
var start, string;
start = parser.pos;
if ( parser.matchString( '"' ) ) {
string = getDoubleQuotedString( parser );
if ( !parser.matchString( '"' ) ) {
parser.pos = start;
return null;
}
return {
t: types.STRING_LITERAL,
v: string
};
}
if ( parser.matchString( '\'' ) ) {
string = getSingleQuotedString( parser );
if ( !parser.matchString( '\'' ) ) {
parser.pos = start;
return null;
}
return {
t: types.STRING_LITERAL,
v: string
};
}
return null;
};
}( types, singleQuotedString, doubleQuotedString );
/* parse/Parser/expressions/shared/patterns.js */
var patterns = {
name: /^[a-zA-Z_$][a-zA-Z_$0-9]*/
};
/* parse/Parser/expressions/shared/key.js */
var key = function( getStringLiteral, getNumberLiteral, patterns ) {
var identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
// http://mathiasbynens.be/notes/javascript-properties
// can be any name, string literal, or number literal
return function( parser ) {
var token;
if ( token = getStringLiteral( parser ) ) {
return identifier.test( token.v ) ? token.v : '"' + token.v.replace( /"/g, '\\"' ) + '"';
}
if ( token = getNumberLiteral( parser ) ) {
return token.v;
}
if ( token = parser.matchPattern( patterns.name ) ) {
return token;
}
};
}( stringLiteral, numberLiteral, patterns );
/* parse/Parser/expressions/primary/literal/objectLiteral/keyValuePair.js */
var keyValuePair = function( types, getKey ) {
return function( parser ) {
var start, key, value;
start = parser.pos;
// allow whitespace between '{' and key
parser.allowWhitespace();
key = getKey( parser );
if ( key === null ) {
parser.pos = start;
return null;
}
// allow whitespace between key and ':'
parser.allowWhitespace();
// next character must be ':'
if ( !parser.matchString( ':' ) ) {
parser.pos = start;
return null;
}
// allow whitespace betwee