ractive
Version:
Next-generation DOM manipulation
2,128 lines (1,642 loc) • 83.8 kB
JavaScript
(function ( doc ) {
// Shims for older browsers
if ( !Date.now ) {
Date.now = function () { return +new Date(); };
}
if ( !doc.createElementNS ) {
doc.createElementNS = function ( ns, type ) {
if ( ns !== null && ns !== 'http://www.w3.org/1999/xhtml' ) {
throw 'This browser does not support namespaces other than http://www.w3.org/1999/xhtml';
}
return doc.createElement( type );
};
}
if ( !Element.prototype.contains ) {
Element.prototype.contains = function ( el ) {
while ( el.parentNode ) {
if ( el.parentNode === this ) {
return true;
}
el = el.parentNode;
}
return false;
};
}
if ( !String.prototype.trim ) {
String.prototype.trim = function () {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
}
// https://gist.github.com/jonathantneal/3748027
if ( !window.addEventListener ) {
(function ( WindowPrototype, DocumentPrototype, ElementPrototype, addEventListener, removeEventListener, dispatchEvent, registry ) {
WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function (type, listener) {
var target = this;
registry.unshift([target, type, listener, function (event) {
event.currentTarget = target;
event.preventDefault = function () { event.returnValue = false; };
event.stopPropagation = function () { event.cancelBubble = true; };
event.target = event.srcElement || target;
listener.call(target, event);
}]);
this.attachEvent("on" + type, registry[0][3]);
};
WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function (type, listener) {
var index, register;
for ( index = 0, register; register = registry[index]; ++index ) {
if ( register[0] === this && register[1] === type && register[2] === listener ) {
return this.detachEvent("on" + type, registry.splice(index, 1)[0][3]);
}
}
};
WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function (eventObject) {
return this.fireEvent("on" + eventObject.type, eventObject);
};
}( Window.prototype, HTMLDocument.prototype, Element.prototype, "addEventListener", "removeEventListener", "dispatchEvent", [] ));
}
// Array extras
if ( !Array.prototype.indexOf ) {
Array.prototype.indexOf = function ( needle, i ) {
var len;
if ( i === undefined ) {
i = 0;
}
if ( i < 0 ) {
i+= this.length;
}
if ( i < 0 ) {
i = 0;
}
for ( len = this.length; i<len; i++ ) {
if ( this.hasOwnProperty( i ) && this[i] === needle ) {
return i;
}
}
return -1;
};
}
if ( !Array.prototype.forEach ) {
Array.prototype.forEach = function ( callback, context ) {
var i, len;
for ( i=0, len=this.length; i<len; i+=1 ) {
if ( this.hasOwnProperty( i ) ) {
callback.call( context, this[i], i, this );
}
}
};
}
if ( !Array.prototype.map ) {
Array.prototype.map = function ( mapper, context ) {
var i, len, mapped = [];
for ( i=0, len=this.length; i<len; i+=1 ) {
if ( this.hasOwnProperty( i ) ) {
mapped[i] = mapper.call( context, this[i], i, this );
}
}
return mapped;
};
}
if ( !Array.prototype.map ) {
Array.prototype.map = function ( filter, context ) {
var i, len, filtered = [];
for ( i=0, len=this.length; i<len; i+=1 ) {
if ( this.hasOwnProperty( i ) && filter.call( context, this[i], i, this ) ) {
filtered[ filtered.length ] = this[i];
}
}
return filtered;
};
}
}( document ));
/*! Ractive - v0.3.0 - 2013-05-30
* Faster, easier, better interactive web development
* http://rich-harris.github.com/Ractive/
* Copyright (c) 2013 Rich Harris; Licensed MIT */
/*jslint eqeq: true, plusplus: true */
/*global document, HTMLElement */
(function ( global ) {
'use strict';
var Ractive,
proto = {},
// properties of the public Ractive object
adaptors = {},
eventDefinitions = {},
easing,
extend,
interpolate,
interpolators,
// internal utils
splitKeypath,
isArray,
isObject,
isNumeric,
isEqual,
getEl,
// internally used caches
keypathCache = {},
// internally used constructors
DomFragment,
TextFragment,
Evaluator,
Animation,
// internally used regexes
leadingWhitespace = /^\s+/,
trailingWhitespace = /\s+$/,
// other bits and pieces
initMustache,
updateMustache,
resolveMustache,
evaluateMustache,
initFragment,
updateSection,
animationCollection,
// array modification
registerKeypathToArray,
unregisterKeypathFromArray,
// parser and tokenizer
stripCommentTokens,
stripHtmlComments,
stripStandalones,
// error messages
missingParser = 'Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser',
// constants
TEXT = 1,
INTERPOLATOR = 2,
TRIPLE = 3,
SECTION = 4,
INVERTED = 5,
CLOSING = 6,
ELEMENT = 7,
PARTIAL = 8,
COMMENT = 9,
DELIMCHANGE = 10,
MUSTACHE = 11,
TAG = 12,
ATTR_VALUE_TOKEN = 13,
EXPRESSION = 14,
NUMBER_LITERAL = 20,
STRING_LITERAL = 21,
ARRAY_LITERAL = 22,
OBJECT_LITERAL = 23,
BOOLEAN_LITERAL = 24,
LITERAL = 25,
GLOBAL = 26,
REFERENCE = 30,
REFINEMENT = 31,
MEMBER = 32,
PREFIX_OPERATOR = 33,
BRACKETED = 34,
CONDITIONAL = 35,
INFIX_OPERATOR = 36,
INVOCATION = 40,
// namespaces
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/'
};
proto.animate = function ( keypath, to, options ) {
var easing, duration, animation, i, keys;
options = options || {};
// cancel any existing animation
// TODO what about upstream/downstream keypaths?
i = animationCollection.animations.length;
while ( i-- ) {
if ( animationCollection.animations[ i ].keypath === keypath ) {
animationCollection.animations[ i ].stop();
}
}
// easing function
if ( options.easing ) {
if ( typeof options.easing === 'function' ) {
easing = options.easing;
}
else {
if ( this.easing && this.easing[ options.easing ] ) {
// use instance easing function first
easing = this.easing[ options.easing ];
} else {
// fallback to global easing functions
easing = Ractive.easing[ options.easing ];
}
}
if ( typeof easing !== 'function' ) {
easing = null;
}
}
// duration
duration = ( options.duration === undefined ? 400 : options.duration );
keys = splitKeypath( keypath );
animation = new Animation({
keys: keys,
from: this.get( keys ),
to: to,
root: this,
duration: duration,
easing: easing,
step: options.step,
complete: options.complete
});
animationCollection.push( animation );
};
proto.bind = function ( adaptor ) {
var bound = this._bound;
if ( bound.indexOf( adaptor ) === -1 ) {
bound[ bound.length ] = adaptor;
adaptor.init( this );
}
};
proto.fire = function ( eventName ) {
var args, i, len, subscribers = this._subs[ eventName ];
if ( !subscribers ) {
return;
}
args = Array.prototype.slice.call( arguments, 1 );
for ( i=0, len=subscribers.length; i<len; i+=1 ) {
subscribers[i].apply( this, args );
}
};
// TODO use dontNormalise
proto.get = function ( keypath, dontNormalise ) {
var keys, normalised, key, parentKeypath, parentValue, value;
if ( !keypath ) {
return this.data;
}
if ( isArray( keypath ) ) {
keys = keypath.slice(); // clone
normalised = keys.join( '.' );
}
else {
// cache hit? great
if ( this._cache.hasOwnProperty( keypath ) ) {
return this._cache[ keypath ];
}
keys = splitKeypath( keypath );
normalised = keys.join( '.' );
}
// we may have a cache hit now that it's been normalised
if ( this._cache.hasOwnProperty( normalised ) ) {
return this._cache[ normalised ];
}
// otherwise it looks like we need to do some work
key = keys.pop();
parentValue = ( keys.length ? this.get( keys ) : this.data );
if ( typeof parentValue !== 'object' || !parentValue.hasOwnProperty( key ) ) {
return;
}
value = parentValue[ key ];
// update map of dependants
parentKeypath = keys.join( '.' );
if ( !this._depsMap[ parentKeypath ] ) {
this._depsMap[ parentKeypath ] = [];
}
if ( this._depsMap[ parentKeypath ].indexOf( normalised ) === -1 ) {
this._depsMap[ parentKeypath ].push( normalised );
}
// Is this an array that needs to be wrapped?
if ( this.modifyArrays ) {
// if it's not an expression, is an array, and we're not here because it sent us here, wrap it
if ( ( normalised.charAt( 0 ) !== '(' ) && isArray( value ) && ( !value.ractive || !value._ractive.setting ) ) {
registerKeypathToArray( value, normalised, this );
}
}
// Update cache
this._cache[ normalised ] = value;
return value;
};
var teardown, clearCache, registerDependant, unregisterDependant, notifyDependants, resolveRef;
teardown = function ( thing ) {
if ( !thing.keypath ) {
// this was on the 'unresolved' list, we need to remove it
var index = thing.root._pendingResolution.indexOf( thing );
if ( index !== -1 ) {
thing.root._pendingResolution.splice( index, 1 );
}
} else {
// this was registered as a dependency
unregisterDependant( thing.root, thing.keypath, thing, thing.priority || 0 );
}
if ( thing.evaluator ) {
thing.evaluator.teardown();
}
};
clearCache = function ( root, keypath ) {
var value, dependants = root._depsMap[ keypath ], i;
// is this a modified array, which shouldn't fire set events on this keypath anymore?
if ( root.modifyArrays ) {
if ( keypath.charAt( 0 ) !== '(' ) { // expressions don't get wrapped
value = root._cache[ keypath ];
if ( isArray( value ) && !value._ractive.setting ) {
unregisterKeypathFromArray( value, keypath, root );
}
}
}
delete root._cache[ keypath ];
if ( !dependants ) {
return;
}
i = dependants.length;
while ( i-- ) {
clearCache( root, dependants[i] );
}
};
registerDependant = function ( root, keypath, dependant, priority ) {
var deps;
if ( !root._deps[ keypath ] ) {
root._deps[ keypath ] = [];
}
deps = root._deps[ keypath ];
if ( !deps[ priority ] ) {
deps[ priority ] = [ dependant ];
return;
}
deps = deps[ priority ];
if ( deps.indexOf( dependant ) === -1 ) {
deps[ deps.length ] = dependant;
}
};
unregisterDependant = function ( root, keypath, dependant, priority ) {
var deps, i, keep;
deps = root._deps[ keypath ][ priority ];
deps.splice( deps.indexOf( dependant ), 1 );
if ( !deps.length ) {
root._deps[ keypath ][ priority ] = null;
}
// can we forget this keypath altogether?
i = root._deps[ keypath ].length;
while ( i-- ) {
if ( root._deps[ keypath ][i] ) {
keep = true;
break;
}
}
if ( !keep ) {
// yes, we can forget it
root._deps[ keypath ] = null;
}
};
notifyDependants = function ( root, keypath, onlyDirect ) {
var depsByPriority, deps, i, j, len, childDeps;
depsByPriority = root._deps[ keypath ];
if ( depsByPriority ) {
len = depsByPriority.length;
for ( i=0; i<len; i+=1 ) {
deps = depsByPriority[i];
if ( deps ) {
j = deps.length;
while ( j-- ) {
deps[j].update();
}
}
}
}
// If we're only notifying direct dependants, not dependants
// of downstream keypaths, then YOU SHALL NOT PASS
if ( onlyDirect ) {
return;
}
// cascade
childDeps = root._depsMap[ keypath ];
if ( childDeps ) {
i = childDeps.length;
while ( i-- ) {
notifyDependants( root, childDeps[i] );
// TODO at some point, no-longer extant dependants need to be removed
// from the map. However a keypath can have no direct dependants yet
// still have downstream dependants, so it's not entirely straightforward
}
}
};
// Resolve a full keypath from `ref` within the given `contextStack` (e.g.
// `'bar.baz'` within the context stack `['foo']` might resolve to `'foo.bar.baz'`
resolveRef = function ( root, ref, contextStack ) {
var keys, lastKey, innerMostContext, contextKeys, parentValue, keypath;
// Implicit iterators - i.e. {{.}} - are a special case
if ( ref === '.' ) {
return contextStack[ contextStack.length - 1 ];
}
keys = splitKeypath( ref );
lastKey = keys.pop();
// Clone the context stack, so we don't mutate the original
contextStack = contextStack.concat();
// Take each context from the stack, working backwards from the innermost context
while ( contextStack.length ) {
innerMostContext = contextStack.pop();
contextKeys = splitKeypath( innerMostContext );
parentValue = root.get( contextKeys.concat( keys ) );
if ( typeof parentValue === 'object' && parentValue.hasOwnProperty( lastKey ) ) {
keypath = innerMostContext + '.' + ref;
break;
}
}
if ( !keypath && root.get( ref ) !== undefined ) {
keypath = ref;
}
return keypath;
};
proto.link = function ( keypath ) {
var self = this;
return function ( value ) {
self.set( keypath, value );
};
};
(function ( proto ) {
var observe, updateObserver;
proto.observe = function ( keypath, callback, options ) {
var observers = [], k;
if ( typeof keypath === 'object' ) {
options = callback;
for ( k in keypath ) {
if ( keypath.hasOwnProperty( k ) ) {
callback = keypath[k];
observers[ observers.length ] = observe( this, k, callback, options );
}
}
return {
cancel: function () {
while ( observers.length ) {
observers.pop().cancel();
}
}
};
}
return observe( this, keypath, callback, options );
};
observe = function ( root, keypath, callback, options ) {
var observer, lastValue, context;
options = options || {};
context = options.context || root;
observer = {
update: function () {
var value;
// TODO create, and use, an internal get method instead - we can skip checks
value = root.get( keypath, true );
if ( !isEqual( value, lastValue ) ) {
callback.call( context, value, lastValue );
lastValue = value;
}
},
cancel: function () {
unregisterDependant( root, keypath, observer, 0 );
}
};
if ( options.init !== false ) {
observer.update();
}
registerDependant( root, keypath, observer, 0 );
return observer;
};
}( proto ));
proto.off = function ( eventName, callback ) {
var subscribers, index;
// if no callback specified, remove all callbacks
if ( !callback ) {
// if no event name specified, remove all callbacks for all events
if ( !eventName ) {
this._subs = {};
} else {
this._subs[ eventName ] = [];
}
}
subscribers = this._subs[ eventName ];
if ( subscribers ) {
index = subscribers.indexOf( callback );
if ( index !== -1 ) {
subscribers.splice( index, 1 );
}
}
};
proto.on = function ( eventName, callback ) {
var self = this, listeners, n;
// allow mutliple listeners to be bound in one go
if ( typeof eventName === 'object' ) {
listeners = [];
for ( n in eventName ) {
if ( eventName.hasOwnProperty( n ) ) {
listeners[ listeners.length ] = this.on( n, eventName[ n ] );
}
}
return {
cancel: function () {
while ( listeners.length ) {
listeners.pop().cancel();
}
}
};
}
if ( !this._subs[ eventName ] ) {
this._subs[ eventName ] = [ callback ];
} else {
this._subs[ eventName ].push( callback );
}
return {
cancel: function () {
self.off( eventName, callback );
}
};
};
// Render instance to element specified here or at initialization
proto.render = function ( options ) {
var el = ( options.el ? getEl( options.el ) : this.el );
if ( !el ) {
throw new Error( 'You must specify a DOM element to render to' );
}
// Clear the element, unless `append` is `true`
if ( !options.append ) {
el.innerHTML = '';
}
if ( options.callback ) {
this.callback = options.callback;
}
// Render our *root fragment*
this.rendered = new DomFragment({
descriptor: this.template,
root: this,
owner: this, // saves doing `if ( this.parent ) { /*...*/ }` later on
parentNode: el
});
el.appendChild( this.rendered.docFrag );
};
(function ( proto ) {
var set, attemptKeypathResolution;
proto.set = function ( keypath, value ) {
var notificationQueue, upstreamQueue, k, normalised, keys, previous;
if ( !this.setting ) {
this.setting = true; // short-circuit any potential infinite loops
this.fire( 'set', keypath, value );
this.setting = false;
}
upstreamQueue = [];
notificationQueue = [];
// setting multiple values in one go
if ( isObject( keypath ) ) {
for ( k in keypath ) {
if ( keypath.hasOwnProperty( k ) ) {
keys = splitKeypath( k );
normalised = keys.join( '.' );
value = keypath[k];
set( this, normalised, keys, value, notificationQueue, upstreamQueue );
}
}
}
// setting a single value
else {
keys = splitKeypath( keypath );
normalised = keys.join( '.' );
set( this, normalised, keys, value, notificationQueue, upstreamQueue );
}
// if anything has changed, notify dependants and attempt to resolve
// any unresolved keypaths
if ( notificationQueue.length ) {
while ( notificationQueue.length ) {
notifyDependants( this, notificationQueue.pop() );
}
attemptKeypathResolution( this );
}
// notify direct descendants of upstream keypaths
while ( upstreamQueue.length ) {
notifyDependants( this, upstreamQueue.pop(), true );
}
// Attributes don't reflect changes automatically if there is a possibility
// that they will need to change again before the .set() cycle is complete
// - they defer their updates until all values have been set
while ( this._def.length ) {
// Update the attribute, then deflag it
this._def.pop().update().deferred = false;
}
};
set = function ( root, keypath, keys, value, queue, upstreamQueue ) {
var previous, key, obj, keysClone;
keysClone = keys.slice();
previous = root.get( keypath );
// update the model, if necessary
if ( previous !== value ) {
// update data
obj = root.data;
while ( keys.length > 1 ) {
key = keys.shift();
// If this branch doesn't exist yet, create a new one - if the next
// key matches /^\s*[0-9]+\s*$/, assume we want an array branch rather
// than an object
if ( !obj[ key ] ) {
obj[ key ] = ( /^\s*[0-9]+\s*$/.test( keys[0] ) ? [] : {} );
}
obj = obj[ key ];
}
key = keys[0];
obj[ key ] = value;
}
else {
// if value is a primitive, we don't need to do anything else
if ( typeof value !== 'object' ) {
return;
}
}
// Clear cache
clearCache( root, keypath );
// add this keypath to the notification queue
queue[ queue.length ] = keypath;
// add upstream keypaths to the upstream notification queue
while ( keysClone.length ) {
keysClone.pop();
keypath = keysClone.join( '.' );
if ( upstreamQueue.indexOf( keypath ) === -1 ) {
upstreamQueue[ upstreamQueue.length ] = keypath;
}
}
};
attemptKeypathResolution = function ( root ) {
var i, unresolved, keypath;
// See if we can resolve any of the unresolved keypaths (if such there be)
i = root._pendingResolution.length;
while ( i-- ) { // Work backwards, so we don't go in circles!
unresolved = root._pendingResolution.splice( i, 1 )[0];
if ( keypath = resolveRef( root, unresolved.ref, unresolved.contextStack ) ) {
// If we've resolved the keypath, we can initialise this item
unresolved.resolve( keypath );
} else {
// If we can't resolve the reference, add to the back of
// the queue (this is why we're working backwards)
root._pendingResolution[ root._pendingResolution.length ] = unresolved;
}
}
};
}( proto ));
// Teardown. This goes through the root fragment and all its children, removing observers
// and generally cleaning up after itself
proto.teardown = function () {
var keypath;
this.rendered.teardown();
// Clear cache - this has the side-effect of unregistering keypaths from modified arrays.
// Once with keypaths that have dependents...
for ( keypath in this._depsMap ) {
if ( this._depsMap.hasOwnProperty( keypath ) ) {
clearCache( this, keypath );
}
}
// Then a second time to mop up the rest
for ( keypath in this._cache ) {
if ( this._cache.hasOwnProperty( keypath ) ) {
clearCache( this, keypath );
}
}
// Teardown any bindings
while ( this._bound.length ) {
this.unbind( this._bound.pop() );
}
};
proto.unbind = function ( adaptor ) {
var bound = this._bound, index;
index = bound.indexOf( adaptor );
if ( index !== -1 ) {
bound.splice( index, 1 );
adaptor.teardown( this );
}
};
proto.update = function ( keypath ) {
clearCache( this, keypath || '' );
notifyDependants( this, keypath || '' );
this.fire( 'update:' + keypath );
this.fire( 'update', keypath );
return this;
};
adaptors.backbone = function ( model, path ) {
var settingModel, settingView, setModel, setView, pathMatcher, pathLength, prefix;
if ( path ) {
path += '.';
pathMatcher = new RegExp( '^' + path.replace( /\./g, '\\.' ) );
pathLength = path.length;
}
return {
init: function ( view ) {
// if no path specified...
if ( !path ) {
setView = function ( model ) {
if ( !settingModel ) {
settingView = true;
view.set( model.changed );
settingView = false;
}
};
setModel = function ( keypath, value ) {
if ( !settingView ) {
settingModel = true;
model.set( keypath, value );
settingModel = false;
}
};
}
else {
prefix = function ( attrs ) {
var attr, result;
result = {};
for ( attr in attrs ) {
if ( attrs.hasOwnProperty( attr ) ) {
result[ path + attr ] = attrs[ attr ];
}
}
return result;
};
setView = function ( model ) {
if ( !settingModel ) {
settingView = true;
view.set( prefix( model.changed ) );
settingView = false;
}
};
setModel = function ( keypath, value ) {
if ( !settingView ) {
if ( pathMatcher.test( keypath ) ) {
settingModel = true;
model.set( keypath.substring( pathLength ), value );
settingModel = false;
}
}
};
}
model.on( 'change', setView );
view.on( 'set', setModel );
// initialise
view.set( path ? prefix( model.attributes ) : model.attributes );
},
teardown: function ( view ) {
model.off( 'change', setView );
view.off( 'set', setModel );
}
};
};
adaptors.statesman = function ( model, path ) {
var settingModel, settingView, setModel, setView;
path = ( path ? path + '.' : '' );
return {
init: function ( view ) {
setView = function ( keypath, value ) {
if ( !settingModel ) {
settingView = true;
view.set( keypath, value );
settingView = false;
}
};
setModel = function ( keypath, value ) {
if ( !settingView ) {
settingModel = true;
model.set( keypath, value );
settingModel = false;
}
};
model.on( 'set', setView );
view.on( 'set', setModel );
// initialise
view.set( model.get() );
},
teardown: function ( view ) {
model.off( 'change', setView );
view.off( 'set', setModel );
}
};
};
// These are a subset of the easing equations found at
// https://raw.github.com/danro/easing-js - license info
// follows:
// --------------------------------------------------
// easing.js v0.5.4
// Generic set of easing functions with AMD support
// https://github.com/danro/easing-js
// This code may be freely distributed under the MIT license
// http://danro.mit-license.org/
// --------------------------------------------------
// All functions adapted from Thomas Fuchs & Jeremy Kahn
// Easing Equations (c) 2003 Robert Penner, BSD license
// https://raw.github.com/danro/easing-js/master/LICENSE
// --------------------------------------------------
// In that library, the functions named easeIn, easeOut, and
// easeInOut below are named easeInCubic, easeOutCubic, and
// (you guessed it) easeInOutCubic.
//
// You can add additional easing functions to this list, and they
// will be globally available.
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 ) );
}
};
eventDefinitions.tap = function ( el, fire ) {
var mousedown, touchstart, distanceThreshold, timeThreshold;
distanceThreshold = 5; // maximum pixels pointer can move before cancel
timeThreshold = 400; // maximum milliseconds between down and up before cancel
mousedown = function ( event ) {
var x, y, currentTarget, up, move, cancel;
x = event.clientX;
y = event.clientY;
currentTarget = this;
up = function ( event ) {
fire.call( currentTarget, event );
cancel();
};
move = function ( event ) {
if ( ( Math.abs( event.clientX - x ) >= distanceThreshold ) || ( Math.abs( event.clientY - y ) >= distanceThreshold ) ) {
cancel();
}
};
cancel = function () {
window.removeEventListener( 'mousemove', move );
window.removeEventListener( 'mouseup', up );
};
window.addEventListener( 'mousemove', move );
window.addEventListener( 'mouseup', up );
setTimeout( cancel, timeThreshold );
};
el.addEventListener( 'mousedown', mousedown );
touchstart = function ( event ) {
var x, y, touch, finger, move, up, cancel;
if ( event.touches.length !== 1 ) {
return;
}
touch = event.touches[0];
x = touch.clientX;
y = touch.clientY;
finger = touch.identifier;
up = function ( event ) {
var touch;
touch = event.changedTouches[0];
if ( touch.identifier !== finger ) {
cancel();
}
fire.call( touch.target, event );
};
move = function ( event ) {
var touch;
if ( event.touches.length !== 1 || event.touches[0].identifier !== finger ) {
cancel();
}
touch = event.touches[0];
if ( ( Math.abs( touch.clientX - x ) >= distanceThreshold ) || ( Math.abs( touch.clientY - y ) >= distanceThreshold ) ) {
cancel();
}
};
cancel = function () {
window.removeEventListener( 'touchmove', move );
window.removeEventListener( 'touchend', up );
window.removeEventListener( 'touchcancel', cancel );
};
window.addEventListener( 'touchmove', move );
window.addEventListener( 'touchend', up );
window.addEventListener( 'touchcancel', cancel );
setTimeout( cancel, timeThreshold );
};
return {
teardown: function () {
el.removeEventListener( 'mousedown', mousedown );
el.removeEventListener( 'touchstart', touchstart );
}
};
};
var fillGaps = function ( target, source ) {
var key;
for ( key in source ) {
if ( source.hasOwnProperty( key ) && !target.hasOwnProperty( key ) ) {
target[ key ] = source[ key ];
}
}
};
extend = function ( childProps ) {
var Parent, Child, key, inheritedOptions, blacklist, template, partials, partial;
Parent = this;
inheritedOptions = [ 'el', 'preserveWhitespace', 'append', 'twoway', 'modifyArrays' ];
blacklist = inheritedOptions.concat( 'data', 'template' );
// Parse template
if ( childProps.template ) {
if ( typeof childProps.template === 'string' ) {
if ( !Ractive.parse ) {
throw new Error( missingParser );
}
template = Ractive.parse( childProps.template );
} else {
template = childProps.template;
}
}
// Parse partials, if necessary
if ( childProps.partials ) {
partials = {};
for ( key in childProps.partials ) {
if ( childProps.partials.hasOwnProperty( key ) ) {
if ( typeof childProps.partials[ key ] === 'string' ) {
if ( !Ractive.parse ) {
throw new Error( missingParser );
}
partial = Ractive.parse( childProps.partials[ key ], childProps );
} else {
partial = childProps.partials[ key ];
}
partials[ key ] = partial;
}
}
}
Child = function ( options ) {
var key, i, optionName;
// Add template to options, if necessary
if ( !options.template && template ) {
options.template = template;
}
// Extend subclass data with instance data
if ( !options.data ) {
options.data = {};
}
fillGaps( options.data, childProps.data );
// Add in preparsed partials
if ( partials ) {
if ( !options.partials ) {
options.partials = {};
}
fillGaps( options.partials, partials );
}
i = inheritedOptions.length;
while ( i-- ) {
optionName = inheritedOptions[i];
if ( !options.hasOwnProperty( optionName ) && childProps.hasOwnProperty( optionName ) ) {
options[ optionName ] = childProps[ optionName ];
}
}
Ractive.call( this, options );
if ( this.init ) {
this.init.call( this, options );
}
};
// extend child with parent methods
for ( key in Parent.prototype ) {
if ( Parent.prototype.hasOwnProperty( key ) ) {
Child.prototype[ key ] = Parent.prototype[ key ];
}
}
// Extend child with specified methods, as long as they don't override Ractive.prototype methods.
// Blacklisted properties don't extend the child, as they are part of the initialisation options
for ( key in childProps ) {
if ( childProps.hasOwnProperty( key ) && blacklist.indexOf( key ) === -1 ) {
if ( Ractive.prototype.hasOwnProperty( key ) ) {
throw new Error( 'Cannot override "' + key + '" method or property of Ractive prototype' );
}
Child.prototype[ key ] = childProps[ key ];
}
}
Child.extend = Parent.extend;
return Child;
};
interpolate = function ( from, to ) {
if ( isNumeric( from ) && isNumeric( to ) ) {
return Ractive.interpolators.number( +from, +to );
}
if ( isArray( from ) && isArray( to ) ) {
return Ractive.interpolators.array( from, to );
}
if ( isObject( from ) && isObject( to ) ) {
return Ractive.interpolators.object( from, to );
}
return function () { return to; };
};
interpolators = {
number: function ( from, to ) {
var 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;
intermediate = [];
interpolators = [];
i = len = Math.min( from.length, to.length );
while ( i-- ) {
interpolators[i] = Ractive.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;
intermediate = {};
interpolators = {};
for ( prop in from ) {
if ( from.hasOwnProperty( prop ) ) {
if ( to.hasOwnProperty( prop ) ) {
properties[ properties.length ] = prop;
interpolators[ prop ] = Ractive.interpolate( from[ prop ], to[ prop ] );
}
else {
intermediate[ prop ] = from[ prop ];
}
}
}
for ( prop in to ) {
if ( to.hasOwnProperty( prop ) && !from.hasOwnProperty( 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;
};
}
};
var defaultOptions = {
preserveWhitespace: false,
append: false,
twoway: true,
modifyArrays: true,
data: {}
};
Ractive = function ( options ) {
var key, partial, i, template, templateEl;
// Options
// -------
for ( key in defaultOptions ) {
if ( defaultOptions.hasOwnProperty( key ) && !options.hasOwnProperty( key ) ) {
options[ key ] = defaultOptions[ key ];
}
}
// Initialization
// --------------
this.el = getEl( options.el );
// add data
this.data = options.data || {};
// Set up event bus
this._subs = {};
// Set up cache
this._cache = {};
// Set up dependants
this._deps = {};
this._depsMap = {};
// Node registry
this.nodes = {};
// Set up observers
this._pendingResolution = [];
// Create an array for deferred attributes
this._def = [];
// Cache proxy event handlers - allows efficient reuse
this._proxies = {};
// Keep a list of used expressions, so we don't duplicate them
this._expressions = [];
// Set up bindings
this._bound = [];
if ( options.bindings ) {
if ( isArray( options.bindings ) ) {
for ( i=0; i<options.bindings.length; i+=1 ) {
this.bind( options.bindings[i] );
}
} else {
this.bind( options.bindings );
}
}
// If we were given unparsed partials, parse them
if ( options.partials ) {
this.partials = {};
for ( key in options.partials ) {
if ( options.partials.hasOwnProperty( key ) ) {
partial = options.partials[ key ];
if ( typeof partial === 'string' ) {
if ( !Ractive.parse ) {
throw new Error( missingParser );
}
partial = Ractive.parse( partial, options );
}
// If the partial was an array with a single string member, that means
// we can use innerHTML - we just need to unpack it
if ( partial.length === 1 && typeof partial[0] === 'string' ) {
partial = partial[0];
}
this.partials[ key ] = partial;
}
}
}
// Compile template, if it hasn't been parsed already
template = options.template;
if ( typeof template === 'string' ) {
if ( !Ractive.parse ) {
throw new Error( missingParser );
}
if ( template.charAt( 0 ) === '#' ) {
// assume this is an ID of a <script type='text/template'> tag
templateEl = document.getElementById( template.substring( 1 ) );
if ( templateEl ) {
this.template = Ractive.parse( templateEl.innerHTML, options );
}
else {
throw new Error( 'Could not find template element (' + template + ')' );
}
}
else {
template = Ractive.parse( template, options );
}
}
// If the template was an array with a single string member, that means
// we can use innerHTML - we just need to unpack it
if ( template && ( template.length === 1 ) && ( typeof template[0] === 'string' ) ) {
this.template = template[0];
} else {
this.template = template;
}
// If passed an element, render immediately
if ( this.el ) {
this.render({ el: this.el, append: options.append });
}
};
Animation = function ( options ) {
var key;
this.startTime = Date.now();
// from and to
for ( key in options ) {
if ( options.hasOwnProperty( key ) ) {
this[ key ] = options[ key ];
}
}
this.interpolator = Ractive.interpolate( this.from, this.to );
this.running = true;
};
Animation.prototype = {
tick: function () {
var elapsed, t, value, timeNow;
if ( this.running ) {
timeNow = Date.now();
elapsed = timeNow - this.startTime;
if ( elapsed >= this.duration ) {
this.root.set( this.keys, this.to );
if ( this.step ) {
this.step( 1, this.to );
}
if ( this.complete ) {
this.complete( 1, this.to );
}
this.running = false;
return false;
}
t = this.easing ? this.easing ( elapsed / this.duration ) : ( elapsed / this.duration );
value = this.interpolator( t );
this.root.set( this.keys, value );
if ( this.step ) {
this.step( t, value );
}
return true;
}
return false;
},
stop: function () {
this.running = false;
}
};
animationCollection = {
animations: [],
tick: function () {
var i, animation;
for ( i=0; i<this.animations.length; i+=1 ) {
animation = this.animations[i];
if ( !animation.tick() ) {
// animation is complete, remove it from the stack, and decrement i so we don't miss one
this.animations.splice( i--, 1 );
}
}
if ( this.animations.length ) {
global.requestAnimationFrame( this.boundTick );
} else {
this.running = false;
}
},
// bind method to animationCollection
boundTick: function () {
animationCollection.tick();
},
push: function ( animation ) {
this.animations[ this.animations.length ] = animation;
if ( !this.running ) {
this.running = true;
this.tick();
}
}
};
// https://gist.github.com/paulirish/1579671
(function( vendors, lastTime, global ) {
var x;
for ( x = 0; x < vendors.length && !global.requestAnimationFrame; ++x ) {
global.requestAnimationFrame = global[vendors[x]+'RequestAnimationFrame'];
global.cancelAnimationFrame = global[vendors[x]+'CancelAnimationFrame'] || global[vendors[x]+'CancelRequestAnimationFrame'];
}
if ( !global.requestAnimationFrame ) {
global.requestAnimationFrame = function(callback) {
var currTime, timeToCall, id;
currTime = Date.now();
timeToCall = Math.max( 0, 16 - (currTime - lastTime ) );
id = global.setTimeout( function() { callback(currTime + timeToCall); }, timeToCall );
lastTime = currTime + timeToCall;
return id;
};
}
if ( !global.cancelAnimationFrame ) {
global.cancelAnimationFrame = function( id ) {
global.clearTimeout( id );
};
}
}( ['ms', 'moz', 'webkit', 'o'], 0, global ));
(function () {
var define, notifyDependents, wrapArray, unwrapArray, WrappedArrayProto, testObj, mutatorMethods;
// just in case we don't have Object.defineProperty, we can use this - it doesn't
// allow us to set non-enumerable properties, but if you're doing for ... in loops on
// an array then you deserve what's coming anyway
if ( !Object.defineProperty ) {
define = function ( obj, prop, desc ) {
obj[ prop ] = desc.value;
};
} else {
define = Object.defineProperty;
}
// Register a keypath to this array. When any of this array's mutator methods are called,
// it will `set` that keypath on the given Ractive instance
registerKeypathToArray = function ( array, keypath, root ) {
var roots, keypathsByIndex, rootIndex, keypaths;
// If this array hasn't been wrapped, we need to wrap it
if ( !array._ractive ) {
define( array, '_ractive', {
value: {
roots: [ root ], // there may be more than one Ractive instance depending on this
keypathsByIndex: [ [ keypath ] ]
},
configurable: true
});
wrapArray( array );
}
else {
roots = array._ractive.roots;
keypathsByIndex = array._ractive.keypathsByIndex;
// Does this Ractive instance currently depend on this array?
rootIndex = roots.indexOf( root );
// If not, associate them
if ( rootIndex === -1 ) {
rootIndex = roots.length;
roots[ rootIndex ] = root;
}
// Find keypaths that reference this array, on this Ractive instance
if ( !keypathsByIndex[ rootIndex ] ) {
keypathsByIndex[ rootIndex ] = [];
}
keypaths = keypathsByIndex[ rootIndex ];
// If the current keypath isn't among them, add it
if ( keypaths.indexOf( keypath ) === -1 ) {
keypaths[ keypaths.length ] = keypath;
}
}
};
// Unregister keypath from array
unregisterKeypathFromArray = function ( array, keypath, root ) {
var roots, keypathsByIndex, rootIndex, keypaths, keypathIndex;
if ( !array._ractive ) {
throw new Error( 'Attempted to remove keypath from non-wrapped array. This error is unexpected - please send a bug report to @rich_harris' );
}
roots = array._ractive.roots;
rootIndex = roots.indexOf( root );
if ( rootIndex === -1 ) {
throw new Error( 'Ractive instance was not listed as a dependent of this array. This error is unexpected - please send a bug report to @rich_harris' );
}
keypathsByIndex = array._ractive.keypathsByIndex;
keypaths = keypathsByIndex[ rootIndex ];
keypathIndex = keypaths.indexOf( keypath );
if ( keypathIndex === -1 ) {
throw new Error( 'Attempted to unlink non-linked keypath from array. This error is unexpected - please send a bug report to @rich_harris' );
}
keypaths.splice( keypathIndex, 1 );
if ( !keypaths.length ) {
roots.splice( rootIndex, 1 );
}
if ( !roots.length ) {
unwrapArray( array ); // It's good to clean up after ourselves
}
};
// Call `set` on each dependent Ractive instance, for each dependent keypath
notifyDependents = function ( array ) {
var roots, keypathsByIndex, root, keypaths, i, j;
roots = array._ractive.roots;
keypathsByIndex = array._ractive.keypathsByIndex;
i = roots.length;
while ( i-- ) {
root = roots[i];
keypaths = keypathsByIndex[i];
j = keypaths.length;
while ( j-- ) {
root.set( keypaths[j], array );
}
}
};
WrappedArrayProto = [];
mutatorMethods = [ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift' ];
mutatorMethods.forEach( function ( methodName ) {
var method = function () {
var result = Array.prototype[ methodName ].apply( this, arguments );
this._ractive.setting = true;
notifyDependents( this );
this._ractive.setting = false;
return result;
};
define( WrappedArrayProto, methodName, {
value: method
});
});
// can we use prototype chain injection?
// http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection
testObj = {};
if ( testObj.__proto__ ) {
// yes, we can
wrapArray = function ( array ) {
array.__proto__ = WrappedArrayProto;
};
unwrapArray = function ( array ) {
delete array._ractive;
array.__proto__ = Array.prototype;
};
}
else {
// no, we can't
wrapArray = function ( array ) {
var i, methodName;
i = mutatorMethods.length;
while ( i-- ) {
methodName = mutatorMethods[i];
define( array, methodName, {
value: WrappedArrayProto[ methodName ]
});
}
};
unwrapArray = function ( array ) {
var i;
i = mutatorMethods.length;
while ( i-- ) {
delete array[ mutatorMethods[i] ];
}
delete array._ractive;
};
}
}());
(function ( evaluators, functionCache ) {
var Reference, getFunctionFromString;
getFunctionFromString = function ( functionString, i ) {
var fn, args;
if ( functionCache[ functionString ] ) {
return functionCache[ functionString ];
}
args = [];
while ( i-- ) {
args[i] = '_' + i;
}
fn = new Function( args.join( ',' ), 'return(' + functionString + ')' );
functionCache[ functionString ] = fn;
return fn;
};
Evaluator = function ( root, mustache, contextStack, indexRefs, descriptor ) {
var i;
this.root = root;
this.mustache = mustache;
this.priority = mustache.priority;
this.str = descriptor.s;
this.keypaths = [];
this.override = []; // need to override index refs when creating a keypath
this.values = [];
if ( !descriptor.r ) {
// no references - we can init immediately
this.init();
}
else {
this.resolvers = [];
this.unresolved = this.numRefs = i = descriptor.r.length;
while ( i-- ) {
// index ref?
if ( indexRefs && indexRefs.hasOwnProperty( descriptor.r[i] ) ) {
this.values[i] = this.override[i] = indexRefs[ descriptor.r[i] ];
this.unresolved -= 1; // because we don't need to resolve the reference
}
else {
this.resolvers[ this.resolvers.length ] = new Reference( root, descriptor.r[i], contextStack, i, this );
}
}
// if this only has one reference (and therefore only one dependency) it can
// update its mustache whenever that dependency changes. Otherwise, it should
// wait until all the information is in before re-evaluating (same principle
// as element attributes)
if ( this.resolvers.length <= 1 ) {
this.selfUpdating = true;
}
// if we have no unresolved references, but we haven't initialised (because
// one or more of the references were index references), initialise now
if ( !this.unresolved && !this.resolved ) {
this.init();
}
}
};
Evaluator.prototype = {
init: function () {
var self = this, functionString;
// we're ready!
this.resolved = true;
this.keypath = '(' + this.str.replace( /❖([0-9]+)/g, function ( match, $1 ) {
if ( self.override.hasOwnProperty( $1 ) ) {
return self.override[ $1 ];
}
return self.keypaths[ $1 ];
}) + ')';
// is this the first of its kind?
if ( this.root._expressions.indexOf( this.keypath ) === -1 ) {
// yes
functionString = this.str.replace( /❖([0-9]+)/g, function ( match, $1 ) {
return '_' + $1;
});
this.fn = getFunctionFromString( functionString, this.numRefs || 0 );
this.update();
this.root._expressions.push( this.keypath );
} else {
// no. tear it down! our mustache will be taken care of by the other expression
// with the same virtual keypath
this.teardown();
}
this.mustache.resolve( this.keypath );
},
teardown: function () {
if ( this.resolvers ) {
while ( this.resolvers.length ) {
this.resolvers.pop().teardown();
}
}
},
resolve: function ( ref, argNum, keypath ) {
var self = this;
this.keypaths[ argNum ] = keypath;
this.unresolved -= 1;
if ( !this.unresolved ) {
this.init();
}
},
bubble: function () {
// If we only have one reference, we can update immediately...
if ( this.selfUpdating ) {
this.update();
}
// ...otherwise we want to register it as a deferred item, to be
// updated once all the information is in, to prevent unnecessary
// cascading. Only if we're already resovled, obviously
else if ( !this.deferred && this.resolved ) {
this.root._def[ this.root._def.length ] = this;
this.deferred = true;
}
},
update: function () {
var value;
if ( !this.resolved ) {
return this;
}
try {
value = this.getter();
} catch ( err ) {
if ( this.root.debug ) {
throw err;
} else {
value = undefined;
}
}
if ( !isEqual( value, this._lastValue ) ) {
clearCache( this.root, this.keypath );
this.root._cache[ this.keypath ] = value;
notifyDependants( this.root, this.keypath );
this._lastValue = value;
}
return this;
},
getter: function () {
return this.fn.apply( null, this.values );
}
};
Reference = function ( root, ref, contextStack, argNum, evaluator ) {
var keypath;
this.ref = ref;
this.root = root;
this.evaluator = evaluator;
this.priority = evaluator.priority;
this.argNum = argNum;
keypath = resolveRef( root, ref, contextStack );
if ( keypath ) {
this.resolve( keypath );
} else {
this.contextStack = contextStack;
root._pendingResolution[ root._pendingResolution.length ] = this;
}
};
Reference.prototype = {
teardown: function () {
teardown( this );
},
resolve: function ( keypath ) {
this.keypath = keypath;
registerDependant( this.root, keypath, this, this.priority );
this.update();
this.evaluator.resolve( this.ref, this.argNum, keypath );
},
update: function () {
var value = this.root.get( this.keypath );
if ( !isEqual( value, this._lastValue ) ) {
this.evaluator.values[ this.argNum ] = value;
this.evaluator.bubble();
this._lastValue = value;
}
}
};
}({}, {}));
initFragment = function ( fragment, options ) {
var numItems, i, itemOptions, parentRefs, ref;
// The item that owns this fragment - an element, section, partial, or attribute
fragment.owner = options.owner;
// 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 ( fragment.owner.type === SECTION ) {
fragment.index = options.index;
}
// index references (the 'i' in {{#section:i}}<!-- -->{{/section}}) need to cascade
// down the tree
if ( fragment.owner.parentFragment ) {
parentRefs = fragment.owner.parentFragment.indexRefs;
if ( parentRefs ) {
fragment.indexRefs = {};
for ( ref in parentRefs ) {
if ( parentRefs.hasOwnProperty( ref ) ) {
fragment.indexRefs[ ref ] = parentRefs[ ref ];
}
}
}
}
if ( options.indexRef ) {
if ( !fragment.indexRefs ) {
fragment.indexRefs = {};
}
fragment.indexRefs[ options.indexRef ] = options.index;
}
// Time to create this fragment's child items;
fragment.items = [];
itemOptions = {
root: options.root,
parentFragment: fragment,
parentNode: options.parentNode,
contextStack: options.contextStack
};
numItems = ( options.descriptor ? options.descriptor.length : 0 );
for ( i=0; i<numItems; i+=1 ) {
itemOptions.descriptor = options.descriptor[i];
itemOptions.index = i;
fragment.items[ fragment.items.length ] = fragment.createItem( itemOptions );
}
};
initMustache = function ( mustache, options ) {
var keypath, index;
mustache.root = options.root;
mustache.descriptor = options.descriptor;
mustache.parentFragment = options.parentFragment;
mustache.contextStack = options.contextStack || [];
mustache.index = options.index || 0;
mustache.priority = options.descriptor.p || 0;
// DOM only
if ( options.parentNode || options.anchor ) {
mustache.parentNode = options.parentNode;
mustache.anchor = options.anchor;
}
mustache.type = options.descriptor.t;
// if this is a simple mustache, with a reference, we just need to resolve
// the reference to a keypath
if ( options.descriptor.r ) {
if ( mustache.parentFragment.indexRefs && mustache.parentFragment.indexRefs.hasOwnProperty( options.descriptor.r ) ) {
index = mustache.parentFragment.indexRefs[ options.descriptor.r ];
mustache.render( index );
}
else {
keypath = resolveRef( mustache.root, options.descriptor.r, mustache.contextStack );
if ( keypath ) {
mustache.resolve( keypath );
} else {
mustache.ref = options.descriptor.r;
mustache.root._pendingResolution[ mustache.roo