vendor/assets/javascripts/ractive.js in ractive-rails-0.0.1 vs vendor/assets/javascripts/ractive.js in ractive-rails-0.0.2

- old
+ new

@@ -1,8034 +1,14347 @@ -/*! Ractive - v0.3.3 - 2013-07-21 -* Next-generation DOM manipulation +/* + ractive.js v0.6.1 + 2014-10-25 - commit 3a576eb3 -* http://rich-harris.github.com/Ractive/ -* Copyright (c) 2013 Rich Harris; Licensed MIT */ + http://ractivejs.org + http://twitter.com/RactiveJS -/*jslint eqeq: true, plusplus: true */ -/*global document, HTMLElement */ + Released under the MIT License. +*/ +( function( global ) { -(function ( global ) { + 'use strict'; -'use strict'; + var noConflict = global.Ractive; -var Ractive, + /* config/defaults/options.js */ + var options = function() { -doc = global.document || null, + var defaultOptions = { + // render placement: + el: void 0, + append: false, + // template: + template: { + v: 1, + t: [] + }, + yield: null, + // parse: + preserveWhitespace: false, + sanitize: false, + stripComments: true, + // 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; + }(); -// Ractive prototype -proto = {}, - -// properties of the public Ractive object -adaptors = {}, -eventDefinitions = {}, -easing, -extend, -parse, -interpolate, -interpolators, -transitions = {}, - - -// internal utils - instance-specific -teardown, -clearCache, -registerDependant, -unregisterDependant, -notifyDependants, -notifyMultipleDependants, -notifyDependantsByPriority, -registerIndexRef, -unregisterIndexRef, -resolveRef, -processDeferredUpdates, - - -// internal utils -splitKeypath, -toString, -isArray, -isObject, -isNumeric, -isEqual, -getEl, -insertHtml, -reassignFragments, -executeTransition, -getPartialDescriptor, -makeTransitionManager, -requestAnimationFrame, -defineProperty, -defineProperties, -create, -createFromNull, -hasOwn = {}.hasOwnProperty, -noop = function () {}, - - -// internally used caches -keypathCache = {}, - - -// internally used constructors -DomFragment, -DomElement, -DomAttribute, -DomPartial, -DomInterpolator, -DomTriple, -DomSection, -DomText, - -StringFragment, -StringPartial, -StringInterpolator, -StringSection, -StringText, - -ExpressionResolver, -Evaluator, -Animation, - - -// internally used regexes -leadingWhitespace = /^\s+/, -trailingWhitespace = /\s+$/, - - -// other bits and pieces -render, - -initMustache, -updateMustache, -resolveMustache, -evaluateMustache, - -initFragment, -updateSection, - -animationCollection, - - -// array modification -registerKeypathToArray, -unregisterKeypathFromArray, - - -// parser and tokenizer -getFragmentStubFromTokens, -getToken, -tokenize, -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, - -UNSET = { unset: true }, - - -// 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/' -}, - - -// current version -VERSION = '0.3.3'; - - - -// we're creating a defineProperty function here - we don't want to add -// this to _legacy.js since it's not a polyfill. It won't allow us to set -// non-enumerable properties. That shouldn't be a problem, unless you're -// using for...in on a (modified) array, in which case you deserve what's -// coming anyway -try { - Object.defineProperty({}, 'test', { value: 0 }); - Object.defineProperties({}, { test: { value: 0 } }); - - defineProperty = Object.defineProperty; - defineProperties = Object.defineProperties; -} 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; - }; - - defineProperties = function ( obj, props ) { - var prop; - - for ( prop in props ) { - if ( props.hasOwnProperty( prop ) ) { - defineProperty( obj, prop, props[ prop ] ); + /* 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 = []; -try { - Object.create( null ); + /* utils/hasOwnProperty.js */ + var hasOwn = Object.prototype.hasOwnProperty; - create = Object.create; + /* utils/isArray.js */ + var isArray = function() { - createFromNull = function () { - return Object.create( null ); - }; -} catch ( err ) { - // sigh - create = (function () { - var F = 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]'; + }; + }(); - return function ( proto, props ) { - var obj; + /* utils/isObject.js */ + var isObject = function() { - F.prototype = proto; - obj = new F(); - - if ( props ) { - Object.defineProperties( obj, props ); - } - - return obj; + var toString = Object.prototype.toString; + return function( thing ) { + return thing && toString.call( thing ) === '[object Object]'; }; - }()); + }(); - createFromNull = function () { - return {}; // hope you're not modifying the Object prototype + /* 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; + }; + } + }; + return interpolators; + }( circular, hasOwn, isArray, isObject, isNumeric ); -var hyphenate = function ( str ) { - return str.replace( /[A-Z]/g, function ( match ) { - return '-' + match.toLowerCase(); - }); -}; + /* config/svg.js */ + var svg = function() { -// determine some facts about our environment -var cssTransitionsEnabled, transition, transitionend; - -(function () { - - var testDiv; - - if ( !doc ) { - return; - } - - testDiv = doc.createElement( 'div' ); - - if ( testDiv.style.transition !== undefined ) { - transition = 'transition'; - transitionend = 'transitionend'; - cssTransitionsEnabled = true; - } else if ( testDiv.style.webkitTransition !== undefined ) { - transition = 'webkitTransition'; - transitionend = 'webkitTransitionEnd'; - cssTransitionsEnabled = true; - } else { - cssTransitionsEnabled = false; - } - -}()); -executeTransition = function ( descriptor, root, owner, contextStack, isIntro ) { - var transitionName, transitionParams, fragment, transitionManager, transition; - - if ( !root.transitionsEnabled ) { - return; - } - - if ( typeof descriptor === 'string' ) { - transitionName = descriptor; - } else { - transitionName = descriptor.n; - - if ( descriptor.a ) { - transitionParams = descriptor.a; - } else if ( descriptor.d ) { - fragment = new TextFragment({ - descriptor: descriptor.d, - root: root, - owner: owner, - contextStack: parentFragment.contextStack - }); - - transitionParams = fragment.toJson(); - fragment.teardown(); + 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; + }(); - transition = root.transitions[ transitionName ] || Ractive.transitions[ transitionName ]; + /* utils/warn.js */ + var warn = function() { - if ( transition ) { - transitionManager = root._transitionManager; - - transitionManager.push( owner.node ); - transition.call( root, owner.node, function () { - transitionManager.pop( owner.node ); - }, transitionParams, transitionManager.info, isIntro ); - } -}; -insertHtml = function ( html, docFrag ) { - var div, nodes = []; - - div = doc.createElement( 'div' ); - div.innerHTML = html; - - while ( div.firstChild ) { - nodes[ nodes.length ] = div.firstChild; - docFrag.appendChild( div.firstChild ); - } - - return nodes; -}; -(function () { - - var reassignFragment, reassignElement, reassignMustache; - - reassignFragments = function ( root, section, start, end, by ) { - var fragmentsToReassign, i, fragment, indexRef, oldIndex, newIndex, oldKeypath, newKeypath; - - indexRef = section.descriptor.i; - - for ( i=start; i<end; i+=1 ) { - fragment = section.fragments[i]; - - oldIndex = i - by; - newIndex = i; - - oldKeypath = section.keypath + '.' + ( i - by ); - newKeypath = section.keypath + '.' + i; - - // change the fragment index - fragment.index += by; - - reassignFragment( fragment, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); + /* 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( '%cRactive.js: %c' + message, 'color: rgb(114, 157, 52);', 'color: rgb(85, 85, 85);' ); + }; + } else { + warn = function() {}; } + return warn; + }(); - processDeferredUpdates( root ); + /* 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', + defaultElSpecified: 'The <{name}/> component has a default `el` property; it has been disregarded', + noElementProxyEventWildcards: 'Only component proxy-events may contain "*" wildcards, <{element} on-{event}/> is not valid.', + methodDeprecated: 'The method "{deprecated}" has been deprecated in favor of "{replacement}" and will likely be removed in a future release. See http://docs.ractivejs.org/latest/migrating for more information.' }; - reassignFragment = function ( fragment, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ) { - var i, j, item, context; + /* utils/log.js */ + var log = function( consolewarn, errors ) { - if ( fragment.indexRefs && fragment.indexRefs[ indexRef ] !== undefined ) { - fragment.indexRefs[ indexRef ] = newIndex; - } - - // fix context stack - i = fragment.contextStack.length; - while ( i-- ) { - context = fragment.contextStack[i]; - if ( context.substr( 0, oldKeypath.length ) === oldKeypath ) { - fragment.contextStack[i] = context.replace( oldKeypath, newKeypath ); + var log = { + warn: function( options, passthru ) { + if ( !options.debug && !passthru ) { + return; + } + this.warnAlways( options ); + }, + warnAlways: function( options ) { + this.logger( getMessage( options ), options.allowDuplicates ); + }, + error: function( options ) { + this.errorOnly( options ); + if ( !options.debug ) { + this.warn( options, true ); + } + }, + errorOnly: function( options ) { + if ( options.debug ) { + this.critical( options ); + } + }, + critical: function( options ) { + var err = options.err || new Error( getMessage( options ) ); + this.thrower( err ); + }, + logger: consolewarn, + thrower: function( err ) { + throw err; } + }; + + function getMessage( options ) { + var message = errors[ options.message ] || options.message || ''; + return interpolate( message, options.args ); } + // simple interpolation. probably quicker (and better) out there, + // but log is not in golden path of execution, only exceptions + function interpolate( message, args ) { + return message.replace( /{([^{}]*)}/g, function( a, b ) { + return args[ b ]; + } ); + } + return log; + }( warn, errors ); - i = fragment.items.length; - while ( i-- ) { - item = fragment.items[i]; + /* Ractive/prototype/shared/hooks/Hook.js */ + var Ractive$shared_hooks_Hook = function( log ) { - switch ( item.type ) { - case ELEMENT: - reassignElement( item, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - break; + var deprecations = { + construct: { + deprecated: 'beforeInit', + replacement: 'onconstruct' + }, + render: { + deprecated: 'init', + message: 'The "init" method has been deprecated ' + 'and will likely be removed in a future release. ' + 'You can either use the "oninit" method which will fire ' + 'only once prior to, and regardless of, any eventual ractive ' + 'instance being rendered, or if you need to access the ' + 'rendered DOM, use "onrender" instead. ' + 'See http://docs.ractivejs.org/latest/migrating for more information.' + }, + complete: { + deprecated: 'complete', + replacement: 'oncomplete' + } + }; - case PARTIAL: - reassignFragment( item.fragment, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - break; - - case SECTION: - case INTERPOLATOR: - case TRIPLE: - reassignMustache( item, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - break; + function Hook( event ) { + this.event = event; + this.method = 'on' + event; + this.deprecate = deprecations[ event ]; + } + Hook.prototype.fire = function( ractive, arg ) { + function call( method ) { + if ( ractive[ method ] ) { + arg ? ractive[ method ]( arg ) : ractive[ method ](); + return true; + } } + call( this.method ); + if ( !ractive[ this.method ] && this.deprecate && call( this.deprecate.deprecated ) ) { + log.warnAlways( { + debug: ractive.debug, + message: this.deprecate.message || 'methodDeprecated', + args: this.deprecate + } ); + } + arg ? ractive.fire( this.event, arg ) : ractive.fire( this.event ); + }; + return Hook; + }( log ); + + /* utils/removeFromArray.js */ + var removeFromArray = function( array, member ) { + var index = array.indexOf( member ); + if ( index !== -1 ) { + array.splice( index, 1 ); } }; - reassignElement = function ( element, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ) { - var i, attribute; + /* utils/Promise.js */ + var Promise = function() { - i = element.attributes.length; - while ( i-- ) { - attribute = element.attributes[i]; - - if ( attribute.fragment ) { - reassignFragment( attribute.fragment, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - - if ( attribute.twoway ) { - attribute.updateBindings(); + var __export; + 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 ); + } ); + }; } - - // reassign proxy argument fragments TODO and intro/outro param fragments - if ( element.proxyFrags ) { - i = element.proxyFrags.length; - while ( i-- ) { - reassignFragment( element.proxyFrags[i], indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - } + __export = _Promise; + // TODO use MutationObservers or something to simulate setImmediate + function wait( callback ) { + setTimeout( callback, 0 ); } - if ( element.node._ractive ) { - if ( element.node._ractive.keypath.substr( 0, oldKeypath.length ) === oldKeypath ) { - element.node._ractive.keypath = element.node._ractive.keypath.replace( oldKeypath, newKeypath ); - } - - element.node._ractive.index[ indexRef ] = newIndex; + function makeDispatcher( handlers, result ) { + return function() { + var handler; + while ( handler = handlers.shift() ) { + handler( result ); + } + }; } - // reassign children - if ( element.fragment ) { - reassignFragment( element.fragment, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - } - }; - - reassignMustache = function ( mustache, indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ) { - var i; - - // expression mustache? - if ( mustache.descriptor.x ) { - if ( mustache.keypath ) { - unregisterDependant( mustache ); + 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' ); } - - if ( mustache.expressionResolver ) { - mustache.expressionResolver.teardown(); + // 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 ); } - - mustache.expressionResolver = new ExpressionResolver( mustache ); } + return __export; + }(); - // normal keypath mustache? - if ( mustache.keypath ) { - if ( mustache.keypath.substr( 0, oldKeypath.length ) === oldKeypath ) { - unregisterDependant( mustache ); + /* utils/normaliseRef.js */ + var normaliseRef = function() { - mustache.keypath = mustache.keypath.replace( oldKeypath, newKeypath ); - registerDependant( mustache ); + 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 !== undefined ) { + return fragment.context; } - } + } while ( fragment = fragment.parent ); + return ''; + }; - // index ref mustache? - else if ( mustache.indexRef === indexRef ) { - mustache.value = newIndex; - mustache.render( newIndex ); + /* utils/isEqual.js */ + var isEqual = function( a, b ) { + if ( a === null && b === null ) { + return true; } - - // otherwise, it's an unresolved reference. the context stack has been updated - // so it will take care of itself - - // if it's a section mustache, we need to go through any children - if ( mustache.fragments ) { - i = mustache.fragments.length; - while ( i-- ) { - reassignFragment( mustache.fragments[i], indexRef, oldIndex, newIndex, by, oldKeypath, newKeypath ); - } + if ( typeof a === 'object' || typeof b === 'object' ) { + return false; } + return a === b; }; -}()); -(function ( cache ) { + /* shared/createComponentBinding.js */ + var createComponentBinding = function( circular, isEqual ) { - var Reference, getFunctionFromString; - - Evaluator = function ( root, keypath, functionStr, args, priority ) { - var i, arg; - - this.root = root; - this.keypath = keypath; - - this.fn = getFunctionFromString( functionStr, args.length ); - this.values = []; - this.refs = []; - - i = args.length; - while ( i-- ) { - arg = args[i]; - - if ( arg[0] ) { - // this is an index ref... we don't need to register a dependant - this.values[i] = arg[1]; + var runloop; + circular.push( function() { + return runloop = circular.runloop; + } ); + var Binding = function( ractive, keypath, otherInstance, otherKeypath ) { + var this$0 = this; + this.root = ractive; + this.keypath = keypath; + this.otherInstance = otherInstance; + this.otherKeypath = otherKeypath; + this.lock = function() { + return this$0.updating = true; + }; + this.unlock = function() { + return this$0.updating = false; + }; + this.bind(); + this.value = this.root.viewmodel.get( this.keypath ); + }; + Binding.prototype = { + isLocked: function() { + return this.updating || this.counterpart && this.counterpart.updating; + }, + shuffle: function( newIndices, value ) { + this.propagateChange( value, newIndices ); + }, + setValue: function( value ) { + this.propagateChange( value ); + }, + propagateChange: function( value, newIndices ) { + var other; + // Only *you* can prevent infinite loops + if ( this.isLocked() ) { + this.value = value; + return; + } + if ( !isEqual( value, this.value ) ) { + this.lock(); + // TODO maybe the case that `value === this.value` - should that result + // in an update rather than a set? + // if the other viewmodel is already locked up, need to do a deferred update + if ( !runloop.addViewmodel( other = this.otherInstance.viewmodel ) && this.counterpart.value !== value ) { + runloop.scheduleTask( function() { + return runloop.addViewmodel( other ); + } ); + } + if ( newIndices ) { + other.smartUpdate( this.otherKeypath, value, newIndices ); + } else { + if ( isSettable( other, this.otherKeypath ) ) { + other.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( this.unlock ); + } + }, + refineValue: function( keypaths ) { + var this$0 = this; + var other; + if ( this.isLocked() ) { + return; + } + this.lock(); + runloop.addViewmodel( other = this.otherInstance.viewmodel ); + keypaths.map( function( keypath ) { + return this$0.otherKeypath + keypath.substr( this$0.keypath.length ); + } ).forEach( function( keypath ) { + return other.mark( keypath ); + } ); + runloop.scheduleTask( this.unlock ); + }, + 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 ); } + }; - else { - this.refs[ this.refs.length ] = new Reference( root, arg[1], this, i, priority ); - } + function isSettable( viewmodel, keypath ) { + var computed = viewmodel.computations[ keypath ]; + return !computed || computed.setter; } - - this.selfUpdating = ( this.refs.length <= 1 ); - - this.update(); - }; - - Evaluator.prototype = { - bubble: function () { - // If we only have one reference, we can update immediately... - if ( this.selfUpdating ) { - this.update(); + return function createComponentBinding( component, parentInstance, parentKeypath, childKeypath ) { + var hash, childInstance, bindings, parentToChildBinding, childToParentBinding; + hash = parentKeypath + '=' + childKeypath; + bindings = component.bindings; + if ( bindings[ hash ] ) { + // TODO does this ever happen? + return; } - - // ...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 resolved, obviously - else if ( !this.deferred ) { - this.root._defEvals[ this.root._defEvals.length ] = this; - this.deferred = true; + childInstance = component.instance; + parentToChildBinding = new Binding( parentInstance, parentKeypath, childInstance, childKeypath ); + bindings.push( parentToChildBinding ); + if ( childInstance.twoway ) { + childToParentBinding = new Binding( childInstance, childKeypath, parentInstance, parentKeypath ); + bindings.push( childToParentBinding ); + parentToChildBinding.counterpart = childToParentBinding; + childToParentBinding.counterpart = parentToChildBinding; } - }, + bindings[ hash ] = parentToChildBinding; + }; + }( circular, isEqual ); - update: function () { - var value; + /* shared/resolveRef.js */ + var resolveRef = function( normaliseRef, getInnerContext, createComponentBinding ) { - try { - value = this.fn.apply( null, this.values ); - } catch ( err ) { - if ( this.root.debug ) { - throw err; - } else { - value = undefined; + var __export; + var ancestorErrorMessage, getOptions; + ancestorErrorMessage = 'Could not resolve reference - too many "../" prefixes'; + getOptions = { + evaluateWrapped: true + }; + __export = function resolveRef( ractive, ref, fragment, isParentLookup ) { + var context, key, index, keypath, parentValue, hasContextChain, parentKeys, childKeys, parentKeypath, childKeypath; + 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 ]; + // get() in viewmodel creation means no fragment (yet) + fragment = fragment || {}; + 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 ( !isEqual( value, this.value ) ) { - clearCache( this.root, this.keypath ); - this.root._cache[ this.keypath ] = value; - notifyDependants( this.root, this.keypath ); - - this.value = value; + // If this is an inline component, and it's not isolated, we + // can try going up the scope chain + if ( ractive._parent && !ractive.isolated ) { + hasContextChain = true; + fragment = ractive.component.parentFragment; + // Special case - index refs + if ( fragment.indexRefs && ( index = fragment.indexRefs[ ref ] ) !== undefined ) { + // Create an index ref binding, so that it can be rebound letter if necessary. + // It doesn't have an alias since it's an implicit binding, hence `...[ ref ] = ref` + ractive.component.indexRefBindings[ ref ] = ref; + ractive.viewmodel.set( ref, index, true ); + return; + } + keypath = resolveRef( ractive._parent, ref, fragment, true ); + if ( keypath ) { + // We need to create an inter-component binding + // If parent keypath is 'one.foo' and child is 'two.foo', we bind + // 'one' to 'two' as it's more efficient and avoids edge cases + parentKeys = keypath.split( '.' ); + childKeys = ref.split( '.' ); + while ( parentKeys.length > 1 && childKeys.length > 1 && parentKeys[ parentKeys.length - 1 ] === childKeys[ childKeys.length - 1 ] ) { + parentKeys.pop(); + childKeys.pop(); + } + parentKeypath = parentKeys.join( '.' ); + childKeypath = childKeys.join( '.' ); + ractive.viewmodel.set( childKeypath, ractive._parent.viewmodel.get( parentKeypath ), true ); + createComponentBinding( ractive.component, ractive._parent, parentKeypath, childKeypath ); + return ref; + } } - - return this; - }, - - // TODO should evaluators ever get torn down? - teardown: function () { - while ( this.refs.length ) { - this.refs.pop().teardown(); + // 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 ( !isParentLookup && !hasContextChain ) { + // the data object needs to have a property by this name, + // to prevent future failed lookups + ractive.viewmodel.set( ref, undefined ); + return ref; } - - clearCache( this.root, this.keypath ); - this.root._evaluators[ this.keypath ] = null; - }, - - // This method forces the evaluator to sync with the current model - // in the case of a smart update - refresh: function () { - if ( !this.selfUpdating ) { - this.deferred = true; + if ( ractive.viewmodel.get( ref ) !== undefined ) { + return ref; } + }; - var i = this.refs.length; - while ( i-- ) { - this.refs[i].update(); + 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( '.' ); } - - if ( this.deferred ) { - this.update(); - this.deferred = false; + // not an ancestor reference - must be a restricted reference (prepended with "." or "./") + if ( !baseContext ) { + return ref.replace( /^\.\/?/, '' ); } + return baseContext + ref.replace( /^\.\//, '.' ); } - }; + return __export; + }( normaliseRef, getInnerContext, createComponentBinding ); + /* global/TransitionManager.js */ + var TransitionManager = function( removeFromArray ) { - Reference = function ( root, keypath, evaluator, argNum, priority ) { - this.evaluator = evaluator; - this.keypath = keypath; - this.root = root; - this.argNum = argNum; - this.type = REFERENCE; - this.priority = priority; - - this.value = evaluator.values[ argNum ] = root.get( keypath ); - - registerDependant( this ); - }; - - Reference.prototype = { - update: function () { - var value = this.root.get( this.keypath ); - - if ( !isEqual( value, this.value ) ) { - this.evaluator.values[ this.argNum ] = value; - this.evaluator.bubble(); - - this.value = value; + 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 ); + } + }; - teardown: function () { - unregisterDependant( this ); + function detach( element ) { + element.detach(); } - }; - - getFunctionFromString = function ( str, i ) { - var fn, args; - - str = str.replace( /❖/g, '_' ); - - if ( cache[ str ] ) { - return cache[ str ]; + function detachNodes( tm ) { + tm.detachNodes(); } - args = []; - while ( i-- ) { - args[i] = '_' + i; - } - - fn = new Function( args.join( ',' ), 'return(' + str + ')' ); - - cache[ str ] = fn; - return fn; - }; - - - -}({})); -(function () { - - var ReferenceScout, getKeypath; - - ExpressionResolver = function ( mustache ) { - - var expression, i, len, ref, indexRefs, args; - - this.root = mustache.root; - this.mustache = mustache; - this.args = []; - this.scouts = []; - - expression = mustache.descriptor.x; - indexRefs = mustache.parentFragment.indexRefs; - - this.str = expression.s; - - // send out scouts for each reference - len = this.unresolved = ( expression.r ? expression.r.length : 0 ); - - if ( !len ) { - this.init(); // some expressions don't have references. edge case, but, yeah. - } - - for ( i=0; i<len; i+=1 ) { - ref = expression.r[i]; - - // is this an index ref? - if ( indexRefs && indexRefs[ ref ] !== undefined ) { - this.resolveRef( i, true, indexRefs[ ref ] ); + 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; } - - else { - this.scouts[ this.scouts.length ] = new ReferenceScout( this, ref, mustache.contextStack, i ); + // 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 ); - ExpressionResolver.prototype = { - init: function () { - this.keypath = getKeypath( this.str, this.args ); - this.createEvaluator(); + /* global/runloop.js */ + var runloop = function( circular, Hook, removeFromArray, Promise, resolveRef, TransitionManager ) { - this.mustache.resolve( this.keypath ); - }, - - teardown: function () { - while ( this.scouts.length ) { - this.scouts.pop().teardown(); + var __export; + var batch, runloop, unresolved = [], + changeHook = new Hook( 'change' ); + 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: [], + instance: instance + }; + if ( instance ) { + batch.viewmodels.push( instance.viewmodel ); + } + return promise; + }, + end: function() { + flushChanges(); + batch.transitionManager.init(); + if ( !batch.previousBatch && !!batch.instance ) + batch.instance.viewmodel.changes = []; + batch = batch.previousBatch; + }, + addViewmodel: function( viewmodel ) { + if ( batch ) { + if ( batch.viewmodels.indexOf( viewmodel ) === -1 ) { + batch.viewmodels.push( viewmodel ); + return true; + } else { + return false; + } + } else { + viewmodel.applyChanges(); + return false; + } + }, + 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, postRender ) { + var _batch; + if ( !batch ) { + task(); + } else { + _batch = batch; + while ( postRender && _batch.previousBatch ) { + // this can't happen until the DOM has been fully updated + // otherwise in some situations (with components inside elements) + // transitions and decorators will initialise prematurely + _batch = _batch.previousBatch; + } + _batch.tasks.push( task ); + } } - }, + }; + circular.runloop = runloop; + __export = runloop; - resolveRef: function ( argNum, isIndexRef, value ) { - this.args[ argNum ] = [ isIndexRef, value ]; - - // can we initialise yet? - if ( --this.unresolved ) { - // no; - return; + function flushChanges() { + var i, thing, changeHash; + for ( i = 0; i < batch.viewmodels.length; i += 1 ) { + thing = batch.viewmodels[ i ]; + changeHash = thing.applyChanges(); + if ( changeHash ) { + changeHook.fire( thing.ractive, changeHash ); + } } - - this.init(); - }, - - createEvaluator: function () { - // only if it doesn't exist yet! - if ( !this.root._evaluators[ this.keypath ] ) { - this.root._evaluators[ this.keypath ] = new Evaluator( this.root, this.keypath, this.str, this.args, this.mustache.priority ); + 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(); } - - else { - // we need to trigger a refresh of the evaluator, since it - // will have become de-synced from the model if we're in a - // reassignment cycle - this.root._evaluators[ this.keypath ].refresh(); + 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(); } - }; - - ReferenceScout = function ( resolver, ref, contextStack, argNum ) { - var keypath, root; - - root = this.root = resolver.root; - - keypath = resolveRef( root, ref, contextStack ); - if ( keypath ) { - resolver.resolveRef( argNum, false, keypath ); - } else { - this.ref = ref; - this.argNum = argNum; - this.resolver = resolver; - this.contextStack = contextStack; - - root._pendingResolution[ root._pendingResolution.length ] = this; + function attemptKeypathResolution() { + var i, item, keypath, resolved; + i = unresolved.length; + // see if we can resolve any unresolved references + while ( i-- ) { + item = unresolved[ i ]; + if ( item.keypath ) { + // it resolved some other way. TODO how? two-way binding? Seems + // weird that we'd still end up here + unresolved.splice( i, 1 ); + } + if ( keypath = resolveRef( item.root, item.ref, item.parentFragment ) ) { + ( resolved || ( resolved = [] ) ).push( { + item: item, + keypath: keypath + } ); + unresolved.splice( i, 1 ); + } + } + if ( resolved ) { + resolved.forEach( resolve ); + } } - }; - ReferenceScout.prototype = { - resolve: function ( keypath ) { - this.keypath = keypath; - this.resolver.resolveRef( this.argNum, false, keypath ); - }, - - teardown: function () { - // if we haven't found a keypath yet, we can - // stop the search now - if ( !this.keypath ) { - teardown( this ); - } + function resolve( resolved ) { + resolved.item.resolve( resolved.keypath ); } - }; + return __export; + }( circular, Ractive$shared_hooks_Hook, removeFromArray, Promise, resolveRef, TransitionManager ); - getKeypath = function ( str, args ) { - var unique; + /* utils/createBranch.js */ + var createBranch = function() { - // get string that is unique to this expression - unique = str.replace( /❖([0-9]+)/g, function ( match, $1 ) { - return args[ $1 ][1]; - }); + var numeric = /^\s*[0-9]+\s*$/; + return function( key ) { + return numeric.test( key ) ? [] : {}; + }; + }(); - // then sanitize by removing any periods or square brackets. Otherwise - // splitKeypath will go mental! - return '(' + unique.replace( /[\.\[\]]/g, '-' ) + ')'; - }; + /* viewmodel/prototype/get/magicAdaptor.js */ + var viewmodel$get_magicAdaptor = function( runloop, createBranch, isArray ) { -}()); -(function () { - - var getPartialFromRegistry, unpack; - - getPartialDescriptor = function ( root, name ) { - var el, partial; - - // If the partial was specified on this instance, great - if ( partial = getPartialFromRegistry( root, name ) ) { - return partial; + var __export; + 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; } + __export = magicAdaptor; - // If not, is it a global partial? - if ( partial = getPartialFromRegistry( Ractive, name ) ) { - return partial; - } - - // Does it exist on the page as a script tag? - if ( doc ) { - el = doc.getElementById( name ); - if ( el && el.tagName === 'SCRIPT' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); + 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; } - - Ractive.partials[ name ] = Ractive.parse( el.innerHTML ); + throw new Error( 'Cannot use magic mode with property "' + property + '" - object is not configurable' ); } - } - - partial = Ractive.partials[ name ]; - - // No match? Return an empty array - if ( !partial ) { - if ( root.debug && console && console.warn ) { - console.warn( 'Could not find descriptor for partial "' + name + '"' ); + // Time to wrap this property + if ( template ) { + oldGet = template.get; + oldSet = template.set; } - - return []; - } - - return unpack( partial ); - }; - - getPartialFromRegistry = function ( registry, name ) { - if ( registry.partials[ name ] ) { - - // If this was added manually to the registry, but hasn't been parsed, - // parse it now - if ( typeof registry.partials[ name ] === 'string' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); + get = oldGet || function() { + return value; + }; + set = function( v ) { + if ( oldSet ) { + oldSet( v ); } + value = oldGet ? oldGet() : v; + set._ractiveWrappers.forEach( updateWrapper ); + }; - registry.partials[ name ] = Ractive.parse( registry.partials[ name ] ); + 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; } - - return unpack( registry.partials[ name ] ); + // 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 + } ); } - }; + return __export; + }( runloop, createBranch, isArray ); - unpack = function ( partial ) { - // Unpack string, if necessary - if ( partial.length === 1 && typeof partial[0] === 'string' ) { - return partial[0]; - } + /* config/magic.js */ + var magic = function( magicAdaptor ) { - return partial; + 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/' }; -}()); -initFragment = function ( fragment, options ) { + /* utils/createElement.js */ + var createElement = function( svg, namespaces ) { - var numItems, i, itemOptions, parentRefs, ref; + 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 ); - // The item that owns this fragment - an element, section, partial, or attribute - fragment.owner = options.owner; + /* config/isClient.js */ + var isClient = function() { - // inherited properties - fragment.root = options.root; - fragment.parentNode = options.parentNode; - fragment.contextStack = options.contextStack || []; + var isClient = typeof document === 'object'; + return isClient; + }(); - // 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; - } + /* utils/defineProperty.js */ + var defineProperty = function( isClient ) { - // 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 = createFromNull(); // avoids need for hasOwnProperty - - for ( ref in parentRefs ) { - fragment.indexRefs[ ref ] = parentRefs[ ref ]; + 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 ); - // while we're in this branch, inherit priority - fragment.priority = fragment.owner.parentFragment.priority + 1; - } else { - fragment.priority = 0; - } + /* utils/defineProperties.js */ + var defineProperties = function( createElement, defineProperty, isClient ) { - if ( options.indexRef ) { - if ( !fragment.indexRefs ) { - fragment.indexRefs = {}; + 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 ); - fragment.indexRefs[ options.indexRef ] = options.index; - } + /* Ractive/prototype/shared/add.js */ + var Ractive$shared_add = function( isNumeric ) { - // Time to create this fragment's child items; - fragment.items = []; + 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 ); - itemOptions = { - parentFragment: fragment - }; + /* Ractive/prototype/add.js */ + var Ractive$add = function( add ) { - numItems = ( options.descriptor ? options.descriptor.length : 0 ); - for ( i=0; i<numItems; i+=1 ) { - itemOptions.descriptor = options.descriptor[i]; - itemOptions.index = i; + return function Ractive$add( keypath, d ) { + return add( this, keypath, d === undefined ? 1 : +d ); + }; + }( Ractive$shared_add ); - fragment.items[ fragment.items.length ] = fragment.createItem( itemOptions ); - } + /* utils/normaliseKeypath.js */ + var normaliseKeypath = function( normaliseRef ) { -}; -initMustache = function ( mustache, options ) { + var leadingDot = /^\.+/; + return function normaliseKeypath( keypath ) { + return normaliseRef( keypath ).replace( leadingDot, '' ); + }; + }( normaliseRef ); - var keypath, index, indexRef, parentFragment; + /* config/vendors.js */ + var vendors = [ + 'o', + 'ms', + 'moz', + 'webkit' + ]; - parentFragment = mustache.parentFragment = options.parentFragment; + /* utils/requestAnimationFrame.js */ + var requestAnimationFrame = function( vendors ) { - mustache.root = parentFragment.root; - mustache.contextStack = parentFragment.contextStack; - - mustache.descriptor = options.descriptor; - mustache.index = options.index || 0; - mustache.priority = parentFragment.priority; + 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 ); - // DOM only - if ( parentFragment.parentNode ) { - mustache.parentNode = parentFragment.parentNode; - } + /* utils/getTime.js */ + var getTime = function() { - 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 ( parentFragment.indexRefs && parentFragment.indexRefs[ options.descriptor.r ] !== undefined ) { - indexRef = parentFragment.indexRefs[ options.descriptor.r ]; - - mustache.indexRef = options.descriptor.r; - mustache.value = indexRef; - mustache.render( mustache.value ); + 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; + }(); - 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.root._pendingResolution.length ] = mustache; + /* shared/animations.js */ + var animations = function( rAF, getTime, runloop ) { - // inverted section? initialise - if ( mustache.descriptor.n ) { - mustache.render( false ); + 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 ); - // if it's an expression, we have a bit more work to do - if ( options.descriptor.x ) { - mustache.expressionResolver = new ExpressionResolver( mustache ); - } + /* config/options/css/transform.js */ + var transform = function() { -}; - - -// methods to add to individual mustache prototypes -updateMustache = function () { - var value; - - value = this.root.get( this.keypath, true ); - - if ( !isEqual( value, this.value ) ) { - this.render( value ); - this.value = value; - } -}; - -resolveMustache = function ( keypath ) { - // TEMP - this.keypath = keypath; - - registerDependant( this ); - this.update(); - - if ( this.expressionResolver ) { - this.expressionResolver = null; - } -}; -(function () { - - var updateInvertedSection, updateListSection, updateContextSection, updateConditionalSection; - - updateSection = function ( section, value ) { - var fragmentOptions; - - fragmentOptions = { - descriptor: section.descriptor.f, - root: section.root, - parentNode: section.parentNode, - owner: section + var __export; + var selectorsPattern = /(?:^|\})?\s*([^\{\}]+)\s*\{/g, + commentsPattern = /\/\*.*?\*\//g, + selectorUnitPattern = /((?:(?:\[[^\]+]\])|(?:[^\s\+\>\~:]))+)((?::[^\s\+\>\~]+)?\s*[\s\+\>\~]?)\s*/g, + mediaQueryPattern = /^@media/, + dataRvcGuidPattern = /\[data-rvcguid="[a-z0-9-]+"]/g; + __export = 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; }; - // if section is inverted, only check for truthiness/falsiness - if ( section.descriptor.n ) { - updateConditionalSection( section, value, true, fragmentOptions ); - return; + function trim( str ) { + if ( str.trim ) { + return str.trim(); + } + return str.replace( /^\s+/, '' ).replace( /\s+$/, '' ); } - // otherwise we need to work out what sort of section we're dealing with - - // if value is an array, iterate through - if ( isArray( value ) ) { - updateListSection( section, value, fragmentOptions ); + function extractString( unit ) { + return unit.str; } + return __export; + }(); + /* config/options/css/css.js */ + var css = function( transformCss ) { - // if value is a hash... - else if ( isObject( value ) ) { - updateContextSection( section, fragmentOptions ); - } + var cssConfig = { + name: 'css', + extend: extend, + init: function() {} + }; - - // otherwise render if value is truthy, unrender if falsy - else { - updateConditionalSection( section, value, false, fragmentOptions ); + 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; + } } - }; - updateListSection = function ( section, value, fragmentOptions ) { - var i, fragmentsToRemove; - - // if the array is shorter than it was previously, remove items - if ( value.length < section.length ) { - fragmentsToRemove = section.fragments.splice( value.length, section.length - value.length ); - - while ( fragmentsToRemove.length ) { - fragmentsToRemove.pop().teardown( true ); + function getCss( css, target, guid ) { + if ( !css ) { + return; } + return target.noCssTransform ? css : transformCss( css, guid ); } + return cssConfig; + }( transform ); - // otherwise... - else { + /* utils/wrapMethod.js */ + var wrapMethod = function() { - if ( value.length > section.length ) { - // add any new ones - for ( i=section.length; i<value.length; i+=1 ) { - // append list item to context stack - fragmentOptions.contextStack = section.contextStack.concat( section.keypath + '.' + i ); - fragmentOptions.index = i; - - if ( section.descriptor.i ) { - fragmentOptions.indexRef = section.descriptor.i; + var __export; + __export = 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; } - - section.fragments[i] = section.createFragment( fragmentOptions ); - } + return result; + }; + } else { + return method; } - } + }; - section.length = value.length; - }; - - updateContextSection = function ( section, fragmentOptions ) { - // ...then if it isn't rendered, render it, adding section.keypath to the context stack - // (if it is already rendered, then any children dependent on the context stack - // will update themselves without any prompting) - if ( !section.length ) { - // append this section to the context stack - fragmentOptions.contextStack = section.contextStack.concat( section.keypath ); - fragmentOptions.index = 0; - - section.fragments[0] = section.createFragment( fragmentOptions ); - section.length = 1; + function needsSuper( method, superMethod ) { + return typeof superMethod === 'function' && /_super/.test( method ); } - }; + return __export; + }(); - updateConditionalSection = function ( section, value, inverted, fragmentOptions ) { - var doRender, emptyArray, fragmentsToRemove; + /* config/options/data.js */ + var data = function( wrap ) { - emptyArray = ( isArray( value ) && value.length === 0 ); + var __export; + var dataConfig = { + name: 'data', + extend: extend, + init: init, + reset: reset + }; + __export = dataConfig; - if ( inverted ) { - doRender = emptyArray || !value; - } else { - doRender = value && !emptyArray; - } - - if ( doRender ) { - if ( !section.length ) { - // no change to context stack - fragmentOptions.contextStack = section.contextStack; - fragmentOptions.index = 0; - - section.fragments[0] = section.createFragment( fragmentOptions ); - section.length = 1; + function combine( Parent, target, options ) { + var value = options.data || {}, + parentValue = getAddedKeys( Parent.prototype.data ); + if ( typeof value !== 'object' && typeof value !== 'function' ) { + throw new TypeError( 'data option must be an object or a function, "' + value + '" is not valid' ); } - - if ( section.length > 1 ) { - fragmentsToRemove = section.fragments.splice( 1 ); - - while ( fragmentsToRemove.length ) { - fragmentsToRemove.pop().teardown( true ); - } - } + return dispatch( parentValue, value ); } - else if ( section.length ) { - section.teardownFragments( true ); - section.length = 0; + function extend( Parent, proto, options ) { + proto.data = combine( Parent, proto, options ); } - }; -}()); -stripCommentTokens = function ( tokens ) { - var i, current, previous, next; - - for ( i=0; i<tokens.length; i+=1 ) { - current = tokens[i]; - previous = tokens[i-1]; - next = tokens[i+1]; - - // if the current token is a comment or a delimiter change, remove it... - if ( current.mustacheType === COMMENT || current.mustacheType === DELIMCHANGE ) { - - tokens.splice( i, 1 ); // remove comment token - - // ... and see if it has text nodes either side, in which case - // they can be concatenated - if ( previous && next ) { - if ( previous.type === TEXT && next.type === TEXT ) { - previous.value += next.value; - - tokens.splice( i, 1 ); // remove next token - } + function init( Parent, ractive, options ) { + var value = options.data, + result = combine( Parent, ractive, options ); + if ( typeof result === 'function' ) { + result = result.call( ractive, value ) || value; } - - i -= 1; // decrement i to account for the splice(s) + return ractive.data = result || {}; } - } - return tokens; -}; -stripHtmlComments = function ( html ) { - var commentStart, commentEnd, processed; - - processed = ''; - - while ( html.length ) { - commentStart = html.indexOf( '<!--' ); - commentEnd = html.indexOf( '-->' ); - - // no comments? great - if ( commentStart === -1 && commentEnd === -1 ) { - processed += html; - break; + function reset( ractive ) { + var result = this.init( ractive.constructor, ractive, ractive ); + if ( result ) { + ractive.data = result; + return true; + } } - // comment start but no comment end - if ( commentStart !== -1 && commentEnd === -1 ) { - throw 'Illegal HTML - expected closing comment sequence (\'-->\')'; + 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 ); } - // comment end but no comment start, or comment end before comment start - if ( ( commentEnd !== -1 && commentStart === -1 ) || ( commentEnd < commentStart ) ) { - throw 'Illegal HTML - unexpected closing comment sequence (\'-->\')'; + 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 ); + } } - processed += html.substr( 0, commentStart ); - html = html.substring( commentEnd + 3 ); - } - - return processed; -}; -stripStandalones = function ( tokens ) { - var i, current, backOne, backTwo, leadingLinebreak, trailingLinebreak; - - leadingLinebreak = /^\s*\r?\n/; - trailingLinebreak = /\r?\n\s*$/; - - for ( i=2; i<tokens.length; i+=1 ) { - current = tokens[i]; - backOne = tokens[i-1]; - backTwo = tokens[i-2]; - - // if we're at the end of a [text][mustache][text] sequence... - if ( current.type === TEXT && ( backOne.type === MUSTACHE ) && backTwo.type === TEXT ) { - - // ... and the mustache is a standalone (i.e. line breaks either side)... - if ( trailingLinebreak.test( backTwo.value ) && leadingLinebreak.test( current.value ) ) { - - // ... then we want to remove the whitespace after the first line break - // if the mustache wasn't a triple or interpolator or partial - if ( backOne.mustacheType !== INTERPOLATOR && backOne.mustacheType !== TRIPLE ) { - backTwo.value = backTwo.value.replace( trailingLinebreak, '\n' ); + function copy( from, to, fillOnly ) { + for ( var key in from ) { + if ( fillOnly && key in to ) { + continue; } - - // and the leading line break of the second text token - current.value = current.value.replace( leadingLinebreak, '' ); - - // if that means the current token is now empty, we should remove it - if ( current.value === '' ) { - tokens.splice( i--, 1 ); // splice and decrement - } + to[ key ] = from[ key ]; } } - } - return tokens; -}; -(function ( proto ) { - - var add = function ( root, keypath, d ) { - var value; - - if ( typeof keypath !== 'string' || !isNumeric( d ) ) { - if ( root.debug ) { - throw new Error( 'Bad arguments' ); + function fromProperties( child, parent ) { + child = child || {}; + if ( !parent ) { + return child; } - return; + copy( parent, child, true ); + return child; } - value = root.get( keypath ); - - if ( value === undefined ) { - value = 0; + 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; + }; } - if ( !isNumeric( value ) ) { - if ( root.debug ) { - throw new Error( 'Cannot add to a non-numeric value' ); + 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; + return wrap( childFn, parentFn ); } + return __export; + }( wrapMethod ); - root.set( keypath, value + d ); + /* 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, + SECTION_IF_WITH: 54 }; - proto.add = function ( keypath, d ) { - add( this, keypath, ( d === undefined ? 1 : d ) ); - }; + /* utils/create.js */ + var create = function() { - proto.subtract = function ( keypath, d ) { - add( this, keypath, ( d === undefined ? -1 : -d ) ); - }; - - proto.toggle = function ( keypath ) { - var value; - - if ( typeof keypath !== 'string' ) { - if ( this.debug ) { - throw new Error( 'Bad arguments' ); - } - return; + 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; + }(); - value = this.get( keypath ); - this.set( keypath, !value ); + /* parse/Parser/expressions/shared/errors.js */ + var parse_Parser_expressions_shared_errors = { + expectedExpression: 'Expected a JavaScript expression', + expectedParen: 'Expected closing paren' }; -}( proto )); -(function ( proto ) { + /* parse/Parser/expressions/primary/literal/numberLiteral.js */ + var numberLiteral = function( types ) { - var animate, noAnimation; + 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 ); - proto.animate = function ( keypath, to, options ) { - - var k, animation, animations; + /* parse/Parser/expressions/primary/literal/booleanLiteral.js */ + var booleanLiteral = function( types ) { - // animate multiple keypaths - if ( typeof keypath === 'object' ) { - options = to || {}; - animations = []; - - for ( k in keypath ) { - if ( hasOwn.call( keypath, k ) ) { - animations[ animations.length ] = animate( this, k, keypath[k], options ); - } + 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 ); - return { - stop: function () { - while ( animations.length ) { - animations.pop().stop(); + /* 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 ); }; - } + }; + }(); - // animate a single keypath - options = options || {}; + /* parse/Parser/expressions/primary/literal/stringLiteral/singleQuotedString.js */ + var singleQuotedString = function( makeQuotedStringMatcher ) { - animation = animate( this, keypath, to, options ); + return makeQuotedStringMatcher( '"' ); + }( makeQuotedStringMatcher ); - return { - stop: function () { - animation.stop(); + /* 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 ); - noAnimation = { - stop: noop + /* parse/Parser/expressions/shared/patterns.js */ + var patterns = { + name: /^[a-zA-Z_$][a-zA-Z_$0-9]*/ }; - animate = function ( root, keypath, to, options ) { - var easing, duration, animation, i, keys, from; + /* parse/Parser/expressions/shared/key.js */ + var key = function( getStringLiteral, getNumberLiteral, patterns ) { - from = root.get( keypath ); - - // 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(); + 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, '\\"' ) + '"'; } - } - - // don't bother animating values that stay the same - if ( isEqual( from, to ) ) { - if ( options.complete ) { - options.complete( 1, options.to ); + if ( token = getNumberLiteral( parser ) ) { + return token.v; } + if ( token = parser.matchPattern( patterns.name ) ) { + return token; + } + }; + }( stringLiteral, numberLiteral, patterns ); - return noAnimation; - } + /* parse/Parser/expressions/primary/literal/objectLiteral/keyValuePair.js */ + var keyValuePair = function( types, getKey ) { - // easing function - if ( options.easing ) { - if ( typeof options.easing === 'function' ) { - easing = options.easing; + 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; } - - else { - if ( root.easing && root.easing[ options.easing ] ) { - // use instance easing function first - easing = root.easing[ options.easing ]; - } else { - // fallback to global easing functions - easing = Ractive.easing[ options.easing ]; - } + // allow whitespace between key and ':' + parser.allowWhitespace(); + // next character must be ':' + if ( !parser.matchString( ':' ) ) { + parser.pos = start; + return null; } - - if ( typeof easing !== 'function' ) { - easing = null; + // allow whitespace between ':' and value + parser.allowWhitespace(); + // next expression must be a, well... expression + value = parser.readExpression(); + if ( value === null ) { + parser.pos = start; + return null; } - } + return { + t: types.KEY_VALUE_PAIR, + k: key, + v: value + }; + }; + }( types, key ); - // duration - duration = ( options.duration === undefined ? 400 : options.duration ); + /* parse/Parser/expressions/primary/literal/objectLiteral/keyValuePairs.js */ + var keyValuePairs = function( getKeyValuePair ) { - // TODO store keys, use an internal set method - //keys = splitKeypath( keypath ); + return function getKeyValuePairs( parser ) { + var start, pairs, pair, keyValuePairs; + start = parser.pos; + pair = getKeyValuePair( parser ); + if ( pair === null ) { + return null; + } + pairs = [ pair ]; + if ( parser.matchString( ',' ) ) { + keyValuePairs = getKeyValuePairs( parser ); + if ( !keyValuePairs ) { + parser.pos = start; + return null; + } + return pairs.concat( keyValuePairs ); + } + return pairs; + }; + }( keyValuePair ); - animation = new Animation({ - keypath: keypath, - from: from, - to: to, - root: root, - duration: duration, - easing: easing, - step: options.step, - complete: options.complete - }); + /* parse/Parser/expressions/primary/literal/objectLiteral/_objectLiteral.js */ + var objectLiteral = function( types, getKeyValuePairs ) { - animationCollection.push( animation ); - root._animations[ root._animations.length ] = animation; + return function( parser ) { + var start, keyValuePairs; + start = parser.pos; + // allow whitespace + parser.allowWhitespace(); + if ( !parser.matchString( '{' ) ) { + parser.pos = start; + return null; + } + keyValuePairs = getKeyValuePairs( parser ); + // allow whitespace between final value and '}' + parser.allowWhitespace(); + if ( !parser.matchString( '}' ) ) { + parser.pos = start; + return null; + } + return { + t: types.OBJECT_LITERAL, + m: keyValuePairs + }; + }; + }( types, keyValuePairs ); - return animation; - }; + /* parse/Parser/expressions/shared/expressionList.js */ + var expressionList = function( errors ) { -}( proto )); -proto.bind = function ( adaptor ) { - var bound = this._bound; + return function getExpressionList( parser ) { + var start, expressions, expr, next; + start = parser.pos; + parser.allowWhitespace(); + expr = parser.readExpression(); + if ( expr === null ) { + return null; + } + expressions = [ expr ]; + // allow whitespace between expression and ',' + parser.allowWhitespace(); + if ( parser.matchString( ',' ) ) { + next = getExpressionList( parser ); + if ( next === null ) { + parser.error( errors.expectedExpression ); + } + next.forEach( append ); + } - if ( bound.indexOf( adaptor ) === -1 ) { - bound[ bound.length ] = adaptor; - adaptor.init( this ); - } -}; -proto.cancelFullscreen = function () { - Ractive.cancelFullscreen( this.el ); -}; -proto.fire = function ( eventName ) { - var args, i, len, subscribers = this._subs[ eventName ]; + function append( expression ) { + expressions.push( expression ); + } + return expressions; + }; + }( parse_Parser_expressions_shared_errors ); - if ( !subscribers ) { - return; - } + /* parse/Parser/expressions/primary/literal/arrayLiteral.js */ + var arrayLiteral = function( types, getExpressionList ) { - args = Array.prototype.slice.call( arguments, 1 ); + return function( parser ) { + var start, expressionList; + start = parser.pos; + // allow whitespace before '[' + parser.allowWhitespace(); + if ( !parser.matchString( '[' ) ) { + parser.pos = start; + return null; + } + expressionList = getExpressionList( parser ); + if ( !parser.matchString( ']' ) ) { + parser.pos = start; + return null; + } + return { + t: types.ARRAY_LITERAL, + m: expressionList + }; + }; + }( types, expressionList ); - for ( i=0, len=subscribers.length; i<len; i+=1 ) { - subscribers[i].apply( this, args ); - } -}; -// TODO use dontNormalise -// TODO refactor this shitball + /* parse/Parser/expressions/primary/literal/_literal.js */ + var literal = function( getNumberLiteral, getBooleanLiteral, getStringLiteral, getObjectLiteral, getArrayLiteral ) { -proto.get = function ( keypath, dontNormalise ) { - var cache, cacheMap, keys, normalised, key, parentKeypath, parentValue, value, ignoreUndefined; + return function( parser ) { + var literal = getNumberLiteral( parser ) || getBooleanLiteral( parser ) || getStringLiteral( parser ) || getObjectLiteral( parser ) || getArrayLiteral( parser ); + return literal; + }; + }( numberLiteral, booleanLiteral, stringLiteral, objectLiteral, arrayLiteral ); - if ( !keypath ) { - return this.data; - } + /* parse/Parser/expressions/primary/reference.js */ + var reference = function( types, patterns ) { - cache = this._cache; + var dotRefinementPattern, arrayMemberPattern, getArrayRefinement, globals, keywords; + dotRefinementPattern = /^\.[a-zA-Z_$0-9]+/; + getArrayRefinement = function( parser ) { + var num = parser.matchPattern( arrayMemberPattern ); + if ( num ) { + return '.' + num; + } + return null; + }; + arrayMemberPattern = /^\[(0|[1-9][0-9]*)\]/; + // if a reference is a browser global, we don't deference it later, so it needs special treatment + globals = /^(?:Array|console|Date|RegExp|decodeURIComponent|decodeURI|encodeURIComponent|encodeURI|isFinite|isNaN|parseFloat|parseInt|JSON|Math|NaN|undefined|null)$/; + // keywords are not valid references, with the exception of `this` + keywords = /^(?:break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|throw|try|typeof|var|void|while|with)$/; + return function( parser ) { + var startPos, ancestor, name, dot, combo, refinement, lastDotIndex; + startPos = parser.pos; + // we might have a root-level reference + if ( parser.matchString( '~/' ) ) { + ancestor = '~/'; + } else { + // we might have ancestor refs... + ancestor = ''; + while ( parser.matchString( '../' ) ) { + ancestor += '../'; + } + } + if ( !ancestor ) { + // we might have an implicit iterator or a restricted reference + dot = parser.matchString( './' ) || parser.matchString( '.' ) || ''; + } + name = parser.matchPattern( /^@(?:keypath|index|key)/ ) || parser.matchPattern( patterns.name ) || ''; + // bug out if it's a keyword + if ( keywords.test( name ) ) { + parser.pos = startPos; + return null; + } + // if this is a browser global, stop here + if ( !ancestor && !dot && globals.test( name ) ) { + return { + t: types.GLOBAL, + v: name + }; + } + combo = ( ancestor || dot ) + name; + if ( !combo ) { + return null; + } + while ( refinement = parser.matchPattern( dotRefinementPattern ) || getArrayRefinement( parser ) ) { + combo += refinement; + } + if ( parser.matchString( '(' ) ) { + // if this is a method invocation (as opposed to a function) we need + // to strip the method name from the reference combo, else the context + // will be wrong + lastDotIndex = combo.lastIndexOf( '.' ); + if ( lastDotIndex !== -1 ) { + combo = combo.substr( 0, lastDotIndex ); + parser.pos = startPos + combo.length; + } else { + parser.pos -= 1; + } + } + return { + t: types.REFERENCE, + n: combo.replace( /^this\./, './' ).replace( /^this$/, '.' ) + }; + }; + }( types, patterns ); - if ( isArray( keypath ) ) { - if ( !keypath.length ) { - return this.data; - } + /* parse/Parser/expressions/primary/bracketedExpression.js */ + var bracketedExpression = function( types, errors ) { - keys = keypath.slice(); // clone - normalised = keys.join( '.' ); + return function( parser ) { + var start, expr; + start = parser.pos; + if ( !parser.matchString( '(' ) ) { + return null; + } + parser.allowWhitespace(); + expr = parser.readExpression(); + if ( !expr ) { + parser.error( errors.expectedExpression ); + } + parser.allowWhitespace(); + if ( !parser.matchString( ')' ) ) { + parser.error( errors.expectedParen ); + } + return { + t: types.BRACKETED, + x: expr + }; + }; + }( types, parse_Parser_expressions_shared_errors ); - ignoreUndefined = true; // because this should be a branch, sod the cache - } + /* parse/Parser/expressions/primary/_primary.js */ + var primary = function( getLiteral, getReference, getBracketedExpression ) { - else { - // cache hit? great - if ( hasOwn.call( cache, keypath ) && cache[ keypath ] !== UNSET ) { - return cache[ keypath ]; - } + return function( parser ) { + return getLiteral( parser ) || getReference( parser ) || getBracketedExpression( parser ); + }; + }( literal, reference, bracketedExpression ); - keys = splitKeypath( keypath ); - normalised = keys.join( '.' ); - } + /* parse/Parser/expressions/shared/refinement.js */ + var refinement = function( types, errors, patterns ) { - // we may have a cache hit now that it's been normalised - if ( hasOwn.call( cache, normalised ) && cache[ normalised ] !== UNSET ) { - if ( cache[ normalised ] === undefined && ignoreUndefined ) { - // continue - } else { - return cache[ normalised ]; - } - } + return function getRefinement( parser ) { + var start, name, expr; + start = parser.pos; + parser.allowWhitespace(); + // "." name + if ( parser.matchString( '.' ) ) { + parser.allowWhitespace(); + if ( name = parser.matchPattern( patterns.name ) ) { + return { + t: types.REFINEMENT, + n: name + }; + } + parser.error( 'Expected a property name' ); + } + // "[" expression "]" + if ( parser.matchString( '[' ) ) { + parser.allowWhitespace(); + expr = parser.readExpression(); + if ( !expr ) { + parser.error( errors.expectedExpression ); + } + parser.allowWhitespace(); + if ( !parser.matchString( ']' ) ) { + parser.error( 'Expected \']\'' ); + } + return { + t: types.REFINEMENT, + x: expr + }; + } + return null; + }; + }( types, parse_Parser_expressions_shared_errors, patterns ); - // is this an uncached evaluator value? - if ( this._evaluators[ normalised ] ) { - value = this._evaluators[ normalised ].value; - cache[ normalised ] = value; - return value; - } + /* parse/Parser/expressions/memberOrInvocation.js */ + var memberOrInvocation = function( types, getPrimary, getExpressionList, getRefinement, errors ) { - // otherwise it looks like we need to do some work - key = keys.pop(); - parentKeypath = keys.join( '.' ); - parentValue = ( keys.length ? this.get( keys ) : this.data ); + return function( parser ) { + var current, expression, refinement, expressionList; + expression = getPrimary( parser ); + if ( !expression ) { + return null; + } + while ( expression ) { + current = parser.pos; + if ( refinement = getRefinement( parser ) ) { + expression = { + t: types.MEMBER, + x: expression, + r: refinement + }; + } else if ( parser.matchString( '(' ) ) { + parser.allowWhitespace(); + expressionList = getExpressionList( parser ); + parser.allowWhitespace(); + if ( !parser.matchString( ')' ) ) { + parser.error( errors.expectedParen ); + } + expression = { + t: types.INVOCATION, + x: expression + }; + if ( expressionList ) { + expression.o = expressionList; + } + } else { + break; + } + } + return expression; + }; + }( types, primary, expressionList, refinement, parse_Parser_expressions_shared_errors ); - if ( parentValue === null || typeof parentValue !== 'object' || parentValue === UNSET ) { - return; - } + /* parse/Parser/expressions/typeof.js */ + var _typeof = function( types, errors, getMemberOrInvocation ) { - // update cache map - if ( !( cacheMap = this._cacheMap[ parentKeypath ] ) ) { - this._cacheMap[ parentKeypath ] = [ normalised ]; - } else { - if ( cacheMap.indexOf( normalised ) === -1 ) { - cacheMap[ cacheMap.length ] = normalised; - } - } + var getTypeof, makePrefixSequenceMatcher; + makePrefixSequenceMatcher = function( symbol, fallthrough ) { + return function( parser ) { + var expression; + if ( expression = fallthrough( parser ) ) { + return expression; + } + if ( !parser.matchString( symbol ) ) { + return null; + } + parser.allowWhitespace(); + expression = parser.readExpression(); + if ( !expression ) { + parser.error( errors.expectedExpression ); + } + return { + s: symbol, + o: expression, + t: types.PREFIX_OPERATOR + }; + }; + }; + // create all prefix sequence matchers, return getTypeof + ( function() { + var i, len, matcher, prefixOperators, fallthrough; + prefixOperators = '! ~ + - typeof'.split( ' ' ); + fallthrough = getMemberOrInvocation; + for ( i = 0, len = prefixOperators.length; i < len; i += 1 ) { + matcher = makePrefixSequenceMatcher( prefixOperators[ i ], fallthrough ); + fallthrough = matcher; + } + // typeof operator is higher precedence than multiplication, so provides the + // fallthrough for the multiplication sequence matcher we're about to create + // (we're skipping void and delete) + getTypeof = fallthrough; + }() ); + return getTypeof; + }( types, parse_Parser_expressions_shared_errors, memberOrInvocation ); - value = parentValue[ key ]; + /* parse/Parser/expressions/logicalOr.js */ + var logicalOr = function( types, getTypeof ) { - // 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 ); - } - } + var getLogicalOr, makeInfixSequenceMatcher; + makeInfixSequenceMatcher = function( symbol, fallthrough ) { + return function( parser ) { + var start, left, right; + left = fallthrough( parser ); + if ( !left ) { + return null; + } + // Loop to handle left-recursion in a case like `a * b * c` and produce + // left association, i.e. `(a * b) * c`. The matcher can't call itself + // to parse `left` because that would be infinite regress. + while ( true ) { + start = parser.pos; + parser.allowWhitespace(); + if ( !parser.matchString( symbol ) ) { + parser.pos = start; + return left; + } + // special case - in operator must not be followed by [a-zA-Z_$0-9] + if ( symbol === 'in' && /[a-zA-Z_$0-9]/.test( parser.remaining().charAt( 0 ) ) ) { + parser.pos = start; + return left; + } + parser.allowWhitespace(); + // right operand must also consist of only higher-precedence operators + right = fallthrough( parser ); + if ( !right ) { + parser.pos = start; + return left; + } + left = { + t: types.INFIX_OPERATOR, + s: symbol, + o: [ + left, + right + ] + }; + } + }; + }; + // create all infix sequence matchers, and return getLogicalOr + ( function() { + var i, len, matcher, infixOperators, fallthrough; + // All the infix operators on order of precedence (source: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence) + // Each sequence matcher will initially fall through to its higher precedence + // neighbour, and only attempt to match if one of the higher precedence operators + // (or, ultimately, a literal, reference, or bracketed expression) already matched + infixOperators = '* / % + - << >> >>> < <= > >= in instanceof == != === !== & ^ | && ||'.split( ' ' ); + // A typeof operator is higher precedence than multiplication + fallthrough = getTypeof; + for ( i = 0, len = infixOperators.length; i < len; i += 1 ) { + matcher = makeInfixSequenceMatcher( infixOperators[ i ], fallthrough ); + fallthrough = matcher; + } + // Logical OR is the fallthrough for the conditional matcher + getLogicalOr = fallthrough; + }() ); + return getLogicalOr; + }( types, _typeof ); - // Update cache - cache[ normalised ] = value; + /* parse/Parser/expressions/conditional.js */ + var conditional = function( types, getLogicalOr, errors ) { - return value; -}; -clearCache = function ( ractive, keypath ) { - var value, len, kp, cacheMap; - - // is this a modified array, which shouldn't fire set events on this keypath anymore? - if ( ractive.modifyArrays ) { - if ( keypath.charAt( 0 ) !== '(' ) { // expressions (and their children) don't get wrapped - value = ractive._cache[ keypath ]; - if ( isArray( value ) && !value._ractive.setting ) { - unregisterKeypathFromArray( value, keypath, ractive ); + return function( parser ) { + var start, expression, ifTrue, ifFalse; + expression = getLogicalOr( parser ); + if ( !expression ) { + return null; } - } - } - - ractive._cache[ keypath ] = UNSET; + start = parser.pos; + parser.allowWhitespace(); + if ( !parser.matchString( '?' ) ) { + parser.pos = start; + return expression; + } + parser.allowWhitespace(); + ifTrue = parser.readExpression(); + if ( !ifTrue ) { + parser.error( errors.expectedExpression ); + } + parser.allowWhitespace(); + if ( !parser.matchString( ':' ) ) { + parser.error( 'Expected ":"' ); + } + parser.allowWhitespace(); + ifFalse = parser.readExpression(); + if ( !ifFalse ) { + parser.error( errors.expectedExpression ); + } + return { + t: types.CONDITIONAL, + o: [ + expression, + ifTrue, + ifFalse + ] + }; + }; + }( types, logicalOr, parse_Parser_expressions_shared_errors ); - if ( cacheMap = ractive._cacheMap[ keypath ] ) { - while ( cacheMap.length ) { - clearCache( ractive, cacheMap.pop() ); - } - } -}; -notifyDependants = function ( ractive, keypath, onlyDirect ) { - var i; + /* parse/Parser/utils/flattenExpression.js */ + var flattenExpression = function( types, isObject ) { - for ( i=0; i<ractive._deps.length; i+=1 ) { // can't cache ractive._deps.length, it may change - notifyDependantsByPriority( ractive, keypath, i, onlyDirect ); - } -}; -notifyDependantsByPriority = function ( ractive, keypath, priority, onlyDirect ) { - var depsByKeypath, deps, i, len, childDeps; + var __export; + __export = function( expression ) { + var refs = [], + flattened; + extractRefs( expression, refs ); + flattened = { + r: refs, + s: stringify( this, expression, refs ) + }; + return flattened; + }; - depsByKeypath = ractive._deps[ priority ]; - - if ( !depsByKeypath ) { - return; - } - - deps = depsByKeypath[ keypath ]; - - if ( deps ) { - i = deps.length; - while ( i-- ) { - deps[i].update(); + function quoteStringLiteral( str ) { + return JSON.stringify( String( str ) ); } - } - - // If we're only notifying direct dependants, not dependants - // of downstream keypaths, then YOU SHALL NOT PASS - if ( onlyDirect ) { - return; - } - - - // cascade - childDeps = ractive._depsMap[ keypath ]; - - if ( childDeps ) { - i = childDeps.length; - while ( i-- ) { - notifyDependantsByPriority( ractive, childDeps[i], priority ); + // TODO maybe refactor this? + function extractRefs( node, refs ) { + var i, list; + if ( node.t === types.REFERENCE ) { + if ( refs.indexOf( node.n ) === -1 ) { + refs.unshift( node.n ); + } + } + list = node.o || node.m; + if ( list ) { + if ( isObject( list ) ) { + extractRefs( list, refs ); + } else { + i = list.length; + while ( i-- ) { + extractRefs( list[ i ], refs ); + } + } + } + if ( node.x ) { + extractRefs( node.x, refs ); + } + if ( node.r ) { + extractRefs( node.r, refs ); + } + if ( node.v ) { + extractRefs( node.v, refs ); + } } - } -}; -notifyMultipleDependants = function ( ractive, keypaths, onlyDirect ) { - var i, j, len; - len = keypaths.length; - - for ( i=0; i<ractive._deps.length; i+=1 ) { - if ( ractive._deps[i] ) { - j = len; - while ( j-- ) { - notifyDependantsByPriority( ractive, keypaths[j], i, onlyDirect ); + function stringify( parser, node, refs ) { + var stringifyAll = function( item ) { + return stringify( parser, item, refs ); + }; + switch ( node.t ) { + case types.BOOLEAN_LITERAL: + case types.GLOBAL: + case types.NUMBER_LITERAL: + return node.v; + case types.STRING_LITERAL: + return quoteStringLiteral( node.v ); + case types.ARRAY_LITERAL: + return '[' + ( node.m ? node.m.map( stringifyAll ).join( ',' ) : '' ) + ']'; + case types.OBJECT_LITERAL: + return '{' + ( node.m ? node.m.map( stringifyAll ).join( ',' ) : '' ) + '}'; + case types.KEY_VALUE_PAIR: + return node.k + ':' + stringify( parser, node.v, refs ); + case types.PREFIX_OPERATOR: + return ( node.s === 'typeof' ? 'typeof ' : node.s ) + stringify( parser, node.o, refs ); + case types.INFIX_OPERATOR: + return stringify( parser, node.o[ 0 ], refs ) + ( node.s.substr( 0, 2 ) === 'in' ? ' ' + node.s + ' ' : node.s ) + stringify( parser, node.o[ 1 ], refs ); + case types.INVOCATION: + return stringify( parser, node.x, refs ) + '(' + ( node.o ? node.o.map( stringifyAll ).join( ',' ) : '' ) + ')'; + case types.BRACKETED: + return '(' + stringify( parser, node.x, refs ) + ')'; + case types.MEMBER: + return stringify( parser, node.x, refs ) + stringify( parser, node.r, refs ); + case types.REFINEMENT: + return node.n ? '.' + node.n : '[' + stringify( parser, node.x, refs ) + ']'; + case types.CONDITIONAL: + return stringify( parser, node.o[ 0 ], refs ) + '?' + stringify( parser, node.o[ 1 ], refs ) + ':' + stringify( parser, node.o[ 2 ], refs ); + case types.REFERENCE: + return '_' + refs.indexOf( node.n ); + default: + parser.error( 'Expected legal JavaScript' ); } } - } -}; -processDeferredUpdates = function ( ractive ) { - var evaluator, attribute; + return __export; + }( types, isObject ); - while ( ractive._defEvals.length ) { - evaluator = ractive._defEvals.pop(); - evaluator.update().deferred = false; - } + /* parse/Parser/_Parser.js */ + var Parser = function( circular, create, hasOwnProperty, getConditional, flattenExpression ) { - while ( ractive._defAttrs.length ) { - attribute = ractive._defAttrs.pop(); - attribute.update().deferred = false; - } -}; -registerDependant = function ( dependant ) { - var depsByKeypath, deps, keys, parentKeypath, map, ractive, keypath, priority; + var Parser, ParseError, leadingWhitespace = /^\s+/; + ParseError = function( message ) { + this.name = 'ParseError'; + this.message = message; + try { + throw new Error( message ); + } catch ( e ) { + this.stack = e.stack; + } + }; + ParseError.prototype = Error.prototype; + Parser = function( str, options ) { + var items, item, lineStart = 0; + this.str = str; + this.options = options || {}; + this.pos = 0; + this.lines = this.str.split( '\n' ); + this.lineEnds = this.lines.map( function( line ) { + var lineEnd = lineStart + line.length + 1; + // +1 for the newline + lineStart = lineEnd; + return lineEnd; + }, 0 ); + // Custom init logic + if ( this.init ) + this.init( str, options ); + items = []; + while ( this.pos < this.str.length && ( item = this.read() ) ) { + items.push( item ); + } + this.leftover = this.remaining(); + this.result = this.postProcess ? this.postProcess( items, options ) : items; + }; + Parser.prototype = { + read: function( converters ) { + var pos, i, len, item; + if ( !converters ) + converters = this.converters; + pos = this.pos; + len = converters.length; + for ( i = 0; i < len; i += 1 ) { + this.pos = pos; + // reset for each attempt + if ( item = converters[ i ]( this ) ) { + return item; + } + } + return null; + }, + readExpression: function() { + // The conditional operator is the lowest precedence operator (except yield, + // assignment operators, and commas, none of which are supported), so we + // start there. If it doesn't match, it 'falls through' to progressively + // higher precedence operators, until it eventually matches (or fails to + // match) a 'primary' - a literal or a reference. This way, the abstract syntax + // tree has everything in its proper place, i.e. 2 + 3 * 4 === 14, not 20. + return getConditional( this ); + }, + flattenExpression: flattenExpression, + getLinePos: function( char ) { + var lineNum = 0, + lineStart = 0, + columnNum; + while ( char >= this.lineEnds[ lineNum ] ) { + lineStart = this.lineEnds[ lineNum ]; + lineNum += 1; + } + columnNum = char - lineStart; + return [ + lineNum + 1, + columnNum + 1, + char + ]; + }, + error: function( message ) { + var pos, lineNum, columnNum, line, annotation, error; + pos = this.getLinePos( this.pos ); + lineNum = pos[ 0 ]; + columnNum = pos[ 1 ]; + line = this.lines[ pos[ 0 ] - 1 ]; + annotation = line + '\n' + new Array( pos[ 1 ] ).join( ' ' ) + '^----'; + error = new ParseError( message + ' at line ' + lineNum + ' character ' + columnNum + ':\n' + annotation ); + error.line = pos[ 0 ]; + error.character = pos[ 1 ]; + error.shortMessage = message; + throw error; + }, + matchString: function( string ) { + if ( this.str.substr( this.pos, string.length ) === string ) { + this.pos += string.length; + return string; + } + }, + matchPattern: function( pattern ) { + var match; + if ( match = pattern.exec( this.remaining() ) ) { + this.pos += match[ 0 ].length; + return match[ 1 ] || match[ 0 ]; + } + }, + allowWhitespace: function() { + this.matchPattern( leadingWhitespace ); + }, + remaining: function() { + return this.str.substring( this.pos ); + }, + nextChar: function() { + return this.str.charAt( this.pos ); + } + }; + Parser.extend = function( proto ) { + var Parent = this, + Child, key; + Child = function( str, options ) { + Parser.call( this, str, options ); + }; + Child.prototype = create( Parent.prototype ); + for ( key in proto ) { + if ( hasOwnProperty.call( proto, key ) ) { + Child.prototype[ key ] = proto[ key ]; + } + } + Child.extend = Parser.extend; + return Child; + }; + circular.Parser = Parser; + return Parser; + }( circular, create, hasOwn, conditional, flattenExpression ); - ractive = dependant.root; - keypath = dependant.keypath; - priority = dependant.priority; + /* parse/converters/mustache/delimiterChange.js */ + var delimiterChange = function() { - depsByKeypath = ractive._deps[ priority ] || ( ractive._deps[ priority ] = {} ); - deps = depsByKeypath[ keypath ] || ( depsByKeypath[ keypath ] = [] ); + var delimiterChangePattern = /^[^\s=]+/, + whitespacePattern = /^\s+/; + return function( parser ) { + var start, opening, closing; + if ( !parser.matchString( '=' ) ) { + return null; + } + start = parser.pos; + // allow whitespace before new opening delimiter + parser.allowWhitespace(); + opening = parser.matchPattern( delimiterChangePattern ); + if ( !opening ) { + parser.pos = start; + return null; + } + // allow whitespace (in fact, it's necessary...) + if ( !parser.matchPattern( whitespacePattern ) ) { + return null; + } + closing = parser.matchPattern( delimiterChangePattern ); + if ( !closing ) { + parser.pos = start; + return null; + } + // allow whitespace before closing '=' + parser.allowWhitespace(); + if ( !parser.matchString( '=' ) ) { + parser.pos = start; + return null; + } + return [ + opening, + closing + ]; + }; + }(); - deps[ deps.length ] = dependant; + /* parse/converters/mustache/delimiterTypes.js */ + var delimiterTypes = [ { + delimiters: 'delimiters', + isTriple: false, + isStatic: false + }, { + delimiters: 'tripleDelimiters', + isTriple: true, + isStatic: false + }, { + delimiters: 'staticDelimiters', + isTriple: false, + isStatic: true + }, { + delimiters: 'staticTripleDelimiters', + isTriple: true, + isStatic: true + } ]; - // update dependants map - keys = splitKeypath( keypath ); - - while ( keys.length ) { - keys.pop(); - parentKeypath = keys.join( '.' ); - - map = ractive._depsMap[ parentKeypath ] || ( ractive._depsMap[ parentKeypath ] = [] ); + /* parse/converters/mustache/type.js */ + var type = function( types ) { - if ( map[ keypath ] === undefined ) { - map[ keypath ] = 0; - map[ map.length ] = keypath; - } + var mustacheTypes = { + '#': types.SECTION, + '^': types.INVERTED, + '/': types.CLOSING, + '>': types.PARTIAL, + '!': types.COMMENT, + '&': types.TRIPLE + }; + return function( parser ) { + var type = mustacheTypes[ parser.str.charAt( parser.pos ) ]; + if ( !type ) { + return null; + } + parser.pos += 1; + return type; + }; + }( types ); - map[ keypath ] += 1; + /* parse/converters/mustache/handlebarsBlockCodes.js */ + var handlebarsBlockCodes = function( types ) { - keypath = parentKeypath; - } -}; -// Render instance to element specified here or at initialization -render = function ( ractive, options ) { - var el, transitionManager; + return { + 'each': types.SECTION_EACH, + 'if': types.SECTION_IF, + 'if-with': types.SECTION_IF_WITH, + 'with': types.SECTION_WITH, + 'unless': types.SECTION_UNLESS + }; + }( types ); - el = ( options.el ? getEl( options.el ) : ractive.el ); + /* empty/legacy.js */ + var legacy = null; - // Clear the element, unless `append` is `true` - if ( el && !options.append ) { - el.innerHTML = ''; - } + /* parse/converters/mustache/content.js */ + var content = function( types, mustacheType, handlebarsBlockCodes ) { - ractive._transitionManager = transitionManager = makeTransitionManager( ractive, options.complete ); - - // Render our *root fragment* - ractive.fragment = new DomFragment({ - descriptor: ractive.template, - root: ractive, - owner: ractive, // saves doing `if ( ractive.parent ) { /*...*/ }` later on - parentNode: el - }); - - if ( el ) { - el.appendChild( ractive.fragment.docFrag ); - } - - // transition manager has finished its work - ractive._transitionManager = null; - transitionManager.ready(); -}; -// 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 ( ractive, ref, contextStack ) { - - var keys, lastKey, innerMostContext, contextKeys, parentValue, keypath; - - // Implicit iterators - i.e. {{.}} - are a special case - if ( ref === '.' ) { - return contextStack[ contextStack.length - 1 ]; - } - - // References prepended with '.' are another special case - if ( ref.charAt( 0 ) === '.' ) { - return contextStack[ contextStack.length - 1 ] + ref; - } - - 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 = ractive.get( contextKeys.concat( keys ) ); - - if ( typeof parentValue === 'object' && parentValue !== null && hasOwn.call( parentValue, lastKey ) ) { - keypath = innerMostContext + '.' + ref; - break; - } - } - - if ( !keypath && ractive.get( ref ) !== undefined ) { - keypath = ref; - } - - return keypath; -}; -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 dependant - unregisterDependant( thing ); - } -}; -unregisterDependant = function ( dependant ) { - var deps, i, keep, keys, parentKeypath, map, evaluator, ractive, keypath, priority; - - ractive = dependant.root; - keypath = dependant.keypath; - priority = dependant.priority; - - deps = ractive._deps[ priority ][ keypath ]; - deps.splice( deps.indexOf( dependant ), 1 ); - - // update dependants map - keys = splitKeypath( keypath ); - - while ( keys.length ) { - keys.pop(); - parentKeypath = keys.join( '.' ); - - map = ractive._depsMap[ parentKeypath ]; - - map[ keypath ] -= 1; - - if ( !map[ keypath ] ) { - // remove from parent deps map - map.splice( map.indexOf( keypath ), 1 ); - map[ keypath ] = undefined; - } - - keypath = parentKeypath; - } -}; -proto.link = function ( keypath ) { - var self = this; - - return function ( value ) { - self.set( keypath, value ); - }; -}; -(function ( proto ) { - - var observe, Observer, updateObserver; - - proto.observe = function ( keypath, callback, options ) { - - var observers = [], k; - - if ( typeof keypath === 'object' ) { - options = callback; - - for ( k in keypath ) { - if ( hasOwn.call( keypath, k ) ) { - callback = keypath[k]; - observers[ observers.length ] = observe( this, k, callback, options ); + var __export; + var indexRefPattern = /^\s*:\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/, + arrayMemberPattern = /^[0-9][1-9]*$/, + handlebarsBlockPattern = new RegExp( '^(' + Object.keys( handlebarsBlockCodes ).join( '|' ) + ')\\b' ), + legalReference; + legalReference = /^[a-zA-Z$_0-9]+(?:(\.[a-zA-Z$_0-9]+)|(\[[a-zA-Z$_0-9]+\]))*$/; + __export = function( parser, delimiterType ) { + var start, pos, mustache, type, block, expression, i, remaining, index, delimiters; + start = parser.pos; + mustache = {}; + delimiters = parser[ delimiterType.delimiters ]; + if ( delimiterType.isStatic ) { + mustache.s = true; + } + // Determine mustache type + if ( delimiterType.isTriple ) { + mustache.t = types.TRIPLE; + } else { + // We need to test for expressions before we test for mustache type, because + // an expression that begins '!' looks a lot like a comment + if ( parser.remaining()[ 0 ] === '!' ) { + try { + expression = parser.readExpression(); + // Was it actually an expression, or a comment block in disguise? + parser.allowWhitespace(); + if ( parser.remaining().indexOf( delimiters[ 1 ] ) ) { + expression = null; + } else { + mustache.t = types.INTERPOLATOR; + } + } catch ( err ) {} + if ( !expression ) { + index = parser.remaining().indexOf( delimiters[ 1 ] ); + if ( ~index ) { + parser.pos += index; + } else { + parser.error( 'Expected closing delimiter (\'' + delimiters[ 1 ] + '\')' ); + } + return { + t: types.COMMENT + }; + } } + if ( !expression ) { + type = mustacheType( parser ); + mustache.t = type || types.INTERPOLATOR; + // default + // See if there's an explicit section type e.g. {{#with}}...{{/with}} + if ( type === types.SECTION ) { + if ( block = parser.matchPattern( handlebarsBlockPattern ) ) { + mustache.n = block; + } + parser.allowWhitespace(); + } else if ( type === types.COMMENT || type === types.CLOSING ) { + remaining = parser.remaining(); + index = remaining.indexOf( delimiters[ 1 ] ); + if ( index !== -1 ) { + mustache.r = remaining.substr( 0, index ).split( ' ' )[ 0 ]; + parser.pos += index; + return mustache; + } + } + } } + if ( !expression ) { + // allow whitespace + parser.allowWhitespace(); + // get expression + expression = parser.readExpression(); + // If this is a partial, it may have a context (e.g. `{{>item foo}}`). These + // cases involve a bit of a hack - we want to turn it into the equivalent of + // `{{#with foo}}{{>item}}{{/with}}`, but to get there we temporarily append + // a 'contextPartialExpression' to the mustache, and process the context instead of + // the reference + var temp; + if ( mustache.t === types.PARTIAL && expression && ( temp = parser.readExpression() ) ) { + mustache = { + contextPartialExpression: expression + }; + expression = temp; + } + // With certain valid references that aren't valid expressions, + // e.g. {{1.foo}}, we have a problem: it looks like we've got an + // expression, but the expression didn't consume the entire + // reference. So we need to check that the mustache delimiters + // appear next, unless there's an index reference (i.e. a colon) + remaining = parser.remaining(); + if ( remaining.substr( 0, delimiters[ 1 ].length ) !== delimiters[ 1 ] && remaining.charAt( 0 ) !== ':' ) { + pos = parser.pos; + parser.pos = start; + remaining = parser.remaining(); + index = remaining.indexOf( delimiters[ 1 ] ); + if ( index !== -1 ) { + mustache.r = remaining.substr( 0, index ).trim(); + // Check it's a legal reference + if ( !legalReference.test( mustache.r ) ) { + parser.error( 'Expected a legal Mustache reference' ); + } + parser.pos += index; + return mustache; + } + parser.pos = pos; + } + } + refineExpression( parser, expression, mustache ); + // if there was context, process the expression now and save it for later + if ( mustache.contextPartialExpression ) { + mustache.contextPartialExpression = [ refineExpression( parser, mustache.contextPartialExpression, { + t: types.PARTIAL + } ) ]; + } + // optional index reference + if ( i = parser.matchPattern( indexRefPattern ) ) { + mustache.i = i; + } + return mustache; + }; - return { - cancel: function () { - while ( observers.length ) { - observers.pop().cancel(); + function refineExpression( parser, expression, mustache ) { + var referenceExpression; + if ( expression ) { + while ( expression.t === types.BRACKETED && expression.x ) { + expression = expression.x; + } + // special case - integers should be treated as array members references, + // rather than as expressions in their own right + if ( expression.t === types.REFERENCE ) { + mustache.r = expression.n; + } else { + if ( expression.t === types.NUMBER_LITERAL && arrayMemberPattern.test( expression.v ) ) { + mustache.r = expression.v; + } else if ( referenceExpression = getReferenceExpression( parser, expression ) ) { + mustache.rx = referenceExpression; + } else { + mustache.x = parser.flattenExpression( expression ); } } + return mustache; + } + } + // TODO refactor this! it's bewildering + function getReferenceExpression( parser, expression ) { + var members = [], + refinement; + while ( expression.t === types.MEMBER && expression.r.t === types.REFINEMENT ) { + refinement = expression.r; + if ( refinement.x ) { + if ( refinement.x.t === types.REFERENCE ) { + members.unshift( refinement.x ); + } else { + members.unshift( parser.flattenExpression( refinement.x ) ); + } + } else { + members.unshift( refinement.n ); + } + expression = expression.x; + } + if ( expression.t !== types.REFERENCE ) { + return null; + } + return { + r: expression.n, + m: members }; } + return __export; + }( types, type, handlebarsBlockCodes, legacy ); - return observe( this, keypath, callback, options ); - }; + /* parse/converters/mustache.js */ + var mustache = function( types, delimiterChange, delimiterTypes, mustacheContent, handlebarsBlockCodes ) { - observe = function ( root, keypath, callback, options ) { - var observer; + var __export; + var delimiterChangeToken = { + t: types.DELIMCHANGE, + exclude: true + }; + __export = getMustache; - observer = new Observer( root, keypath, callback, options ); - - if ( !options || options.init !== false ) { - observer.update( true ); + function getMustache( parser ) { + var types; + // If we're inside a <script> or <style> tag, and we're not + // interpolating, bug out + if ( parser.interpolate[ parser.inside ] === false ) { + return null; + } + types = delimiterTypes.slice().sort( function compare( a, b ) { + // Sort in order of descending opening delimiter length (longer first), + // to protect against opening delimiters being substrings of each other + return parser[ b.delimiters ][ 0 ].length - parser[ a.delimiters ][ 0 ].length; + } ); + return function r( type ) { + if ( !type ) { + return null; + } else { + return getMustacheOfType( parser, type ) || r( types.shift() ); + } + }( types.shift() ); } - registerDependant( observer ); - - return { - cancel: function () { - unregisterDependant( observer ); + function getMustacheOfType( parser, delimiterType ) { + var start, mustache, delimiters, children, expectedClose, elseChildren, currentChildren, child; + start = parser.pos; + delimiters = parser[ delimiterType.delimiters ]; + if ( !parser.matchString( delimiters[ 0 ] ) ) { + return null; } - }; - }; - - Observer = function ( root, keypath, callback, options ) { - this.root = root; - this.keypath = keypath; - this.callback = callback; - this.priority = 0; // observers get top priority - - // default to root as context, but allow it to be overridden - this.context = ( options && options.context ? options.context : root ); - }; - - Observer.prototype = { - update: function ( init ) { - var value; - - // TODO create, and use, an internal get method instead - we can skip checks - value = this.root.get( this.keypath, true ); - - if ( !isEqual( value, this.value ) || init ) { - // wrap the callback in a try-catch block, and only throw error in - // debug mode - try { - this.callback.call( this.context, value, this.value ); - } catch ( err ) { - if ( this.root.debug ) { - throw err; + // delimiter change? + if ( mustache = delimiterChange( parser ) ) { + // find closing delimiter or abort... + if ( !parser.matchString( delimiters[ 1 ] ) ) { + return null; + } + // ...then make the switch + parser[ delimiterType.delimiters ] = mustache; + return delimiterChangeToken; + } + parser.allowWhitespace(); + mustache = mustacheContent( parser, delimiterType ); + if ( mustache === null ) { + parser.pos = start; + return null; + } + // allow whitespace before closing delimiter + parser.allowWhitespace(); + if ( !parser.matchString( delimiters[ 1 ] ) ) { + parser.error( 'Expected closing delimiter \'' + delimiters[ 1 ] + '\' after reference' ); + } + if ( mustache.t === types.COMMENT ) { + mustache.exclude = true; + } + if ( mustache.t === types.CLOSING ) { + parser.sectionDepth -= 1; + if ( parser.sectionDepth < 0 ) { + parser.pos = start; + parser.error( 'Attempted to close a section that wasn\'t open' ); + } + } + // partials with context + if ( mustache.contextPartialExpression ) { + mustache.f = mustache.contextPartialExpression; + mustache.t = types.SECTION; + mustache.n = 'with'; + delete mustache.contextPartialExpression; + } else if ( isSection( mustache ) ) { + parser.sectionDepth += 1; + children = []; + currentChildren = children; + expectedClose = mustache.n; + while ( child = parser.read() ) { + if ( child.t === types.CLOSING ) { + if ( expectedClose && child.r !== expectedClose ) { + parser.error( 'Expected {{/' + expectedClose + '}}' ); + } + break; } + // {{else}} tags require special treatment + if ( child.t === types.INTERPOLATOR && child.r === 'else' ) { + // no {{else}} allowed in {{#unless}} + if ( mustache.n === 'unless' ) { + parser.error( '{{else}} not allowed in {{#unless}}' ); + } else { + currentChildren = elseChildren = []; + continue; + } + } + currentChildren.push( child ); } - this.value = value; + if ( children.length ) { + mustache.f = children; + } + if ( elseChildren && elseChildren.length ) { + mustache.l = elseChildren; + if ( mustache.n === 'with' ) { + mustache.n = 'if-with'; + } + } } + if ( parser.includeLinePositions ) { + mustache.p = parser.getLinePos( start ); + } + // Replace block name with code + if ( mustache.n ) { + mustache.n = handlebarsBlockCodes[ mustache.n ]; + } else if ( mustache.t === types.INVERTED ) { + mustache.t = types.SECTION; + mustache.n = types.SECTION_UNLESS; + } + return mustache; } - }; -}( 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 ] = []; + function isSection( mustache ) { + return mustache.t === types.SECTION || mustache.t === types.INVERTED; } - } + return __export; + }( types, delimiterChange, delimiterTypes, content, handlebarsBlockCodes ); - subscribers = this._subs[ eventName ]; + /* parse/converters/comment.js */ + var comment = function( types ) { - 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 ( hasOwn.call( eventName, n ) ) { - listeners[ listeners.length ] = this.on( n, eventName[ n ] ); + var OPEN_COMMENT = '<!--', + CLOSE_COMMENT = '-->'; + return function( parser ) { + var start, content, remaining, endIndex, comment; + start = parser.pos; + if ( !parser.matchString( OPEN_COMMENT ) ) { + return null; } - } - - return { - cancel: function () { - while ( listeners.length ) { - listeners.pop().cancel(); - } + remaining = parser.remaining(); + endIndex = remaining.indexOf( CLOSE_COMMENT ); + if ( endIndex === -1 ) { + parser.error( 'Illegal HTML - expected closing comment sequence (\'-->\')' ); } + content = remaining.substr( 0, endIndex ); + parser.pos += endIndex + 3; + comment = { + t: types.COMMENT, + c: content + }; + if ( parser.includeLinePositions ) { + comment.p = parser.getLinePos( start ); + } + return comment; }; - } + }( types ); - if ( !this._subs[ eventName ] ) { - this._subs[ eventName ] = [ callback ]; - } else { - this._subs[ eventName ].push( callback ); - } + /* config/voidElementNames.js */ + var voidElementNames = function() { - return { - cancel: function () { - self.off( eventName, callback ); + var voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; + return voidElementNames; + }(); + + /* parse/converters/utils/getLowestIndex.js */ + var getLowestIndex = function( haystack, needles ) { + var i, index, lowest; + i = needles.length; + while ( i-- ) { + index = haystack.indexOf( needles[ i ] ); + // short circuit + if ( !index ) { + return 0; + } + if ( index === -1 ) { + continue; + } + if ( !lowest || index < lowest ) { + lowest = index; + } } + return lowest || -1; }; -}; -proto.renderHTML = function () { - return this.fragment.toString(); -}; -proto.requestFullscreen = function () { - Ractive.requestFullscreen( this.el ); -}; -(function ( proto ) { - var set, attemptKeypathResolution; + /* shared/decodeCharacterReferences.js */ + var decodeCharacterReferences = function() { - proto.set = function ( keypath, value, complete ) { - var notificationQueue, upstreamQueue, k, normalised, keys, previous, previousTransitionManager, transitionManager; - - upstreamQueue = [ '' ]; // empty string will always be an upstream keypath - notificationQueue = []; - - if ( isObject( keypath ) ) { - complete = value; + var __export; + var htmlEntities, controlCharacters, entityPattern; + htmlEntities = { + quot: 34, + amp: 38, + apos: 39, + lt: 60, + gt: 62, + nbsp: 160, + iexcl: 161, + cent: 162, + pound: 163, + curren: 164, + yen: 165, + brvbar: 166, + sect: 167, + uml: 168, + copy: 169, + ordf: 170, + laquo: 171, + not: 172, + shy: 173, + reg: 174, + macr: 175, + deg: 176, + plusmn: 177, + sup2: 178, + sup3: 179, + acute: 180, + micro: 181, + para: 182, + middot: 183, + cedil: 184, + sup1: 185, + ordm: 186, + raquo: 187, + frac14: 188, + frac12: 189, + frac34: 190, + iquest: 191, + Agrave: 192, + Aacute: 193, + Acirc: 194, + Atilde: 195, + Auml: 196, + Aring: 197, + AElig: 198, + Ccedil: 199, + Egrave: 200, + Eacute: 201, + Ecirc: 202, + Euml: 203, + Igrave: 204, + Iacute: 205, + Icirc: 206, + Iuml: 207, + ETH: 208, + Ntilde: 209, + Ograve: 210, + Oacute: 211, + Ocirc: 212, + Otilde: 213, + Ouml: 214, + times: 215, + Oslash: 216, + Ugrave: 217, + Uacute: 218, + Ucirc: 219, + Uuml: 220, + Yacute: 221, + THORN: 222, + szlig: 223, + agrave: 224, + aacute: 225, + acirc: 226, + atilde: 227, + auml: 228, + aring: 229, + aelig: 230, + ccedil: 231, + egrave: 232, + eacute: 233, + ecirc: 234, + euml: 235, + igrave: 236, + iacute: 237, + icirc: 238, + iuml: 239, + eth: 240, + ntilde: 241, + ograve: 242, + oacute: 243, + ocirc: 244, + otilde: 245, + ouml: 246, + divide: 247, + oslash: 248, + ugrave: 249, + uacute: 250, + ucirc: 251, + uuml: 252, + yacute: 253, + thorn: 254, + yuml: 255, + OElig: 338, + oelig: 339, + Scaron: 352, + scaron: 353, + Yuml: 376, + fnof: 402, + circ: 710, + tilde: 732, + Alpha: 913, + Beta: 914, + Gamma: 915, + Delta: 916, + Epsilon: 917, + Zeta: 918, + Eta: 919, + Theta: 920, + Iota: 921, + Kappa: 922, + Lambda: 923, + Mu: 924, + Nu: 925, + Xi: 926, + Omicron: 927, + Pi: 928, + Rho: 929, + Sigma: 931, + Tau: 932, + Upsilon: 933, + Phi: 934, + Chi: 935, + Psi: 936, + Omega: 937, + alpha: 945, + beta: 946, + gamma: 947, + delta: 948, + epsilon: 949, + zeta: 950, + eta: 951, + theta: 952, + iota: 953, + kappa: 954, + lambda: 955, + mu: 956, + nu: 957, + xi: 958, + omicron: 959, + pi: 960, + rho: 961, + sigmaf: 962, + sigma: 963, + tau: 964, + upsilon: 965, + phi: 966, + chi: 967, + psi: 968, + omega: 969, + thetasym: 977, + upsih: 978, + piv: 982, + ensp: 8194, + emsp: 8195, + thinsp: 8201, + zwnj: 8204, + zwj: 8205, + lrm: 8206, + rlm: 8207, + ndash: 8211, + mdash: 8212, + lsquo: 8216, + rsquo: 8217, + sbquo: 8218, + ldquo: 8220, + rdquo: 8221, + bdquo: 8222, + dagger: 8224, + Dagger: 8225, + bull: 8226, + hellip: 8230, + permil: 8240, + prime: 8242, + Prime: 8243, + lsaquo: 8249, + rsaquo: 8250, + oline: 8254, + frasl: 8260, + euro: 8364, + image: 8465, + weierp: 8472, + real: 8476, + trade: 8482, + alefsym: 8501, + larr: 8592, + uarr: 8593, + rarr: 8594, + darr: 8595, + harr: 8596, + crarr: 8629, + lArr: 8656, + uArr: 8657, + rArr: 8658, + dArr: 8659, + hArr: 8660, + forall: 8704, + part: 8706, + exist: 8707, + empty: 8709, + nabla: 8711, + isin: 8712, + notin: 8713, + ni: 8715, + prod: 8719, + sum: 8721, + minus: 8722, + lowast: 8727, + radic: 8730, + prop: 8733, + infin: 8734, + ang: 8736, + and: 8743, + or: 8744, + cap: 8745, + cup: 8746, + 'int': 8747, + there4: 8756, + sim: 8764, + cong: 8773, + asymp: 8776, + ne: 8800, + equiv: 8801, + le: 8804, + ge: 8805, + sub: 8834, + sup: 8835, + nsub: 8836, + sube: 8838, + supe: 8839, + oplus: 8853, + otimes: 8855, + perp: 8869, + sdot: 8901, + lceil: 8968, + rceil: 8969, + lfloor: 8970, + rfloor: 8971, + lang: 9001, + rang: 9002, + loz: 9674, + spades: 9824, + clubs: 9827, + hearts: 9829, + diams: 9830 + }; + controlCharacters = [ + 8364, + 129, + 8218, + 402, + 8222, + 8230, + 8224, + 8225, + 710, + 8240, + 352, + 8249, + 338, + 141, + 381, + 143, + 144, + 8216, + 8217, + 8220, + 8221, + 8226, + 8211, + 8212, + 732, + 8482, + 353, + 8250, + 339, + 157, + 382, + 376 + ]; + entityPattern = new RegExp( '&(#?(?:x[\\w\\d]+|\\d+|' + Object.keys( htmlEntities ).join( '|' ) + '));?', 'g' ); + __export = function decodeCharacterReferences( html ) { + return html.replace( entityPattern, function( match, entity ) { + var code; + // Handle named entities + if ( entity[ 0 ] !== '#' ) { + code = htmlEntities[ entity ]; + } else if ( entity[ 1 ] === 'x' ) { + code = parseInt( entity.substring( 2 ), 16 ); + } else { + code = parseInt( entity.substring( 1 ), 10 ); + } + if ( !code ) { + return match; + } + return String.fromCharCode( validateCode( code ) ); + } ); + }; + // some code points are verboten. If we were inserting HTML, the browser would replace the illegal + // code points with alternatives in some cases - since we're bypassing that mechanism, we need + // to replace them ourselves + // + // Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters + function validateCode( code ) { + if ( !code ) { + return 65533; + } + // line feed becomes generic whitespace + if ( code === 10 ) { + return 32; + } + // ASCII range. (Why someone would use HTML entities for ASCII characters I don't know, but...) + if ( code < 128 ) { + return code; + } + // code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need + // to correct the mistake or we'll end up with missing € signs and so on + if ( code <= 159 ) { + return controlCharacters[ code - 128 ]; + } + // basic multilingual plane + if ( code < 55296 ) { + return code; + } + // UTF-16 surrogate halves + if ( code <= 57343 ) { + return 65533; + } + // rest of the basic multilingual plane + if ( code <= 65535 ) { + return code; + } + return 65533; } + return __export; + }( legacy ); - // manage transitions - previousTransitionManager = this._transitionManager; - this._transitionManager = transitionManager = makeTransitionManager( this, complete ); + /* parse/converters/text.js */ + var text = function( getLowestIndex, decodeCharacterReferences ) { - // setting multiple values in one go - if ( isObject( keypath ) ) { - for ( k in keypath ) { - if ( hasOwn.call( keypath, k ) ) { - keys = splitKeypath( k ); - normalised = keys.join( '.' ); - value = keypath[k]; - - set( this, normalised, keys, value, notificationQueue, upstreamQueue ); + return function( parser ) { + var index, remaining, disallowed, barrier; + remaining = parser.remaining(); + barrier = parser.inside ? '</' + parser.inside : '<'; + if ( parser.inside && !parser.interpolate[ parser.inside ] ) { + index = remaining.indexOf( barrier ); + } else { + disallowed = [ + parser.delimiters[ 0 ], + parser.tripleDelimiters[ 0 ], + parser.staticDelimiters[ 0 ], + parser.staticTripleDelimiters[ 0 ] + ]; + // http://developers.whatwg.org/syntax.html#syntax-attributes + if ( parser.inAttribute === true ) { + // we're inside an unquoted attribute value + disallowed.push( '"', '\'', '=', '<', '>', '`' ); + } else if ( parser.inAttribute ) { + // quoted attribute value + disallowed.push( parser.inAttribute ); + } else { + disallowed.push( barrier ); } + index = getLowestIndex( remaining, disallowed ); } - } + if ( !index ) { + return null; + } + if ( index === -1 ) { + index = remaining.length; + } + parser.pos += index; + return parser.inside ? remaining.substr( 0, index ) : decodeCharacterReferences( remaining.substr( 0, index ) ); + }; + }( getLowestIndex, decodeCharacterReferences ); - // setting a single value - else { - keys = splitKeypath( keypath ); - normalised = keys.join( '.' ); + /* parse/converters/element/closingTag.js */ + var closingTag = function( types ) { - set( this, normalised, keys, value, notificationQueue, upstreamQueue ); - } + var closingTagPattern = /^([a-zA-Z]{1,}:?[a-zA-Z0-9\-]*)\s*\>/; + return function( parser ) { + var tag; + // are we looking at a closing tag? + if ( !parser.matchString( '</' ) ) { + return null; + } + if ( tag = parser.matchPattern( closingTagPattern ) ) { + return { + t: types.CLOSING_TAG, + e: tag + }; + } + // We have an illegal closing tag, report it + parser.pos -= 2; + parser.error( 'Illegal closing tag' ); + }; + }( types ); - // if anything has changed, attempt to resolve any unresolved keypaths... - if ( notificationQueue.length && this._pendingResolution.length ) { - attemptKeypathResolution( this ); - } + /* parse/converters/element/attribute.js */ + var attribute = function( getLowestIndex, getMustache, decodeCharacterReferences ) { - // ...and notify dependants - if ( upstreamQueue.length ) { - notifyMultipleDependants( this, upstreamQueue, true ); - } + var __export; + var attributeNamePattern = /^[^\s"'>\/=]+/, + unquotedAttributeValueTextPattern = /^[^\s"'=<>`]+/; + __export = getAttribute; - if ( notificationQueue.length ) { - notifyMultipleDependants( this, notificationQueue ); + function getAttribute( parser ) { + var attr, name, value; + parser.allowWhitespace(); + name = parser.matchPattern( attributeNamePattern ); + if ( !name ) { + return null; + } + attr = { + name: name + }; + value = getAttributeValue( parser ); + if ( value ) { + attr.value = value; + } + return attr; } - // 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 - processDeferredUpdates( this ); - - // transition manager has finished its work - this._transitionManager = previousTransitionManager; - transitionManager.ready(); - - // fire event - if ( !this.setting ) { - this.setting = true; // short-circuit any potential infinite loops - - if ( typeof keypath === 'object' ) { - this.fire( 'set', keypath ); - } else { - this.fire( 'set', keypath, value ); + function getAttributeValue( parser ) { + var start, valueStart, startDepth, value; + start = parser.pos; + parser.allowWhitespace(); + if ( !parser.matchString( '=' ) ) { + parser.pos = start; + return null; } - - this.setting = false; + parser.allowWhitespace(); + valueStart = parser.pos; + startDepth = parser.sectionDepth; + value = getQuotedAttributeValue( parser, '\'' ) || getQuotedAttributeValue( parser, '"' ) || getUnquotedAttributeValue( parser ); + if ( parser.sectionDepth !== startDepth ) { + parser.pos = valueStart; + parser.error( 'An attribute value must contain as many opening section tags as closing section tags' ); + } + if ( value === null ) { + parser.pos = start; + return null; + } + if ( !value.length ) { + return null; + } + if ( value.length === 1 && typeof value[ 0 ] === 'string' ) { + return decodeCharacterReferences( value[ 0 ] ); + } + return value; } - return this; - }; - - - set = function ( root, keypath, keys, value, queue, upstreamQueue ) { - var previous, key, obj, keysClone, accumulated, keypathToClear; - - keysClone = keys.slice(); - accumulated = []; - - previous = root.get( keypath ); - - // update the model, if necessary - if ( previous !== value ) { - // update data - obj = root.data; - while ( keys.length > 1 ) { - key = accumulated[ accumulated.length ] = 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 ] ) { - - // if we're creating a new branch, we may need to clear the upstream - // keypath - if ( !keypathToClear ) { - keypathToClear = accumulated.join( '.' ); - } - - obj[ key ] = ( /^\s*[0-9]+\s*$/.test( keys[0] ) ? [] : {} ); - } - - obj = obj[ key ]; + function getUnquotedAttributeValueToken( parser ) { + var start, text, haystack, needles, index; + start = parser.pos; + text = parser.matchPattern( unquotedAttributeValueTextPattern ); + if ( !text ) { + return null; } - - key = keys[0]; - - obj[ key ] = value; + haystack = text; + needles = [ + parser.delimiters[ 0 ], + parser.tripleDelimiters[ 0 ], + parser.staticDelimiters[ 0 ], + parser.staticTripleDelimiters[ 0 ] + ]; + if ( ( index = getLowestIndex( haystack, needles ) ) !== -1 ) { + text = text.substr( 0, index ); + parser.pos = start + text.length; + } + return text; } - else { - // if value is a primitive, we don't need to do anything else - if ( typeof value !== 'object' ) { - return; + function getUnquotedAttributeValue( parser ) { + var tokens, token; + parser.inAttribute = true; + tokens = []; + token = getMustache( parser ) || getUnquotedAttributeValueToken( parser ); + while ( token !== null ) { + tokens.push( token ); + token = getMustache( parser ) || getUnquotedAttributeValueToken( parser ); } + if ( !tokens.length ) { + return null; + } + parser.inAttribute = false; + return tokens; } - - // Clear cache - clearCache( root, keypathToClear || keypath ); - - // add this keypath to the notification queue - queue[ queue.length ] = keypath; - - - // add upstream keypaths to the upstream notification queue - while ( keysClone.length > 1 ) { - keysClone.pop(); - keypath = keysClone.join( '.' ); - - if ( upstreamQueue.indexOf( keypath ) === -1 ) { - upstreamQueue[ upstreamQueue.length ] = keypath; + function getQuotedAttributeValue( parser, quoteMark ) { + var start, tokens, token; + start = parser.pos; + if ( !parser.matchString( quoteMark ) ) { + return null; } + parser.inAttribute = quoteMark; + tokens = []; + token = getMustache( parser ) || getQuotedStringToken( parser, quoteMark ); + while ( token !== null ) { + tokens.push( token ); + token = getMustache( parser ) || getQuotedStringToken( parser, quoteMark ); + } + if ( !parser.matchString( quoteMark ) ) { + parser.pos = start; + return null; + } + parser.inAttribute = false; + return tokens; } - - }; - 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; + function getQuotedStringToken( parser, quoteMark ) { + var start, index, haystack, needles; + start = parser.pos; + haystack = parser.remaining(); + needles = [ + quoteMark, + parser.delimiters[ 0 ], + parser.tripleDelimiters[ 0 ], + parser.staticDelimiters[ 0 ], + parser.staticTripleDelimiters[ 0 ] + ]; + index = getLowestIndex( haystack, needles ); + if ( index === -1 ) { + parser.error( 'Quoted attribute value must have a closing quote' ); } + if ( !index ) { + return null; + } + parser.pos += index; + return haystack.substr( 0, index ); } - }; + return __export; + }( getLowestIndex, mustache, decodeCharacterReferences ); -}( proto )); -// Teardown. This goes through the root fragment and all its children, removing observers -// and generally cleaning up after itself -proto.teardown = function ( complete ) { - var keypath, transitionManager, previousTransitionManager; + /* utils/parseJSON.js */ + var parseJSON = function( Parser, getStringLiteral, getKey ) { - this.fire( 'teardown' ); - - previousTransitionManager = this._transitionManager; - this._transitionManager = transitionManager = makeTransitionManager( this, complete ); - - this.fragment.teardown( true ); - - // Cancel any animations in progress - while ( this._animations[0] ) { - this._animations[0].stop(); // it will remove itself from the index - } - - // Clear cache - this has the side-effect of unregistering keypaths from modified arrays. - for ( keypath in this._cache ) { - clearCache( this, keypath ); - } - - // Teardown any bindings - while ( this._bound.length ) { - this.unbind( this._bound.pop() ); - } - - // transition manager has finished its work - this._transitionManager = previousTransitionManager; - transitionManager.ready(); -}; -proto.toggleFullscreen = function () { - if ( Ractive.isFullscreen( this.el ) ) { - this.cancelFullscreen(); - } else { - this.requestFullscreen(); - } -}; -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, complete ) { - var transitionManager, previousTransitionManager; - - if ( typeof keypath === 'function' ) { - complete = keypath; - } - - // manage transitions - previousTransitionManager = this._transitionManager; - this._transitionManager = transitionManager = makeTransitionManager( this, complete ); - - clearCache( this, keypath || '' ); - notifyDependants( this, keypath || '' ); - - processDeferredUpdates( this ); - - // transition manager has finished its work - this._transitionManager = previousTransitionManager; - transitionManager.ready(); - - if ( typeof keypath === 'string' ) { - this.fire( 'update', keypath ); - } else { - this.fire( 'update' ); - } - - 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; - } + var JsonParser, specials, specialsPattern, numberPattern, placeholderPattern, placeholderAtStartPattern, onlyWhitespace; + specials = { + 'true': true, + 'false': false, + 'undefined': undefined, + 'null': null + }; + specialsPattern = new RegExp( '^(?:' + Object.keys( specials ).join( '|' ) + ')' ); + numberPattern = /^(?:[+-]?)(?:(?:(?:0|[1-9]\d*)?\.\d+)|(?:(?:0|[1-9]\d*)\.)|(?:0|[1-9]\d*))(?:[eE][+-]?\d+)?/; + placeholderPattern = /\$\{([^\}]+)\}/g; + placeholderAtStartPattern = /^\$\{([^\}]+)\}/; + onlyWhitespace = /^\s*$/; + JsonParser = Parser.extend( { + init: function( str, options ) { + this.values = options.values; + this.allowWhitespace(); + }, + postProcess: function( result ) { + if ( result.length !== 1 || !onlyWhitespace.test( this.leftover ) ) { + return null; + } + return { + value: result[ 0 ].v }; + }, + converters: [ - setModel = function ( keypath, value ) { - if ( !settingView ) { - settingModel = true; - model.set( keypath, value ); - settingModel = false; + function getPlaceholder( parser ) { + var placeholder; + if ( !parser.values ) { + return null; } - }; - } - - else { - prefix = function ( attrs ) { - var attr, result; - + placeholder = parser.matchPattern( placeholderAtStartPattern ); + if ( placeholder && parser.values.hasOwnProperty( placeholder ) ) { + return { + v: parser.values[ placeholder ] + }; + } + }, + function getSpecial( parser ) { + var special; + if ( special = parser.matchPattern( specialsPattern ) ) { + return { + v: specials[ special ] + }; + } + }, + function getNumber( parser ) { + var number; + if ( number = parser.matchPattern( numberPattern ) ) { + return { + v: +number + }; + } + }, + function getString( parser ) { + var stringLiteral = getStringLiteral( parser ), + values; + if ( stringLiteral && ( values = parser.values ) ) { + return { + v: stringLiteral.v.replace( placeholderPattern, function( match, $1 ) { + return $1 in values ? values[ $1 ] : $1; + } ) + }; + } + return stringLiteral; + }, + function getObject( parser ) { + var result, pair; + if ( !parser.matchString( '{' ) ) { + return null; + } result = {}; - - for ( attr in attrs ) { - if ( hasOwn.call( attrs, attr ) ) { - result[ path + attr ] = attrs[ attr ]; + parser.allowWhitespace(); + if ( parser.matchString( '}' ) ) { + return { + v: result + }; + } + while ( pair = getKeyValuePair( parser ) ) { + result[ pair.key ] = pair.value; + parser.allowWhitespace(); + if ( parser.matchString( '}' ) ) { + return { + v: result + }; } + if ( !parser.matchString( ',' ) ) { + return null; + } } - - return result; - }; - - setView = function ( model ) { - if ( !settingModel ) { - settingView = true; - view.set( prefix( model.changed ) ); - settingView = false; + return null; + }, + function getArray( parser ) { + var result, valueToken; + if ( !parser.matchString( '[' ) ) { + return null; } - }; - - setModel = function ( keypath, value ) { - if ( !settingView ) { - if ( pathMatcher.test( keypath ) ) { - settingModel = true; - model.set( keypath.substring( pathLength ), value ); - settingModel = false; + result = []; + parser.allowWhitespace(); + if ( parser.matchString( ']' ) ) { + return { + v: result + }; + } + while ( valueToken = parser.read() ) { + result.push( valueToken.v ); + parser.allowWhitespace(); + if ( parser.matchString( ']' ) ) { + return { + v: result + }; } + if ( !parser.matchString( ',' ) ) { + return null; + } + parser.allowWhitespace(); } - }; - } + return null; + } + ] + } ); - 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 ); + function getKeyValuePair( parser ) { + var key, valueToken, pair; + parser.allowWhitespace(); + key = getKey( parser ); + if ( !key ) { + return null; + } + pair = { + key: key + }; + parser.allowWhitespace(); + if ( !parser.matchString( ':' ) ) { + return null; + } + parser.allowWhitespace(); + valueToken = parser.read(); + if ( !valueToken ) { + return null; + } + pair.value = valueToken.v; + return pair; } - }; -}; -adaptors.statesman = function ( model, path ) { - var settingModel, settingView, setModel, setView, pathMatcher, pathLength, prefix; + return function( str, values ) { + var parser = new JsonParser( str, { + values: values + } ); + return parser.result; + }; + }( Parser, stringLiteral, key ); - if ( path ) { - path += '.'; - pathMatcher = new RegExp( '^' + path.replace( /\./g, '\\.' ) ); - pathLength = path.length; + /* parse/converters/element/processDirective.js */ + var processDirective = function( Parser, conditional, flattenExpression, parseJSON ) { - prefix = function ( attrs ) { - var attr, result; - - if ( !attrs ) { - return; + var methodCallPattern = /^([a-zA-Z_$][a-zA-Z_$0-9]*)\(/, + ExpressionParser; + ExpressionParser = Parser.extend( { + converters: [ conditional ] + } ); + // TODO clean this up, it's shocking + return function( tokens ) { + var result, match, parser, args, token, colonIndex, directiveName, directiveArgs, parsed; + if ( typeof tokens === 'string' ) { + if ( match = methodCallPattern.exec( tokens ) ) { + result = { + m: match[ 1 ] + }; + args = '[' + tokens.slice( result.m.length + 1, -1 ) + ']'; + parser = new ExpressionParser( args ); + result.a = flattenExpression( parser.result[ 0 ] ); + return result; + } + if ( tokens.indexOf( ':' ) === -1 ) { + return tokens.trim(); + } + tokens = [ tokens ]; } - result = {}; - - for ( attr in attrs ) { - if ( hasOwn.call( attrs, attr ) ) { - result[ path + attr ] = attrs[ attr ]; + directiveName = []; + directiveArgs = []; + if ( tokens ) { + while ( tokens.length ) { + token = tokens.shift(); + if ( typeof token === 'string' ) { + colonIndex = token.indexOf( ':' ); + if ( colonIndex === -1 ) { + directiveName.push( token ); + } else { + // is the colon the first character? + if ( colonIndex ) { + // no + directiveName.push( token.substr( 0, colonIndex ) ); + } + // if there is anything after the colon in this token, treat + // it as the first token of the directiveArgs fragment + if ( token.length > colonIndex + 1 ) { + directiveArgs[ 0 ] = token.substring( colonIndex + 1 ); + } + break; + } + } else { + directiveName.push( token ); + } } + directiveArgs = directiveArgs.concat( tokens ); } - + if ( !directiveName.length ) { + result = ''; + } else if ( directiveArgs.length || typeof directiveName !== 'string' ) { + result = { + // TODO is this really necessary? just use the array + n: directiveName.length === 1 && typeof directiveName[ 0 ] === 'string' ? directiveName[ 0 ] : directiveName + }; + if ( directiveArgs.length === 1 && typeof directiveArgs[ 0 ] === 'string' ) { + parsed = parseJSON( '[' + directiveArgs[ 0 ] + ']' ); + result.a = parsed ? parsed.value : directiveArgs[ 0 ].trim(); + } else { + result.d = directiveArgs; + } + } else { + result = directiveName; + } return result; }; - } + }( Parser, conditional, flattenExpression, parseJSON ); + /* parse/converters/element.js */ + var element = function( types, voidElementNames, getMustache, getComment, getText, getClosingTag, getAttribute, processDirective ) { - return { - init: function ( view ) { - - var data; + var __export; + var tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/, + validTagNameFollower = /^[\s\n\/>]/, + onPattern = /^on/, + proxyEventPattern = /^on-([a-zA-Z\\*\\.$_][a-zA-Z\\*\\.$_0-9\-]+)$/, + reservedEventNames = /^(?:change|reset|teardown|update|construct|config|init|render|unrender|detach|insert)$/, + directives = { + 'intro-outro': 't0', + intro: 't1', + outro: 't2', + decorator: 'o' + }, + exclude = { + exclude: true + }, + converters, disallowedContents; + // Different set of converters, because this time we're looking for closing tags + converters = [ + getMustache, + getComment, + getElement, + getText, + getClosingTag + ]; + // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission + disallowedContents = { + li: [ 'li' ], + dt: [ + 'dt', + 'dd' + ], + dd: [ + 'dt', + 'dd' + ], + p: 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ), + rt: [ + 'rt', + 'rp' + ], + rp: [ + 'rt', + 'rp' + ], + optgroup: [ 'optgroup' ], + option: [ + 'option', + 'optgroup' + ], + thead: [ + 'tbody', + 'tfoot' + ], + tbody: [ + 'tbody', + 'tfoot' + ], + tfoot: [ 'tbody' ], + tr: [ + 'tr', + 'tbody' + ], + td: [ + 'td', + 'th', + 'tr' + ], + th: [ + 'td', + 'th', + 'tr' + ] + }; + __export = getElement; - // if no path specified... - if ( !path ) { - setView = function ( change ) { - if ( !settingModel ) { - settingView = true; - - view.set( change ); - - settingView = false; - } - }; - - if ( view.twoway ) { - setModel = function ( keypath, value ) { - if ( !settingView ) { - settingModel = true; - model.set( keypath, value ); - settingModel = false; + function getElement( parser ) { + var start, element, lowerCaseName, directiveName, match, addProxyEvent, attribute, directive, selfClosing, children, child; + start = parser.pos; + if ( parser.inside || parser.inAttribute ) { + return null; + } + if ( !parser.matchString( '<' ) ) { + return null; + } + // if this is a closing tag, abort straight away + if ( parser.nextChar() === '/' ) { + return null; + } + element = { + t: types.ELEMENT + }; + if ( parser.includeLinePositions ) { + element.p = parser.getLinePos( start ); + } + if ( parser.matchString( '!' ) ) { + element.y = 1; + } + // element name + element.e = parser.matchPattern( tagNamePattern ); + if ( !element.e ) { + return null; + } + // next character must be whitespace, closing solidus or '>' + if ( !validTagNameFollower.test( parser.nextChar() ) ) { + parser.error( 'Illegal tag name' ); + } + addProxyEvent = function( name, directive ) { + var directiveName = directive.n || directive; + if ( reservedEventNames.test( directiveName ) ) { + parser.pos -= directiveName.length; + parser.error( 'Cannot use reserved event names (change, reset, teardown, update, construct, config, init, render, unrender, detach, insert)' ); + } + element.v[ name ] = directive; + }; + parser.allowWhitespace(); + // directives and attributes + while ( attribute = getMustache( parser ) || getAttribute( parser ) ) { + // regular attributes + if ( attribute.name ) { + // intro, outro, decorator + if ( directiveName = directives[ attribute.name ] ) { + element[ directiveName ] = processDirective( attribute.value ); + } else if ( match = proxyEventPattern.exec( attribute.name ) ) { + if ( !element.v ) + element.v = {}; + directive = processDirective( attribute.value ); + addProxyEvent( match[ 1 ], directive ); + } else { + if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { + if ( !element.a ) + element.a = {}; + element.a[ attribute.name ] = attribute.value || 0; } - }; + } + } else { + if ( !element.m ) + element.m = []; + element.m.push( attribute ); } + parser.allowWhitespace(); } - - else { - setView = function ( change ) { - if ( !settingModel ) { - settingView = true; - - change = prefix( change ); - view.set( change ); - - settingView = false; + // allow whitespace before closing solidus + parser.allowWhitespace(); + // self-closing solidus? + if ( parser.matchString( '/' ) ) { + selfClosing = true; + } + // closing angle bracket + if ( !parser.matchString( '>' ) ) { + return null; + } + lowerCaseName = element.e.toLowerCase(); + if ( !selfClosing && !voidElementNames.test( element.e ) ) { + // Special case - if we open a script element, further tags should + // be ignored unless they're a closing script element + if ( lowerCaseName === 'script' || lowerCaseName === 'style' ) { + parser.inside = lowerCaseName; + } + children = []; + while ( canContain( lowerCaseName, parser.remaining() ) && ( child = parser.read( converters ) ) ) { + // Special case - closing section tag + if ( child.t === types.CLOSING ) { + break; } - }; - - if ( view.twoway ) { - setModel = function ( keypath, value ) { - if ( !settingView ) { - if ( pathMatcher.test( keypath ) ) { - settingModel = true; - model.set( keypath.substring( pathLength ), value ); - settingModel = false; - } - } - }; + if ( child.t === types.CLOSING_TAG ) { + break; + } + children.push( child ); } + if ( children.length ) { + element.f = children; + } } - - model.on( 'change', setView ); - - if ( view.twoway ) { - view.on( 'set', setModel ); + parser.inside = null; + if ( parser.sanitizeElements && parser.sanitizeElements.indexOf( lowerCaseName ) !== -1 ) { + return exclude; } - - // initialise - data = ( path ? prefix( model.get() ) : model.get() ); + return element; + } - if ( data ) { - view.set( path ? prefix( model.get() ) : model.get() ); + function canContain( name, remaining ) { + var match, disallowed; + match = /^<([a-zA-Z][a-zA-Z0-9]*)/.exec( remaining ); + disallowed = disallowedContents[ name ]; + if ( !match || !disallowed ) { + return true; } - }, - - teardown: function ( view ) { - model.off( 'change', setView ); - view.off( 'set', setModel ); + return !~disallowed.indexOf( match[ 1 ].toLowerCase() ); } - }; -}; -// These are a subset of the easing equations found at -// https://raw.github.com/danro/easing-js - license info -// follows: + return __export; + }( types, voidElementNames, mustache, comment, text, closingTag, attribute, processDirective ); -// -------------------------------------------------- -// 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 -// -------------------------------------------------- + /* parse/utils/trimWhitespace.js */ + var trimWhitespace = function() { -// 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.hover = function ( node, fire ) { - var mouseoverHandler, mouseoutHandler; - - mouseoverHandler = function ( event ) { - fire({ - node: node, - original: event, - hover: true - }); - }; - - mouseoutHandler = function ( event ) { - fire({ - node: node, - original: event, - hover: false - }); - }; - - node.addEventListener( 'mouseover', mouseoverHandler ); - node.addEventListener( 'mouseout', mouseoutHandler ); - - return { - teardown: function () { - node.removeEventListener( 'mouseover', mouseoverHandler ); - node.removeEventListener( 'mouseout', mouseoutHandler ); - } - }; -}; -(function () { - - var makeKeyDefinition = function ( code ) { - return function ( node, fire ) { - var keydownHandler; - - node.addEventListener( 'keydown', keydownHandler = function ( event ) { - var which = event.which || event.keyCode; - - if ( which === code ) { - event.preventDefault(); - - fire({ - node: node, - original: event - }); + var leadingWhitespace = /^[ \t\f\r\n]+/, + trailingWhitespace = /[ \t\f\r\n]+$/; + return function( items, leading, trailing ) { + var item; + if ( leading ) { + item = items[ 0 ]; + if ( typeof item === 'string' ) { + item = item.replace( leadingWhitespace, '' ); + if ( !item ) { + items.shift(); + } else { + items[ 0 ] = item; + } } - }); - - return { - teardown: function () { - node.removeEventListener( keydownHandler ); + } + if ( trailing ) { + item = items[ items.length - 1 ]; + if ( typeof item === 'string' ) { + item = item.replace( trailingWhitespace, '' ); + if ( !item ) { + items.pop(); + } else { + items[ items.length - 1 ] = item; + } } - }; + } }; - }; + }(); - eventDefinitions.enter = makeKeyDefinition( 13 ); - eventDefinitions.tab = makeKeyDefinition( 9 ); - eventDefinitions.escape = makeKeyDefinition( 27 ); - eventDefinitions.space = makeKeyDefinition( 32 ); + /* parse/utils/stripStandalones.js */ + var stripStandalones = function( types ) { -}()); -eventDefinitions.tap = function ( node, 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 currentTarget, x, y, up, move, cancel; - - x = event.clientX; - y = event.clientY; - currentTarget = this; - - up = function ( event ) { - fire({ - node: currentTarget, - original: event - }); - - cancel(); - }; - - move = function ( event ) { - if ( ( Math.abs( event.clientX - x ) >= distanceThreshold ) || ( Math.abs( event.clientY - y ) >= distanceThreshold ) ) { - cancel(); + var __export; + var leadingLinebreak = /^\s*\r?\n/, + trailingLinebreak = /\r?\n\s*$/; + __export = function( items ) { + var i, current, backOne, backTwo, lastSectionItem; + for ( i = 1; i < items.length; i += 1 ) { + current = items[ i ]; + backOne = items[ i - 1 ]; + backTwo = items[ i - 2 ]; + // if we're at the end of a [text][comment][text] sequence... + if ( isString( current ) && isComment( backOne ) && isString( backTwo ) ) { + // ... and the comment is a standalone (i.e. line breaks either side)... + if ( trailingLinebreak.test( backTwo ) && leadingLinebreak.test( current ) ) { + // ... then we want to remove the whitespace after the first line break + items[ i - 2 ] = backTwo.replace( trailingLinebreak, '\n' ); + // and the leading line break of the second text token + items[ i ] = current.replace( leadingLinebreak, '' ); + } + } + // if the current item is a section, and it is preceded by a linebreak, and + // its first item is a linebreak... + if ( isSection( current ) && isString( backOne ) ) { + if ( trailingLinebreak.test( backOne ) && isString( current.f[ 0 ] ) && leadingLinebreak.test( current.f[ 0 ] ) ) { + items[ i - 1 ] = backOne.replace( trailingLinebreak, '\n' ); + current.f[ 0 ] = current.f[ 0 ].replace( leadingLinebreak, '' ); + } + } + // if the last item was a section, and it is followed by a linebreak, and + // its last item is a linebreak... + if ( isString( current ) && isSection( backOne ) ) { + lastSectionItem = backOne.f[ backOne.f.length - 1 ]; + if ( isString( lastSectionItem ) && trailingLinebreak.test( lastSectionItem ) && leadingLinebreak.test( current ) ) { + backOne.f[ backOne.f.length - 1 ] = lastSectionItem.replace( trailingLinebreak, '\n' ); + items[ i ] = current.replace( leadingLinebreak, '' ); + } + } } + return items; }; - cancel = function () { - doc.removeEventListener( 'mousemove', move ); - doc.removeEventListener( 'mouseup', up ); - }; + function isString( item ) { + return typeof item === 'string'; + } - doc.addEventListener( 'mousemove', move ); - doc.addEventListener( 'mouseup', up ); + function isComment( item ) { + return item.t === types.COMMENT || item.t === types.DELIMCHANGE; + } - setTimeout( cancel, timeThreshold ); - }; - - node.addEventListener( 'mousedown', mousedown ); - - - touchstart = function ( event ) { - var currentTarget, x, y, touch, finger, move, up, cancel; - - if ( event.touches.length !== 1 ) { - return; + function isSection( item ) { + return ( item.t === types.SECTION || item.t === types.INVERTED ) && item.f; } + return __export; + }( types ); - touch = event.touches[0]; + /* utils/escapeRegExp.js */ + var escapeRegExp = function() { - x = touch.clientX; - y = touch.clientY; - currentTarget = this; + var pattern = /[-/\\^$*+?.()|[\]{}]/g; + return function escapeRegExp( str ) { + return str.replace( pattern, '\\$&' ); + }; + }(); - finger = touch.identifier; + /* parse/_parse.js */ + var parse = function( types, Parser, mustache, comment, element, text, trimWhitespace, stripStandalones, escapeRegExp ) { - up = function ( event ) { - var touch; - - touch = event.changedTouches[0]; - if ( touch.identifier !== finger ) { - cancel(); + var __export; + var StandardParser, parse, contiguousWhitespace = /[ \t\f\r\n]+/g, + preserveWhitespaceElements = /^(?:pre|script|style|textarea)$/i, + leadingWhitespace = /^\s+/, + trailingWhitespace = /\s+$/; + StandardParser = Parser.extend( { + init: function( str, options ) { + // config + setDelimiters( options, this ); + this.sectionDepth = 0; + this.interpolate = { + script: !options.interpolate || options.interpolate.script !== false, + style: !options.interpolate || options.interpolate.style !== false + }; + if ( options.sanitize === true ) { + options.sanitize = { + // blacklist from https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/lang/html/html4-elements-whitelist.json + elements: 'applet base basefont body frame frameset head html isindex link meta noframes noscript object param script style title'.split( ' ' ), + eventAttributes: true + }; + } + this.sanitizeElements = options.sanitize && options.sanitize.elements; + this.sanitizeEventAttributes = options.sanitize && options.sanitize.eventAttributes; + this.includeLinePositions = options.includeLinePositions; + }, + postProcess: function( items, options ) { + if ( this.sectionDepth > 0 ) { + this.error( 'A section was left open' ); + } + cleanup( items, options.stripComments !== false, options.preserveWhitespace, !options.preserveWhitespace, !options.preserveWhitespace, options.rewriteElse !== false ); + return items; + }, + converters: [ + mustache, + comment, + element, + text + ] + } ); + parse = function( template ) { + var options = arguments[ 1 ]; + if ( options === void 0 ) + options = {}; + var result, remaining, partials, name, startMatch, endMatch, inlinePartialStart, inlinePartialEnd; + setDelimiters( options ); + inlinePartialStart = new RegExp( '<!--\\s*' + escapeRegExp( options.delimiters[ 0 ] ) + '\\s*>\\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\\s*' + escapeRegExp( options.delimiters[ 1 ] ) + '\\s*-->' ); + inlinePartialEnd = new RegExp( '<!--\\s*' + escapeRegExp( options.delimiters[ 0 ] ) + '\\s*\\/\\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\\s*' + escapeRegExp( options.delimiters[ 1 ] ) + '\\s*-->' ); + result = { + v: 1 + }; + if ( inlinePartialStart.test( template ) ) { + remaining = template; + template = ''; + while ( startMatch = inlinePartialStart.exec( remaining ) ) { + name = startMatch[ 1 ]; + template += remaining.substr( 0, startMatch.index ); + remaining = remaining.substring( startMatch.index + startMatch[ 0 ].length ); + endMatch = inlinePartialEnd.exec( remaining ); + if ( !endMatch || endMatch[ 1 ] !== name ) { + throw new Error( 'Inline partials must have a closing delimiter, and cannot be nested. Expected closing for "' + name + '", but ' + ( endMatch ? 'instead found "' + endMatch[ 1 ] + '"' : ' no closing found' ) ); + } + ( partials || ( partials = {} ) )[ name ] = new StandardParser( remaining.substr( 0, endMatch.index ), options ).result; + remaining = remaining.substring( endMatch.index + endMatch[ 0 ].length ); + } + template += remaining; + result.p = partials; } - - event.preventDefault(); // prevent compatibility mouse event - fire({ - node: currentTarget, - original: event - }); - - cancel(); + result.t = new StandardParser( template, options ).result; + return result; }; + __export = parse; - move = function ( event ) { - var touch; - - if ( event.touches.length !== 1 || event.touches[0].identifier !== finger ) { - cancel(); + function cleanup( items, stripComments, preserveWhitespace, removeLeadingWhitespace, removeTrailingWhitespace, rewriteElse ) { + var i, item, previousItem, nextItem, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, unlessBlock, key; + // First pass - remove standalones and comments etc + stripStandalones( items ); + i = items.length; + while ( i-- ) { + item = items[ i ]; + // Remove delimiter changes, unsafe elements etc + if ( item.exclude ) { + items.splice( i, 1 ); + } else if ( stripComments && item.t === types.COMMENT ) { + items.splice( i, 1 ); + } } - - touch = event.touches[0]; - if ( ( Math.abs( touch.clientX - x ) >= distanceThreshold ) || ( Math.abs( touch.clientY - y ) >= distanceThreshold ) ) { - cancel(); + // If necessary, remove leading and trailing whitespace + trimWhitespace( items, removeLeadingWhitespace, removeTrailingWhitespace ); + i = items.length; + while ( i-- ) { + item = items[ i ]; + // Recurse + if ( item.f ) { + preserveWhitespaceInsideFragment = preserveWhitespace || item.t === types.ELEMENT && preserveWhitespaceElements.test( item.e ); + if ( !preserveWhitespaceInsideFragment ) { + previousItem = items[ i - 1 ]; + nextItem = items[ i + 1 ]; + // if the previous item was a text item with trailing whitespace, + // remove leading whitespace inside the fragment + if ( !previousItem || typeof previousItem === 'string' && trailingWhitespace.test( previousItem ) ) { + removeLeadingWhitespaceInsideFragment = true; + } + // and vice versa + if ( !nextItem || typeof nextItem === 'string' && leadingWhitespace.test( nextItem ) ) { + removeTrailingWhitespaceInsideFragment = true; + } + } + cleanup( item.f, stripComments, preserveWhitespaceInsideFragment, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, rewriteElse ); + } + // Split if-else blocks into two (an if, and an unless) + if ( item.l ) { + cleanup( item.l, stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, rewriteElse ); + if ( rewriteElse ) { + unlessBlock = { + t: 4, + n: types.SECTION_UNLESS, + f: item.l + }; + // copy the conditional based on its type + if ( item.r ) { + unlessBlock.r = item.r; + } + if ( item.x ) { + unlessBlock.x = item.x; + } + if ( item.rx ) { + unlessBlock.rx = item.rx; + } + items.splice( i + 1, 0, unlessBlock ); + delete item.l; + } + } + // Clean up element attributes + if ( item.a ) { + for ( key in item.a ) { + if ( item.a.hasOwnProperty( key ) && typeof item.a[ key ] !== 'string' ) { + cleanup( item.a[ key ], stripComments, preserveWhitespace, removeLeadingWhitespaceInsideFragment, removeTrailingWhitespaceInsideFragment, rewriteElse ); + } + } + } } - }; + // final pass - fuse text nodes together + i = items.length; + while ( i-- ) { + if ( typeof items[ i ] === 'string' ) { + if ( typeof items[ i + 1 ] === 'string' ) { + items[ i ] = items[ i ] + items[ i + 1 ]; + items.splice( i + 1, 1 ); + } + if ( !preserveWhitespace ) { + items[ i ] = items[ i ].replace( contiguousWhitespace, ' ' ); + } + if ( items[ i ] === '' ) { + items.splice( i, 1 ); + } + } + } + } - 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 ); - }; - - node.addEventListener( 'touchstart', touchstart ); - - - return { - teardown: function () { - node.removeEventListener( 'mousedown', mousedown ); - node.removeEventListener( 'touchstart', touchstart ); + function setDelimiters( source ) { + var target = arguments[ 1 ]; + if ( target === void 0 ) + target = source; + target.delimiters = source.delimiters || [ + '{{', + '}}' + ]; + target.tripleDelimiters = source.tripleDelimiters || [ + '{{{', + '}}}' + ]; + target.staticDelimiters = source.staticDelimiters || [ + '[[', + ']]' + ]; + target.staticTripleDelimiters = source.staticTripleDelimiters || [ + '[[[', + ']]]' + ]; } - }; -}; -(function () { + return __export; + }( types, Parser, mustache, comment, element, text, trimWhitespace, stripStandalones, escapeRegExp ); - var fillGaps, - clone, - augment, + /* config/options/groups/optionGroup.js */ + var optionGroup = function() { - inheritFromParent, - wrapMethod, - inheritFromChildProps, - conditionallyParseTemplate, - extractInlinePartials, - conditionallyParsePartials, - initChildInstance, + return function createOptionGroup( keys, config ) { + var group = keys.map( config ); + keys.forEach( function( key, i ) { + group[ key ] = group[ i ]; + } ); + return group; + }; + }( legacy ); - extendable, - inheritable, - blacklist; + /* config/options/groups/parseOptions.js */ + var parseOptions = function( optionGroup ) { - extend = function ( childProps ) { + var keys, parseOptions; + keys = [ + 'preserveWhitespace', + 'sanitize', + 'stripComments', + 'delimiters', + 'tripleDelimiters', + 'interpolate' + ]; + parseOptions = optionGroup( keys, function( key ) { + return key; + } ); + return parseOptions; + }( optionGroup ); - var Parent, Child, key, template, partials, partial, member; + /* config/options/template/parser.js */ + var parser = function( errors, isClient, parse, create, parseOptions ) { - Parent = this; - - // create Child constructor - Child = function ( options ) { - initChildInstance( this, Child, options || {}); + var parser = { + parse: doParse, + fromId: fromId, + isHashedId: isHashedId, + isParsed: isParsed, + getParseOptions: getParseOptions, + createHelper: createHelper }; - Child.prototype = create( Parent.prototype ); - - // inherit options from parent, if we're extending a subclass - if ( Parent !== Ractive ) { - inheritFromParent( Child, Parent ); + function createHelper( parseOptions ) { + var helper = create( parser ); + helper.parse = function( template, options ) { + return doParse( template, options || parseOptions ); + }; + return helper; } - // apply childProps - inheritFromChildProps( Child, childProps ); - - // parse template and any partials that need it - conditionallyParseTemplate( Child ); - extractInlinePartials( Child, childProps ); - conditionallyParsePartials( Child ); - - Child.extend = Parent.extend; - - return Child; - }; - - extendable = [ 'data', 'partials', 'transitions', 'eventDefinitions' ]; - inheritable = [ 'el', 'template', 'complete', 'modifyArrays', 'twoway', 'lazy', 'append', 'preserveWhitespace', 'sanitize', 'noIntro', 'transitionsEnabled' ]; - blacklist = extendable.concat( inheritable ); - - inheritFromParent = function ( Child, Parent ) { - extendable.forEach( function ( property ) { - if ( Parent[ property ] ) { - Child[ property ] = clone( Parent[ property ] ); + function doParse( template, parseOptions ) { + if ( !parse ) { + throw new Error( errors.missingParser ); } - }); + return parse( template, parseOptions || this.options ); + } - inheritable.forEach( function ( property ) { - if ( Parent[ property ] !== undefined ) { - Child[ property ] = Parent[ property ]; + function fromId( id, options ) { + var template; + if ( !isClient ) { + if ( options && options.noThrow ) { + return; + } + throw new Error( 'Cannot retrieve template #' + id + ' as Ractive is not running in a browser.' ); } - }); - }; + if ( isHashedId( id ) ) { + id = id.substring( 1 ); + } + if ( !( template = document.getElementById( id ) ) ) { + if ( options && options.noThrow ) { + return; + } + throw new Error( 'Could not find template element with id #' + id ); + } + if ( template.tagName.toUpperCase() !== 'SCRIPT' ) { + if ( options && options.noThrow ) { + return; + } + throw new Error( 'Template element with id #' + id + ', must be a <script> element' ); + } + return template.innerHTML; + } - wrapMethod = function ( method, superMethod ) { - if ( /_super/.test( method ) ) { - return function () { - var _super = this._super; - this._super = superMethod; + function isHashedId( id ) { + return id && id.charAt( 0 ) === '#'; + } - method.apply( this, arguments ); - - this._super = _super; - }; + function isParsed( template ) { + return !( typeof template === 'string' ); } - else { - return method; + function getParseOptions( ractive ) { + // Could be Ractive or a Component + if ( ractive.defaults ) { + ractive = ractive.defaults; + } + return parseOptions.reduce( function( val, key ) { + val[ key ] = ractive[ key ]; + return val; + }, {} ); } - }; + return parser; + }( errors, isClient, parse, create, parseOptions ); - inheritFromChildProps = function ( Child, childProps ) { - var key, member; + /* config/options/template/template.js */ + var template = function( parser, parse ) { - extendable.forEach( function ( property ) { - var value = childProps[ property ]; - - if ( value ) { - if ( Child[ property ] ) { - augment( Child[ property ], value ); + var templateConfig = { + name: 'template', + extend: function extend( Parent, proto, options ) { + var template; + // only assign if exists + if ( 'template' in options ) { + template = options.template; + if ( typeof template === 'function' ) { + proto.template = template; + } else { + proto.template = parseIfString( template, proto ); + } } - - else { - Child[ property ] = value; + }, + init: function init( Parent, ractive, options ) { + var template, fn; + // TODO because of prototypal inheritance, we might just be able to use + // ractive.template, and not bother passing through the Parent object. + // At present that breaks the test mocks' expectations + template = 'template' in options ? options.template : Parent.prototype.template; + if ( typeof template === 'function' ) { + fn = template; + template = getDynamicTemplate( ractive, fn ); + ractive._config.template = { + fn: fn, + result: template + }; } + template = parseIfString( template, ractive ); + // TODO the naming of this is confusing - ractive.template refers to [...], + // but Component.prototype.template refers to {v:1,t:[],p:[]}... + // it's unnecessary, because the developer never needs to access + // ractive.template + ractive.template = template.t; + if ( template.p ) { + extendPartials( ractive.partials, template.p ); + } + }, + reset: function( ractive ) { + var result = resetValue( ractive ), + parsed; + if ( result ) { + parsed = parseIfString( result, ractive ); + ractive.template = parsed.t; + extendPartials( ractive.partials, parsed.p, true ); + return true; + } } - }); + }; - inheritable.forEach( function ( property ) { - if ( childProps[ property ] !== undefined ) { - Child[ property ] = childProps[ property ]; + function resetValue( ractive ) { + var initial = ractive._config.template, + result; + // If this isn't a dynamic template, there's nothing to do + if ( !initial || !initial.fn ) { + return; } - }); - - // Blacklisted properties don't extend the child, as they are part of the initialisation options - for ( key in childProps ) { - if ( hasOwn.call( childProps, key ) && !hasOwn.call( Child.prototype, key ) && blacklist.indexOf( key ) === -1 ) { - member = childProps[ key ]; - - // if this is a method that overwrites a prototype method, we may need - // to wrap it - if ( typeof member === 'function' && typeof Child.prototype[ key ] === 'function' ) { - Child.prototype[ key ] = wrapMethod( member, Child.prototype[ key ] ); - } else { - Child.prototype[ key ] = member; - } + result = getDynamicTemplate( ractive, initial.fn ); + // TODO deep equality check to prevent unnecessary re-rendering + // in the case of already-parsed templates + if ( result !== initial.result ) { + initial.result = result; + result = parseIfString( result, ractive ); + return result; } } - }; - conditionallyParseTemplate = function ( Child ) { - var templateEl; + function getDynamicTemplate( ractive, fn ) { + var helper = parser.createHelper( parser.getParseOptions( ractive ) ); + return fn.call( ractive, ractive.data, helper ); + } - if ( typeof Child.template === 'string' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); - } - - if ( Child.template.charAt( 0 ) === '#' && doc ) { - templateEl = doc.getElementById( Child.template.substring( 1 ) ); - if ( templateEl && templateEl.tagName === 'SCRIPT' ) { - Child.template = Ractive.parse( templateEl.innerHTML, Child ); - } else { - throw new Error( 'Could not find template element (' + Child.template + ')' ); + function parseIfString( template, ractive ) { + if ( typeof template === 'string' ) { + // ID of an element containing the template? + if ( template[ 0 ] === '#' ) { + template = parser.fromId( template ); } - } else { - Child.template = Ractive.parse( Child.template, Child ); // all the relevant options are on Child + template = parse( template, parser.getParseOptions( ractive ) ); + } else if ( template.v !== 1 ) { + throw new Error( 'Mismatched template version! Please ensure you are using the latest version of Ractive.js in your build process as well as in your app' ); } + return template; } - }; - extractInlinePartials = function ( Child, childProps ) { - // does our template contain inline partials? - if ( isObject( Child.template ) ) { - if ( !Child.partials ) { - Child.partials = {}; + function extendPartials( existingPartials, newPartials, overwrite ) { + if ( !newPartials ) + return; + // TODO there's an ambiguity here - we need to overwrite in the `reset()` + // case, but not initially... + for ( var key in newPartials ) { + if ( overwrite || !existingPartials.hasOwnProperty( key ) ) { + existingPartials[ key ] = newPartials[ key ]; + } } - - // get those inline partials - augment( Child.partials, Child.template.partials ); - - // but we also need to ensure that any explicit partials override inline ones - if ( childProps.partials ) { - augment( Child.partials, childProps.partials ); - } - - // move template to where it belongs - Child.template = Child.template.template; } - }; + return templateConfig; + }( parser, parse ); - conditionallyParsePartials = function ( Child ) { - var key, partial; + /* config/options/Registry.js */ + var Registry = function( create ) { - // Parse partials, if necessary - if ( Child.partials ) { - for ( key in Child.partials ) { - if ( hasOwn.call( Child.partials, key ) ) { - if ( typeof Child.partials[ key ] === 'string' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); + function Registry( name, useDefaults ) { + this.name = name; + this.useDefaults = useDefaults; + } + Registry.prototype = { + constructor: Registry, + extend: function( Parent, proto, options ) { + this.configure( this.useDefaults ? Parent.defaults : Parent, this.useDefaults ? proto : proto.constructor, options ); + }, + init: function( Parent, ractive, options ) { + this.configure( this.useDefaults ? Parent.defaults : Parent, ractive, options ); + }, + configure: function( Parent, target, options ) { + var name = this.name, + option = options[ name ], + registry; + registry = create( Parent[ name ] ); + for ( var key in option ) { + registry[ key ] = option[ key ]; + } + target[ name ] = registry; + }, + reset: function( ractive ) { + var registry = ractive[ this.name ]; + var changed = false; + Object.keys( registry ).forEach( function( key ) { + var item = registry[ key ]; + if ( item._fn ) { + if ( item._fn.isOwner ) { + registry[ key ] = item._fn; + } else { + delete registry[ key ]; } - - partial = Ractive.parse( Child.partials[ key ], Child ); - } else { - partial = Child.partials[ key ]; + changed = true; } - - Child.partials[ key ] = partial; + } ); + return changed; + }, + findOwner: function( ractive, key ) { + return ractive[ this.name ].hasOwnProperty( key ) ? ractive : this.findConstructor( ractive.constructor, key ); + }, + findConstructor: function( constructor, key ) { + if ( !constructor ) { + return; } + return constructor[ this.name ].hasOwnProperty( key ) ? constructor : this.findConstructor( constructor._parent, key ); + }, + find: function( ractive, key ) { + var this$0 = this; + return recurseFind( ractive, function( r ) { + return r[ this$0.name ][ key ]; + } ); + }, + findInstance: function( ractive, key ) { + var this$0 = this; + return recurseFind( ractive, function( r ) { + return r[ this$0.name ][ key ] ? r : void 0; + } ); } - } - }; + }; - initChildInstance = function ( child, Child, options ) { - var key, i, optionName; - - // Add template to options, if necessary - if ( !options.template && Child.template ) { - options.template = Child.template; - } - - extendable.forEach( function ( property ) { - if ( !options[ property ] ) { - if ( Child[ property ] ) { - options[ property ] = clone( Child[ property ] ); - } - } else { - fillGaps( options[ property ], Child[ property ] ); + function recurseFind( ractive, fn ) { + var find, parent; + if ( find = fn( ractive ) ) { + return find; } - }); - - inheritable.forEach( function ( property ) { - if ( options[ property ] === undefined && Child[ property ] !== undefined ) { - options[ property ] = Child[ property ]; + if ( !ractive.isolated && ( parent = ractive._parent ) ) { + return recurseFind( parent, fn ); } - }); + } + return Registry; + }( create, legacy ); - Ractive.call( child, options ); + /* config/options/groups/registries.js */ + var registries = function( optionGroup, Registry ) { - if ( child.init ) { - child.init.call( child, options ); - } - }; + var keys = [ + 'adaptors', + 'components', + 'computed', + 'decorators', + 'easing', + 'events', + 'interpolators', + 'partials', + 'transitions' + ], + registries = optionGroup( keys, function( key ) { + return new Registry( key, key === 'computed' ); + } ); + return registries; + }( optionGroup, Registry ); - fillGaps = function ( target, source ) { - var key; + /* utils/noop.js */ + var noop = function() {}; - for ( key in source ) { - if ( hasOwn.call( source, key ) && !hasOwn.call( target, key ) ) { - target[ key ] = source[ key ]; + /* utils/wrapPrototypeMethod.js */ + var wrapPrototypeMethod = function( noop ) { + + var __export; + __export = function wrap( parent, name, method ) { + if ( !/_super/.test( method ) ) { + return method; } - } - }; + var wrapper = function wrapSuper() { + var superMethod = getSuperMethod( wrapper._parent, name ), + hasSuper = '_super' in this, + oldSuper = this._super, + result; + this._super = superMethod; + result = method.apply( this, arguments ); + if ( hasSuper ) { + this._super = oldSuper; + } else { + delete this._super; + } + return result; + }; + wrapper._parent = parent; + wrapper._method = method; + return wrapper; + }; - clone = function ( source ) { - var target = {}, key; - - for ( key in source ) { - if ( hasOwn.call( source, key ) ) { - target[ key ] = source[ key ]; + function getSuperMethod( parent, name ) { + var method; + if ( name in parent ) { + var value = parent[ name ]; + if ( typeof value === 'function' ) { + method = value; + } else { + method = function returnValue() { + return value; + }; + } + } else { + method = noop; } + return method; } + return __export; + }( noop ); - return target; - }; + /* config/deprecate.js */ + var deprecate = function( warn, isArray ) { - augment = function ( target, source ) { - var key; - - for ( key in source ) { - if ( hasOwn.call( source, key ) ) { - target[ key ] = source[ key ]; + function deprecate( options, deprecated, correct ) { + if ( deprecated in options ) { + if ( !( correct in options ) ) { + warn( getMessage( deprecated, correct ) ); + options[ correct ] = options[ deprecated ]; + } else { + throw new Error( getMessage( deprecated, correct, true ) ); + } } } - }; -}()); -// TODO short circuit values that stay the same -interpolate = function ( from, to ) { - if ( isNumeric( from ) && isNumeric( to ) ) { - return Ractive.interpolators.number( +from, +to ); - } + function getMessage( deprecated, correct, isError ) { + return 'options.' + deprecated + ' has been deprecated in favour of options.' + correct + '.' + ( isError ? ' You cannot specify both options, please use options.' + correct + '.' : '' ); + } - 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; }; + function deprecateEventDefinitions( options ) { + deprecate( options, 'eventDefinitions', 'events' ); } - return function ( t ) { - return from + ( t * delta ); + function deprecateAdaptors( options ) { + // Using extend with Component instead of options, + // like Human.extend( Spider ) means adaptors as a registry + // gets copied to options. So we have to check if actually an array + if ( isArray( options.adaptors ) ) { + deprecate( options, 'adaptors', 'adapt' ); + } + } + return function deprecateOptions( options ) { + deprecate( options, 'beforeInit', 'onconstruct' ); + deprecate( options, 'init', 'onrender' ); + deprecate( options, 'complete', 'oncomplete' ); + deprecateEventDefinitions( options ); + deprecateAdaptors( options ); }; - }, + }( warn, isArray ); - array: function ( from, to ) { - var intermediate, interpolators, len, i; + /* config/config.js */ + var config = function( css, data, defaults, template, parseOptions, registries, wrapPrototype, deprecate ) { - intermediate = []; - interpolators = []; - - i = len = Math.min( from.length, to.length ); - while ( i-- ) { - interpolators[i] = Ractive.interpolate( from[i], to[i] ); + var custom, options, config, blacklisted; + // would be nice to not have these here, + // they get added during initialise, so for now we have + // to make sure not to try and extend them. + // Possibly, we could re-order and not add till later + // in process. + blacklisted = { + '_parent': true, + '_component': true + }; + custom = { + data: data, + template: template, + css: css + }; + options = Object.keys( defaults ).filter( function( key ) { + return !registries[ key ] && !custom[ key ] && !parseOptions[ key ]; + } ); + // this defines the order: + config = [].concat( custom.data, parseOptions, options, registries, custom.template, custom.css ); + for ( var key in custom ) { + config[ key ] = custom[ key ]; } + // for iteration + config.keys = Object.keys( defaults ).concat( registries.map( function( r ) { + return r.name; + } ) ).concat( [ 'css' ] ); + // add these to blacklisted key's that we don't double extend + config.keys.forEach( function( key ) { + return blacklisted[ key ] = true; + } ); + config.parseOptions = parseOptions; + config.registries = registries; - // surplus values - don't interpolate, but don't exclude them either - for ( i=len; i<from.length; i+=1 ) { - intermediate[i] = from[i]; + function customConfig( method, key, Parent, instance, options ) { + custom[ key ][ method ]( Parent, instance, options ); } + config.extend = function( Parent, proto, options ) { + configure( 'extend', Parent, proto, options ); + }; + config.init = function( Parent, ractive, options ) { + configure( 'init', Parent, ractive, options ); + }; - for ( i=len; i<to.length; i+=1 ) { - intermediate[i] = to[i]; + function isStandardDefaultKey( key ) { + return key in defaults && !( key in config.parseOptions ) && !( key in custom ); } - 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 ( hasOwn.call( from, prop ) ) { - if ( hasOwn.call( to, prop ) ) { - properties[ properties.length ] = prop; - interpolators[ prop ] = Ractive.interpolate( from[ prop ], to[ prop ] ); + function configure( method, Parent, instance, options ) { + deprecate( options ); + customConfig( method, 'data', Parent, instance, options ); + config.parseOptions.forEach( function( key ) { + if ( key in options ) { + instance[ key ] = options[ key ]; } - - else { - intermediate[ prop ] = from[ prop ]; + } ); + for ( var key in options ) { + if ( isStandardDefaultKey( key ) ) { + var value = options[ key ]; + instance[ key ] = typeof value === 'function' ? wrapPrototype( Parent.prototype, key, value ) : value; } } + config.registries.forEach( function( registry ) { + registry[ method ]( Parent, instance, options ); + } ); + customConfig( method, 'template', Parent, instance, options ); + customConfig( method, 'css', Parent, instance, options ); + extendOtherMethods( Parent.prototype, instance, options ); } - for ( prop in to ) { - if ( hasOwn.call( to, prop ) && !hasOwn.call( from, prop ) ) { - intermediate[ prop ] = to[ prop ]; + function extendOtherMethods( parent, instance, options ) { + for ( var key in options ) { + if ( !( key in blacklisted ) && options.hasOwnProperty( key ) ) { + var member = options[ key ]; + // if this is a method that overwrites a method, wrap it: + if ( typeof member === 'function' ) { + member = wrapPrototype( parent, key, member ); + } + instance[ key ] = member; + } } } + config.reset = function( ractive ) { + return config.filter( function( c ) { + return c.reset && c.reset( ractive ); + } ).map( function( c ) { + return c.name; + } ); + }; + config.getConstructTarget = function( ractive, options ) { + if ( options.onconstruct ) { + // pretend this object literal is the ractive instance + return { + onconstruct: wrapPrototype( ractive, 'onconstruct', options.onconstruct ).bind( ractive ), + fire: ractive.fire.bind( ractive ) + }; + } else { + return ractive; + } + }; + return config; + }( css, data, options, template, parseOptions, registries, wrapPrototypeMethod, deprecate ); - len = properties.length; + /* shared/interpolate.js */ + var interpolate = function( circular, warn, interpolators, config ) { - return function ( t ) { - var i = len, prop; - - while ( i-- ) { - prop = properties[i]; - - intermediate[ prop ] = interpolators[ prop ]( t ); + var __export; + var interpolate = function( from, to, ractive, type ) { + if ( from === to ) { + return snap( to ); } - - return intermediate; + if ( type ) { + var interpol = config.registries.interpolators.find( ractive, type ); + if ( interpol ) { + return interpol( from, to ) || snap( to ); + } + warn( 'Missing "' + type + '" interpolator. You may need to download a plugin from [TODO]' ); + } + return interpolators.number( from, to ) || interpolators.array( from, to ) || interpolators.object( from, to ) || snap( to ); }; - } -}; -var defaultOptions = createFromNull(); + circular.interpolate = interpolate; + __export = interpolate; -defineProperties( defaultOptions, { - preserveWhitespace: { enumerable: true, value: false }, - append: { enumerable: true, value: false }, - twoway: { enumerable: true, value: true }, - modifyArrays: { enumerable: true, value: true }, - data: { enumerable: true, value: {} }, - lazy: { enumerable: true, value: false }, - debug: { enumerable: true, value: false }, - transitions: { enumerable: true, value: {} }, - eventDefinitions: { enumerable: true, value: {} }, - noIntro: { enumerable: true, value: false }, - transitionsEnabled: { enumerable: true, value: true } -}); - -Ractive = function ( options ) { - - var key, partial, i, template, templateEl, parsedTemplate; - - // Options - // ------- - for ( key in defaultOptions ) { - if ( !hasOwn.call( options, key ) ) { - options[ key ] = ( typeof defaultOptions[ key ] === 'object' ? {} : defaultOptions[ key ] ); + function snap( to ) { + return function() { + return to; + }; } - } + return __export; + }( circular, warn, interpolators, config ); + /* Ractive/prototype/animate/Animation.js */ + var Ractive$animate_Animation = function( warn, runloop, interpolate ) { - // Initialization - // -------------- + var 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 = interpolate( this.from, this.to, this.root, this.interpolator ); + this.running = true; + this.tick(); + }; + Animation.prototype = { + tick: function() { + var elapsed, t, value, timeNow, index, keypath; + keypath = this.keypath; + if ( this.running ) { + timeNow = Date.now(); + elapsed = timeNow - this.startTime; + if ( elapsed >= this.duration ) { + if ( keypath !== null ) { + runloop.start( this.root ); + this.root.viewmodel.set( keypath, this.to ); + runloop.end(); + } + if ( this.step ) { + this.step( 1, this.to ); + } + this.complete( this.to ); + index = this.root._animations.indexOf( this ); + // TODO investigate why this happens + if ( index === -1 ) { + warn( 'Animation was not found' ); + } + this.root._animations.splice( index, 1 ); + this.running = false; + return false; + } + t = this.easing ? this.easing( elapsed / this.duration ) : elapsed / this.duration; + if ( keypath !== null ) { + value = this.interpolator( t ); + runloop.start( this.root ); + this.root.viewmodel.set( keypath, value ); + runloop.end(); + } + if ( this.step ) { + this.step( t, value ); + } + return true; + } + return false; + }, + stop: function() { + var index; + this.running = false; + index = this.root._animations.indexOf( this ); + // TODO investigate why this happens + if ( index === -1 ) { + warn( 'Animation was not found' ); + } + this.root._animations.splice( index, 1 ); + } + }; + return Animation; + }( warn, runloop, interpolate ); - // We use Object.defineProperties (where possible) as these should be read-only - defineProperties( this, { - // Generate a unique identifier, for places where you'd use a weak map if it - // existed - _guid: { - value: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r, v; + /* Ractive/prototype/animate.js */ + var Ractive$animate = function( isEqual, Promise, normaliseKeypath, animations, Animation ) { - r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }) - }, + var __export; + var noop = function() {}, + noAnimation = { + stop: noop + }; + __export = function Ractive$animate( keypath, to, options ) { + var promise, fulfilPromise, k, animation, animations, easing, duration, step, complete, makeValueCollector, currentValues, collectValue, dummy, dummyOptions; + promise = new Promise( function( fulfil ) { + fulfilPromise = fulfil; + } ); + // animate multiple keypaths + if ( typeof keypath === 'object' ) { + options = to || {}; + easing = options.easing; + duration = options.duration; + animations = []; + // we don't want to pass the `step` and `complete` handlers, as they will + // run for each animation! So instead we'll store the handlers and create + // our own... + step = options.step; + complete = options.complete; + if ( step || complete ) { + currentValues = {}; + options.step = null; + options.complete = null; + makeValueCollector = function( keypath ) { + return function( t, value ) { + currentValues[ keypath ] = value; + }; + }; + } + for ( k in keypath ) { + if ( keypath.hasOwnProperty( k ) ) { + if ( step || complete ) { + collectValue = makeValueCollector( k ); + options = { + easing: easing, + duration: duration + }; + if ( step ) { + options.step = collectValue; + } + } + options.complete = complete ? collectValue : noop; + animations.push( animate( this, k, keypath[ k ], options ) ); + } + } + // Create a dummy animation, to facilitate step/complete + // callbacks, and Promise fulfilment + dummyOptions = { + easing: easing, + duration: duration + }; + if ( step ) { + dummyOptions.step = function( t ) { + step( t, currentValues ); + }; + } + if ( complete ) { + promise.then( function( t ) { + complete( t, currentValues ); + } ); + } + dummyOptions.complete = fulfilPromise; + dummy = animate( this, null, null, dummyOptions ); + animations.push( dummy ); + promise.stop = function() { + var animation; + while ( animation = animations.pop() ) { + animation.stop(); + } + if ( dummy ) { + dummy.stop(); + } + }; + return promise; + } + // animate a single keypath + options = options || {}; + if ( options.complete ) { + promise.then( options.complete ); + } + options.complete = fulfilPromise; + animation = animate( this, keypath, to, options ); + promise.stop = function() { + animation.stop(); + }; + return promise; + }; - // events - _subs: { value: createFromNull() }, - - // cache - _cache: { value: {} }, // we need to be able to use hasOwnProperty, so can't inherit from null - _cacheMap: { value: createFromNull() }, - - // dependency graph - _deps: { value: [] }, - _depsMap: { value: createFromNull() }, - - // unresolved dependants - _pendingResolution: { value: [] }, - - // Create arrays for deferred attributes and evaluators - _defAttrs: { value: [] }, - _defEvals: { value: [] }, - - // Cache proxy event handlers - allows efficient reuse - _proxies: { value: createFromNull() }, - _customProxies: { value: createFromNull() }, - - // Keep a list of used evaluators, so we don't duplicate them - _evaluators: { value: createFromNull() }, - - // bindings - _bound: { value: [] }, - - // transition manager - _transitionManager: { value: null, writable: true }, - - // animations (so we can stop any in progress at teardown) - _animations: { value: [] }, - - // nodes registry - nodes: { value: {} } - }); - - // options - this.modifyArrays = options.modifyArrays; - this.twoway = options.twoway; - this.lazy = options.lazy; - this.debug = options.debug; - - if ( options.el ) { - this.el = getEl( options.el ); - if ( !this.el && this.debug ) { - throw new Error( 'Could not find container element' ); - } - } - - // add data - this.data = options.data || {}; - - - // Partials registry - this.partials = {}; - - // Transition registry - this.transitions = options.transitions; - - // Instance-specific event definitions registry - this.eventDefinitions = options.eventDefinitions; - - // Set up bindings - if ( options.bindings ) { - if ( isArray( options.bindings ) ) { - for ( i=0; i<options.bindings.length; i+=1 ) { - this.bind( options.bindings[i] ); + function animate( root, keypath, to, options ) { + var easing, duration, animation, from; + if ( keypath ) { + keypath = normaliseKeypath( keypath ); } - } else { - this.bind( options.bindings ); + if ( keypath !== null ) { + from = root.viewmodel.get( keypath ); + } + // cancel any existing animation + // TODO what about upstream/downstream keypaths? + animations.abort( keypath, root ); + // don't bother animating values that stay the same + if ( isEqual( from, to ) ) { + if ( options.complete ) { + options.complete( options.to ); + } + return noAnimation; + } + // easing function + if ( options.easing ) { + if ( typeof options.easing === 'function' ) { + easing = options.easing; + } else { + easing = root.easing[ options.easing ]; + } + if ( typeof easing !== 'function' ) { + easing = null; + } + } + // duration + duration = options.duration === undefined ? 400 : options.duration; + // TODO store keys, use an internal set method + animation = new Animation( { + keypath: keypath, + from: from, + to: to, + root: root, + duration: duration, + easing: easing, + interpolator: options.interpolator, + // TODO wrap callbacks if necessary, to use instance as context + step: options.step, + complete: options.complete + } ); + animations.add( animation ); + root._animations.push( animation ); + return animation; } - } + return __export; + }( isEqual, Promise, normaliseKeypath, animations, Ractive$animate_Animation ); + /* Ractive/prototype/detach.js */ + var Ractive$detach = function( Hook, removeFromArray ) { - // Parse template, if necessary - template = options.template; - - if ( typeof template === 'string' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); - } - - if ( template.charAt( 0 ) === '#' && doc ) { - // assume this is an ID of a <script type='text/ractive'> tag - templateEl = doc.getElementById( template.substring( 1 ) ); - if ( templateEl ) { - parsedTemplate = Ractive.parse( templateEl.innerHTML, options ); + var detachHook = new Hook( 'detach' ); + return function Ractive$detach() { + if ( this.detached ) { + return this.detached; } - - else { - throw new Error( 'Could not find template element (' + template + ')' ); + if ( this.el ) { + removeFromArray( this.el.__ractive_instances__, this ); } - } + this.detached = this.fragment.detach(); + detachHook.fire( this ); + return this.detached; + }; + }( Ractive$shared_hooks_Hook, removeFromArray ); - else { - parsedTemplate = Ractive.parse( template, options ); + /* Ractive/prototype/find.js */ + var Ractive$find = function Ractive$find( selector ) { + if ( !this.el ) { + return null; } - } else { - parsedTemplate = template; - } + return this.fragment.find( selector ); + }; - // deal with compound template - if ( isObject( parsedTemplate ) ) { - this.partials = parsedTemplate.partials; - parsedTemplate = parsedTemplate.template; - } + /* utils/matches.js */ + var matches = function( isClient, vendors, createElement ) { - // If the template was an array with a single string member, that means - // we can use innerHTML - we just need to unpack it - if ( parsedTemplate && ( parsedTemplate.length === 1 ) && ( typeof parsedTemplate[0] === 'string' ) ) { - parsedTemplate = parsedTemplate[0]; - } - - this.template = parsedTemplate; - - - // If we were given unparsed partials, parse them - if ( options.partials ) { - for ( key in options.partials ) { - if ( hasOwn.call( options.partials, key ) ) { - partial = options.partials[ key ]; - - if ( typeof partial === 'string' ) { - if ( !Ractive.parse ) { - throw new Error( missingParser ); + var matches, div, methodNames, unprefixed, prefixed, i, j, makeFunction; + if ( !isClient ) { + matches = null; + } else { + div = createElement( 'div' ); + methodNames = [ + 'matches', + 'matchesSelector' + ]; + makeFunction = function( methodName ) { + return function( node, selector ) { + return node[ methodName ]( selector ); + }; + }; + i = methodNames.length; + while ( i-- && !matches ) { + unprefixed = methodNames[ i ]; + if ( div[ unprefixed ] ) { + matches = makeFunction( unprefixed ); + } else { + j = vendors.length; + while ( j-- ) { + prefixed = vendors[ i ] + unprefixed.substr( 0, 1 ).toUpperCase() + unprefixed.substring( 1 ); + if ( div[ prefixed ] ) { + matches = makeFunction( prefixed ); + break; + } } - - partial = Ractive.parse( partial, options ); } - - this.partials[ key ] = partial; } + // IE8... + if ( !matches ) { + matches = function( node, selector ) { + var nodes, parentNode, i; + parentNode = node.parentNode; + if ( !parentNode ) { + // empty dummy <div> + div.innerHTML = ''; + parentNode = div; + node = node.cloneNode(); + div.appendChild( node ); + } + nodes = parentNode.querySelectorAll( selector ); + i = nodes.length; + while ( i-- ) { + if ( nodes[ i ] === node ) { + return true; + } + } + return false; + }; + } } - } + return matches; + }( isClient, vendors, createElement ); - // Unpack string-based partials, if necessary - for ( key in this.partials ) { - if ( hasOwn.call( this.partials, key ) && this.partials[ key ].length === 1 && typeof this.partials[ key ][0] === 'string' ) { - this.partials[ key ] = this.partials[ key ][0]; - } - } + /* Ractive/prototype/shared/makeQuery/test.js */ + var Ractive$shared_makeQuery_test = function( matches ) { - // temporarily disable transitions, if noIntro flag is set - this.transitionsEnabled = ( options.noIntro ? false : options.transitionsEnabled ); + return function( item, noDirty ) { + var itemMatches = this._isComponentQuery ? !this.selector || item.name === this.selector : matches( item.node, this.selector ); + if ( itemMatches ) { + this.push( item.node || item.instance ); + if ( !noDirty ) { + this._makeDirty(); + } + return true; + } + }; + }( matches ); - render( this, { el: this.el, append: options.append, complete: options.complete }); - - // reset transitionsEnabled - this.transitionsEnabled = options.transitionsEnabled; -}; - -(function () { - - var getOriginalComputedStyles, setStyle, augment, makeTransition, transform, transformsEnabled, inside, outside; - - // no point executing this code on the server - if ( !doc ) { - return; - } - - getOriginalComputedStyles = function ( computedStyle, properties ) { - var original = {}, i; - - i = properties.length; - while ( i-- ) { - original[ properties[i] ] = computedStyle[ properties[i] ]; + /* Ractive/prototype/shared/makeQuery/cancel.js */ + var Ractive$shared_makeQuery_cancel = function() { + var liveQueries, selector, index; + liveQueries = this._root[ this._isComponentQuery ? 'liveComponentQueries' : 'liveQueries' ]; + selector = this.selector; + index = liveQueries.indexOf( selector ); + if ( index !== -1 ) { + liveQueries.splice( index, 1 ); + liveQueries[ selector ] = null; } - - return original; }; - setStyle = function ( node, properties, map, params ) { - var i = properties.length, prop; + /* Ractive/prototype/shared/makeQuery/sortByItemPosition.js */ + var Ractive$shared_makeQuery_sortByItemPosition = function() { - while ( i-- ) { - prop = properties[i]; - if ( map && map[ prop ] ) { - if ( typeof map[ prop ] === 'function' ) { - node.style[ prop ] = map[ prop ]( params ); - } else { - node.style[ prop ] = map[ prop ]; - } + var __export; + __export = function( a, b ) { + var ancestryA, ancestryB, oldestA, oldestB, mutualAncestor, indexA, indexB, fragments, fragmentA, fragmentB; + ancestryA = getAncestry( a.component || a._ractive.proxy ); + ancestryB = getAncestry( b.component || b._ractive.proxy ); + oldestA = ancestryA[ ancestryA.length - 1 ]; + oldestB = ancestryB[ ancestryB.length - 1 ]; + // remove items from the end of both ancestries as long as they are identical + // - the final one removed is the closest mutual ancestor + while ( oldestA && oldestA === oldestB ) { + ancestryA.pop(); + ancestryB.pop(); + mutualAncestor = oldestA; + oldestA = ancestryA[ ancestryA.length - 1 ]; + oldestB = ancestryB[ ancestryB.length - 1 ]; } + // now that we have the mutual ancestor, we can find which is earliest + oldestA = oldestA.component || oldestA; + oldestB = oldestB.component || oldestB; + fragmentA = oldestA.parentFragment; + fragmentB = oldestB.parentFragment; + // if both items share a parent fragment, our job is easy + if ( fragmentA === fragmentB ) { + indexA = fragmentA.items.indexOf( oldestA ); + indexB = fragmentB.items.indexOf( oldestB ); + // if it's the same index, it means one contains the other, + // so we see which has the longest ancestry + return indexA - indexB || ancestryA.length - ancestryB.length; + } + // if mutual ancestor is a section, we first test to see which section + // fragment comes first + if ( fragments = mutualAncestor.fragments ) { + indexA = fragments.indexOf( fragmentA ); + indexB = fragments.indexOf( fragmentB ); + return indexA - indexB || ancestryA.length - ancestryB.length; + } + throw new Error( 'An unexpected condition was met while comparing the position of two components. Please file an issue at https://github.com/RactiveJS/Ractive/issues - thanks!' ); + }; - else { - node.style[ prop ] = 0; + function getParent( item ) { + var parentFragment; + if ( parentFragment = item.parentFragment ) { + return parentFragment.owner; } + if ( item.component && ( parentFragment = item.component.parentFragment ) ) { + return parentFragment.owner; + } } - }; - augment = function ( target, source ) { - var key; - - if ( !source ) { - return target; - } - - for ( key in source ) { - if ( hasOwn.call( source, key ) ) { - target[ key ] = source[ key ]; + function getAncestry( item ) { + var ancestry, ancestor; + ancestry = [ item ]; + ancestor = getParent( item ); + while ( ancestor ) { + ancestry.push( ancestor ); + ancestor = getParent( ancestor ); } + return ancestry; } + return __export; + }(); - return target; - }; + /* Ractive/prototype/shared/makeQuery/sortByDocumentPosition.js */ + var Ractive$shared_makeQuery_sortByDocumentPosition = function( sortByItemPosition ) { - if ( cssTransitionsEnabled ) { - makeTransition = function ( properties, defaults, outside, inside ) { - if ( typeof properties === 'string' ) { - properties = [ properties ]; + return function( node, otherNode ) { + var bitmask; + if ( node.compareDocumentPosition ) { + bitmask = node.compareDocumentPosition( otherNode ); + return bitmask & 2 ? 1 : -1; } + // In old IE, we can piggy back on the mechanism for + // comparing component positions + return sortByItemPosition( node, otherNode ); + }; + }( Ractive$shared_makeQuery_sortByItemPosition ); - return function ( node, complete, params, info, isIntro ) { - var transitionEndHandler, transitionStyle, computedStyle, originalComputedStyles, startTransition, originalStyle, originalOpacity, targetOpacity, duration, delay, start, end, source, target, positionStyle, visibilityStyle, stylesToRemove; + /* Ractive/prototype/shared/makeQuery/sort.js */ + var Ractive$shared_makeQuery_sort = function( sortByDocumentPosition, sortByItemPosition ) { - params = parseTransitionParams( params ); - - duration = params.duration || defaults.duration; - easing = hyphenate( params.easing || defaults.easing ); - delay = ( params.delay || defaults.delay || 0 ) + ( ( params.stagger || defaults.stagger || 0 ) * info.i ); + return function() { + this.sort( this._isComponentQuery ? sortByItemPosition : sortByDocumentPosition ); + this._dirty = false; + }; + }( Ractive$shared_makeQuery_sortByDocumentPosition, Ractive$shared_makeQuery_sortByItemPosition ); - start = ( isIntro ? outside : inside ); - end = ( isIntro ? inside : outside ); + /* Ractive/prototype/shared/makeQuery/dirty.js */ + var Ractive$shared_makeQuery_dirty = function( runloop ) { - computedStyle = window.getComputedStyle( node ); - originalStyle = node.getAttribute( 'style' ); + return function() { + var this$0 = this; + if ( !this._dirty ) { + this._dirty = true; + // Once the DOM has been updated, ensure the query + // is correctly ordered + runloop.scheduleTask( function() { + this$0._sort(); + } ); + } + }; + }( runloop ); - // if this is an intro, we need to transition TO the original styles - if ( isIntro ) { - // hide, to avoid flashes - positionStyle = node.style.position; - visibilityStyle = node.style.visibility; - node.style.position = 'absolute'; - node.style.visibility = 'hidden'; + /* Ractive/prototype/shared/makeQuery/remove.js */ + var Ractive$shared_makeQuery_remove = function( nodeOrComponent ) { + var index = this.indexOf( this._isComponentQuery ? nodeOrComponent.instance : nodeOrComponent ); + if ( index !== -1 ) { + this.splice( index, 1 ); + } + }; - // we need to wait a beat before we can actually get values from computedStyle. - // Yeah, I know, WTF browsers - setTimeout( function () { - var i, prop; + /* Ractive/prototype/shared/makeQuery/_makeQuery.js */ + var Ractive$shared_makeQuery__makeQuery = function( defineProperties, test, cancel, sort, dirty, remove ) { - originalComputedStyles = getOriginalComputedStyles( computedStyle, properties ); - - start = outside; - end = augment( originalComputedStyles, inside ); - - // starting style - node.style.position = positionStyle; - node.style.visibility = visibilityStyle; - - setStyle( node, properties, start, params ); - - setTimeout( startTransition, 0 ); - }, delay ); + return function makeQuery( ractive, selector, live, isComponentQuery ) { + var query = []; + defineProperties( query, { + selector: { + value: selector + }, + live: { + value: live + }, + _isComponentQuery: { + value: isComponentQuery + }, + _test: { + value: test } - - // otherwise we need to transition FROM them - else { - setTimeout( function () { - var i, prop; - - originalComputedStyles = getOriginalComputedStyles( computedStyle, properties ); - - start = augment( originalComputedStyles, inside ); - end = outside; - - // ending style - setStyle( node, properties, start, params ); - - setTimeout( startTransition, 0 ); - }, delay ); + } ); + if ( !live ) { + return query; + } + defineProperties( query, { + cancel: { + value: cancel + }, + _root: { + value: ractive + }, + _sort: { + value: sort + }, + _makeDirty: { + value: dirty + }, + _remove: { + value: remove + }, + _dirty: { + value: false, + writable: true } - - startTransition = function () { - var i, prop; - - node.style[ transition + 'Duration' ] = ( duration / 1000 ) + 's'; - node.style[ transition + 'Properties' ] = properties.map( hyphenate ).join( ',' ); - node.style[ transition + 'TimingFunction' ] = easing; - - transitionEndHandler = function ( event ) { - node.removeEventListener( transitionend, transitionEndHandler ); - - if ( isIntro ) { - node.setAttribute( 'style', originalStyle || '' ); - } - - complete(); - }; - - node.addEventListener( transitionend, transitionEndHandler ); - - setStyle( node, properties, end, params ); - }; - }; + } ); + return query; }; + }( defineProperties, Ractive$shared_makeQuery_test, Ractive$shared_makeQuery_cancel, Ractive$shared_makeQuery_sort, Ractive$shared_makeQuery_dirty, Ractive$shared_makeQuery_remove ); - transitions.slide = makeTransition([ - 'height', - 'borderTopWidth', - 'borderBottomWidth', - 'paddingTop', - 'paddingBottom', - 'overflowY' - ], { duration: 400, easing: 'easeInOut' }, { overflowY: 'hidden' }, { overflowY: 'hidden' }); + /* Ractive/prototype/findAll.js */ + var Ractive$findAll = function( makeQuery ) { - transitions.fade = makeTransition( 'opacity', { - duration: 300, - easing: 'linear' - }); - - transitions.fly = makeTransition([ 'opacity', 'left', 'position' ], { - duration: 400, easing: 'easeOut' - }, { position: 'relative', left: '-500px' }, { position: 'relative', left: 0 }); - } - - - -}()); -var parseTransitionParams = function ( params ) { - if ( params === 'fast' ) { - return { duration: 200 }; - } - - if ( params === 'slow' ) { - return { duration: 600 }; - } - - if ( isNumeric( params ) ) { - return { duration: +params }; - } - - return params || {}; -}; -(function ( transitions ) { - - var typewriter, typewriteNode, typewriteTextNode; - - if ( !doc ) { - return; - } - - typewriteNode = function ( node, complete, interval ) { - var children, next, hideData; - - if ( node.nodeType === 3 ) { - typewriteTextNode( node, complete, interval ); - return; - } - - children = Array.prototype.slice.call( node.childNodes ); - - next = function () { - if ( !children.length ) { - complete(); - return; + return function Ractive$findAll( selector, options ) { + var liveQueries, query; + if ( !this.el ) { + return []; } - - typewriteNode( children.shift(), next, interval ); + options = options || {}; + liveQueries = this._liveQueries; + // Shortcut: if we're maintaining a live query with this + // selector, we don't need to traverse the parallel DOM + if ( query = liveQueries[ selector ] ) { + // Either return the exact same query, or (if not live) a snapshot + return options && options.live ? query : query.slice(); + } + query = makeQuery( this, selector, !!options.live, false ); + // Add this to the list of live queries Ractive needs to maintain, + // if applicable + if ( query.live ) { + liveQueries.push( selector ); + liveQueries[ '_' + selector ] = query; + } + this.fragment.findAll( selector, query ); + return query; }; + }( Ractive$shared_makeQuery__makeQuery ); - next(); - }; + /* Ractive/prototype/findAllComponents.js */ + var Ractive$findAllComponents = function( makeQuery ) { - typewriteTextNode = function ( node, complete, interval ) { - var str, len, loop, i; - - // text node - str = node._hiddenData; - len = str.length; - - if ( !len ) { - complete(); - return; - } - - i = 0; - - loop = setInterval( function () { - var substr, remaining, match, remainingNonWhitespace, filler; - - substr = str.substr( 0, i ); - remaining = str.substring( i ); - - match = /^\w+/.exec( remaining ); - remainingNonWhitespace = ( match ? match[0].length : 0 ); - - // add some non-breaking whitespace corresponding to the remaining length of the - // current word (only really works with monospace fonts, but better than nothing) - filler = new Array( remainingNonWhitespace + 1 ).join( '\u00a0' ); - - node.data = substr + filler; - if ( i === len ) { - clearInterval( loop ); - delete node._hiddenData; - complete(); + return function Ractive$findAllComponents( selector, options ) { + var liveQueries, query; + options = options || {}; + liveQueries = this._liveComponentQueries; + // Shortcut: if we're maintaining a live query with this + // selector, we don't need to traverse the parallel DOM + if ( query = liveQueries[ selector ] ) { + // Either return the exact same query, or (if not live) a snapshot + return options && options.live ? query : query.slice(); } + query = makeQuery( this, selector, !!options.live, true ); + // Add this to the list of live queries Ractive needs to maintain, + // if applicable + if ( query.live ) { + liveQueries.push( selector ); + liveQueries[ '_' + selector ] = query; + } + this.fragment.findAllComponents( selector, query ); + return query; + }; + }( Ractive$shared_makeQuery__makeQuery ); - i += 1; - }, interval ); + /* Ractive/prototype/findComponent.js */ + var Ractive$findComponent = function Ractive$findComponent( selector ) { + return this.fragment.findComponent( selector ); }; - typewriter = function ( node, complete, params, info, isIntro ) { - var interval, style, computedStyle, hideData; + /* utils/getPotentialWildcardMatches.js */ + var getPotentialWildcardMatches = function() { - params = parseTransitionParams( params ); - - interval = params.interval || ( params.speed ? 1000 / params.speed : ( params.duration ? node.textContent.length / params.duration : 4 ) ); - - style = node.getAttribute( 'style' ); - computedStyle = window.getComputedStyle( node ); - - node.style.visibility = 'hidden'; - - setTimeout( function () { - var computedHeight, computedWidth, computedVisibility; - - computedWidth = computedStyle.width; - computedHeight = computedStyle.height; - computedVisibility = computedStyle.visibility; - - hideData( node ); - - setTimeout( function () { - node.style.width = computedWidth; - node.style.height = computedHeight; - node.style.visibility = 'visible'; - - typewriteNode( node, function () { - node.setAttribute( 'style', style || '' ); - complete(); - }, interval ); - }, params.delay || 0 ); - }); - - hideData = function ( node ) { - var children, i; - - if ( node.nodeType === 3 ) { - node._hiddenData = '' + node.data; - node.data = ''; - - return; + var __export; + var starMaps = {}; + // This function takes a keypath such as 'foo.bar.baz', and returns + // all the variants of that keypath that include a wildcard in place + // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. + // These are then checked against the dependants map (ractive.viewmodel.depsMap) + // to see if any pattern observers are downstream of one or more of + // these wildcard keypaths (e.g. 'foo.bar.*.status') + __export = function getPotentialWildcardMatches( keypath ) { + var keys, starMap, mapper, i, result, wildcardKeypath; + keys = keypath.split( '.' ); + if ( !( starMap = starMaps[ keys.length ] ) ) { + starMap = getStarMap( keys.length ); } - - children = Array.prototype.slice.call( node.childNodes ); - i = children.length; + result = []; + mapper = function( star, i ) { + return star ? '*' : keys[ i ]; + }; + i = starMap.length; while ( i-- ) { - hideData( children[i] ); + wildcardKeypath = starMap[ i ].map( mapper ).join( '.' ); + if ( !result.hasOwnProperty( wildcardKeypath ) ) { + result.push( wildcardKeypath ); + result[ wildcardKeypath ] = true; + } } + return result; }; - }; - - transitions.typewriter = typewriter; - -}( transitions )); -(function ( Ractive ) { - - var requestFullscreen, cancelFullscreen, fullscreenElement, testDiv; - - if ( !doc ) { - return; - } - - Ractive.fullscreenEnabled = doc.fullscreenEnabled || doc.mozFullScreenEnabled || doc.webkitFullscreenEnabled; - - if ( !Ractive.fullscreenEnabled ) { - Ractive.requestFullscreen = Ractive.cancelFullscreen = noop; - return; - } - - testDiv = doc.createElement( 'div' ); - - // get prefixed name of requestFullscreen method - if ( testDiv.requestFullscreen ) { - requestFullscreen = 'requestFullscreen'; - } else if ( testDiv.mozRequestFullScreen ) { - requestFullscreen = 'mozRequestFullScreen'; - } else if ( testDiv.webkitRequestFullscreen ) { - requestFullscreen = 'webkitRequestFullscreen'; - } - - Ractive.requestFullscreen = function ( el ) { - if ( el[ requestFullscreen ] ) { - el[ requestFullscreen ](); + // This function returns all the possible true/false combinations for + // a given number - e.g. for two, the possible combinations are + // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. + // It does so by getting all the binary values between 0 and e.g. 11 + function getStarMap( num ) { + var ones = '', + max, binary, starMap, mapper, i; + if ( !starMaps[ num ] ) { + starMap = []; + while ( ones.length < num ) { + ones += 1; + } + max = parseInt( ones, 2 ); + mapper = function( digit ) { + return digit === '1'; + }; + for ( i = 0; i <= max; i += 1 ) { + binary = i.toString( 2 ); + while ( binary.length < num ) { + binary = '0' + binary; + } + starMap[ i ] = Array.prototype.map.call( binary, mapper ); + } + starMaps[ num ] = starMap; + } + return starMaps[ num ]; } - }; + return __export; + }(); - // get prefixed name of cancelFullscreen method - if ( doc.cancelFullscreen ) { - cancelFullscreen = 'cancelFullscreen'; - } else if ( doc.mozCancelFullScreen ) { - cancelFullscreen = 'mozCancelFullScreen'; - } else if ( doc.webkitCancelFullScreen ) { - cancelFullscreen = 'webkitCancelFullScreen'; - } + /* Ractive/prototype/shared/fireEvent.js */ + var Ractive$shared_fireEvent = function( getPotentialWildcardMatches ) { - Ractive.cancelFullscreen = function () { - doc[ cancelFullscreen ](); - }; + var __export; + __export = function fireEvent( ractive, eventName ) { + var options = arguments[ 2 ]; + if ( options === void 0 ) + options = {}; + if ( !eventName ) { + return; + } + if ( !options.event ) { + options.event = { + name: eventName, + context: ractive.data, + keypath: '', + // until event not included as argument default + _noArg: true + }; + } else { + options.event.name = eventName; + } + var eventNames = getPotentialWildcardMatches( eventName ); + fireEventAs( ractive, eventNames, options.event, options.args, true ); + }; - // get prefixed name of fullscreenElement property - if ( doc.fullscreenElement !== undefined ) { - fullscreenElement = 'fullscreenElement'; - } else if ( doc.mozFullScreenElement !== undefined ) { - fullscreenElement = 'mozFullScreenElement'; - } else if ( doc.webkitFullscreenElement !== undefined ) { - fullscreenElement = 'webkitFullscreenElement'; - } - - Ractive.isFullscreen = function ( el ) { - return el === doc[ fullscreenElement ]; - }; - -}( Ractive )); -Animation = function ( options ) { - var key; - - this.startTime = Date.now(); - - // from and to - for ( key in options ) { - if ( hasOwn.call( options, 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, index; - - if ( this.running ) { - timeNow = Date.now(); - elapsed = timeNow - this.startTime; - - if ( elapsed >= this.duration ) { - this.root.set( this.keypath, this.to ); - - if ( this.step ) { - this.step( 1, this.to ); + function fireEventAs( ractive, eventNames, event, args ) { + var initialFire = arguments[ 4 ]; + if ( initialFire === void 0 ) + initialFire = false; + var subscribers, i, bubble = true; + if ( event ) { + ractive.event = event; + } + for ( i = eventNames.length; i >= 0; i-- ) { + subscribers = ractive._subs[ eventNames[ i ] ]; + if ( subscribers ) { + bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble; } - - if ( this.complete ) { - this.complete( 1, this.to ); + } + if ( event ) { + delete ractive.event; + } + if ( ractive._parent && bubble ) { + if ( initialFire && ractive.component ) { + var fullName = ractive.component.name + '.' + eventNames[ eventNames.length - 1 ]; + eventNames = getPotentialWildcardMatches( fullName ); + if ( event ) { + event.component = ractive; + } } + fireEventAs( ractive._parent, eventNames, event, args ); + } + } - index = this.root._animations.indexOf( this ); - - // TODO remove this check, once we're satisifed this never happens! - if ( index === -1 && console && console.warn ) { - console.warn( 'Animation was not found' ); + function notifySubscribers( ractive, subscribers, event, args ) { + var originalEvent = null, + stopEvent = false; + if ( event && !event._noArg ) { + args = [ event ].concat( args ); + } + for ( var i = 0, len = subscribers.length; i < len; i += 1 ) { + if ( subscribers[ i ].apply( ractive, args ) === false ) { + stopEvent = true; } - - this.root._animations.splice( index, 1 ); - - this.running = false; - return false; } - - t = this.easing ? this.easing ( elapsed / this.duration ) : ( elapsed / this.duration ); - value = this.interpolator( t ); - - this.root.set( this.keypath, value ); - - if ( this.step ) { - this.step( t, value ); + if ( event && !event._noArg && stopEvent && ( originalEvent = event.original ) ) { + originalEvent.preventDefault && originalEvent.preventDefault(); + originalEvent.stopPropagation && originalEvent.stopPropagation(); } - - return true; + return !stopEvent; } + return __export; + }( getPotentialWildcardMatches ); - return false; - }, + /* Ractive/prototype/fire.js */ + var Ractive$fire = function( fireEvent ) { - stop: function () { - var index; + return function Ractive$fire( eventName ) { + var options = { + args: Array.prototype.slice.call( arguments, 1 ) + }; + fireEvent( this, eventName, options ); + }; + }( Ractive$shared_fireEvent ); - this.running = false; + /* Ractive/prototype/get.js */ + var Ractive$get = function( normaliseKeypath, resolveRef ) { - index = this.root._animations.indexOf( this ); - - // TODO remove this check, once we're satisifed this never happens! - if ( index === -1 && console && console.warn ) { - console.warn( 'Animation was not found' ); - } - - this.root._animations.splice( index, 1 ); - } -}; -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 ); + var options = { + capture: true + }; + // top-level calls should be intercepted + return function Ractive$get( keypath ) { + var value; + keypath = normaliseKeypath( keypath ); + value = this.viewmodel.get( keypath, options ); + // Create inter-component binding, if necessary + if ( value === undefined && this._parent && !this.isolated ) { + if ( resolveRef( this, keypath, this.fragment ) ) { + // creates binding as side-effect, if appropriate + value = this.viewmodel.get( keypath ); + } } - } + return value; + }; + }( normaliseKeypath, resolveRef ); - if ( this.animations.length ) { - requestAnimationFrame( this.boundTick ); - } else { - this.running = false; + /* utils/getElement.js */ + var getElement = function getElement( input ) { + var output; + if ( !input || typeof input === 'boolean' ) { + return; } - }, - - // 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(); + if ( typeof window === 'undefined' || !document || !input ) { + return null; } - } -}; -// https://gist.github.com/paulirish/1579671 -(function( vendors, lastTime, global ) { - - var x, setTimeout; - - if ( global.requestAnimationFrame ) { - requestAnimationFrame = global.requestAnimationFrame; - return; - } - - for ( x = 0; x < vendors.length && !requestAnimationFrame; ++x ) { - requestAnimationFrame = global[vendors[x]+'RequestAnimationFrame']; - } - - if ( !requestAnimationFrame ) { - setTimeout = global.setTimeout; - - 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; - }; - } - -}( ['ms', 'moz', 'webkit', 'o'], 0, global )); -(function () { - - var notifyArrayDependants, - - wrapArray, - unwrapArray, - WrappedArrayProto, - testObj, - mutatorMethods; - - - // 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, keypathsByGuid, rootIndex, keypaths; - - // If this array hasn't been wrapped, we need to wrap it - if ( !array._ractive ) { - defineProperty( array, '_ractive', { - value: { - roots: [ root ], // there may be more than one Ractive instance depending on this - keypathsByGuid: {} - }, - configurable: true - }); - - array._ractive.keypathsByGuid[ root._guid ] = [ keypath ]; - - wrapArray( array ); + // We already have a DOM node - no work to do. (Duck typing alert!) + if ( input.nodeType ) { + return input; } - - else { - roots = array._ractive.roots; - keypathsByGuid = array._ractive.keypathsByGuid; - - // Does this Ractive instance currently depend on this array? - // If not, associate them - if ( !keypathsByGuid[ root._guid ] ) { - roots[ roots.length ] = root; - keypathsByGuid[ root._guid ] = []; + // Get node from string + if ( typeof input === 'string' ) { + // try ID first + output = document.getElementById( input ); + // then as selector, if possible + if ( !output && document.querySelector ) { + output = document.querySelector( input ); } - - keypaths = keypathsByGuid[ root._guid ]; - - // If the current keypath isn't among them, add it - if ( keypaths.indexOf( keypath ) === -1 ) { - keypaths[ keypaths.length ] = keypath; + // did it work? + if ( output && output.nodeType ) { + return output; } } + // If we've been given a collection (jQuery, Zepto etc), extract the first item + if ( input[ 0 ] && input[ 0 ].nodeType ) { + return input[ 0 ]; + } + return null; }; + /* Ractive/prototype/insert.js */ + var Ractive$insert = function( Hook, getElement ) { - // Unregister keypath from array - unregisterKeypathFromArray = function ( array, keypath, root ) { - var roots, keypathsByGuid, rootIndex, keypaths, keypathIndex; + var __export; + var insertHook = new Hook( 'insert' ); + __export = function Ractive$insert( target, anchor ) { + if ( !this.fragment.rendered ) { + // TODO create, and link to, documentation explaining this + throw new Error( 'The API has changed - you must call `ractive.render(target[, anchor])` to render your Ractive instance. Once rendered you can use `ractive.insert()`.' ); + } + target = getElement( target ); + anchor = getElement( anchor ) || null; + if ( !target ) { + throw new Error( 'You must specify a valid target to insert into' ); + } + target.insertBefore( this.detach(), anchor ); + this.el = target; + ( target.__ractive_instances__ || ( target.__ractive_instances__ = [] ) ).push( this ); + this.detached = null; + fireInsertHook( this ); + }; - 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' ); + function fireInsertHook( ractive ) { + insertHook.fire( ractive ); + ractive.findAllComponents( '*' ).forEach( function( child ) { + fireInsertHook( child.instance ); + } ); } + return __export; + }( Ractive$shared_hooks_Hook, getElement ); - roots = array._ractive.roots; - keypathsByGuid = array._ractive.keypathsByGuid; + /* Ractive/prototype/merge.js */ + var Ractive$merge = function( runloop, isArray, normaliseKeypath ) { - if ( !keypathsByGuid[ root._guid ] ) { - 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' ); - } - - keypaths = keypathsByGuid[ root._guid ]; - 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( roots.indexOf( root ), 1 ); - keypathsByGuid[ root._guid ] = null; - } - - if ( !roots.length ) { - unwrapArray( array ); // It's good to clean up after ourselves - } - }; - - - notifyArrayDependants = function ( array, methodName, args ) { - var processRoots, - processRoot, - processKeypaths, - processKeypath, - queueAllDependants, - queueDependants, - keypathsByGuid; - - keypathsByGuid = array._ractive.keypathsByGuid; - - processRoots = function ( roots ) { - var i = roots.length; - while ( i-- ) { - processRoot( roots[i] ); + return function Ractive$merge( keypath, array, options ) { + var currentArray, promise; + keypath = normaliseKeypath( keypath ); + currentArray = this.viewmodel.get( keypath ); + // If either the existing value or the new value isn't an + // array, just do a regular set + if ( !isArray( currentArray ) || !isArray( array ) ) { + return this.set( keypath, array, options && options.complete ); } + // Manage transitions + promise = runloop.start( this, true ); + this.viewmodel.merge( keypath, currentArray, array, options ); + runloop.end(); + // attach callback as fulfilment handler, if specified + if ( options && options.complete ) { + promise.then( options.complete ); + } + return promise; }; + }( runloop, isArray, normaliseKeypath ); - processRoot = function ( root ) { - var previousTransitionManager = root._transitionManager, transitionManager; + /* Ractive/prototype/observe/Observer.js */ + var Ractive$observe_Observer = function( runloop, isEqual ) { - root._transitionManager = transitionManager = makeTransitionManager( root, noop ); - processKeypaths( root, keypathsByGuid[ root._guid ] ); - root._transitionManager = previousTransitionManager; - - transitionManager.ready(); + var Observer = function( ractive, keypath, callback, options ) { + this.root = ractive; + this.keypath = keypath; + this.callback = callback; + this.defer = options.defer; + // default to root as context, but allow it to be overridden + this.context = options && options.context ? options.context : ractive; }; - - processKeypaths = function ( root, keypaths ) { - var i = keypaths.length; - while ( i-- ) { - processKeypath( root, keypaths[i] ); + Observer.prototype = { + init: function( immediate ) { + this.value = this.root.get( this.keypath ); + if ( immediate !== false ) { + this.update(); + } else { + this.oldValue = this.value; + } + }, + setValue: function( value ) { + var this$0 = this; + if ( !isEqual( value, this.value ) ) { + this.value = value; + if ( this.defer && this.ready ) { + runloop.scheduleTask( function() { + return this$0.update(); + } ); + } else { + this.update(); + } + } + }, + update: function() { + // Prevent infinite loops + if ( this.updating ) { + return; + } + this.updating = true; + this.callback.call( this.context, this.value, this.oldValue, this.keypath ); + this.oldValue = this.value; + this.updating = false; } }; + return Observer; + }( runloop, isEqual ); - processKeypath = function ( root, keypath ) { - var depsByKeypath, deps, keys, upstreamQueue, smartUpdateQueue, dumbUpdateQueue, i, j, item; + /* shared/getMatchingKeypaths.js */ + var getMatchingKeypaths = function( isArray ) { - // We don't do root.set(), because we don't want to update DOM sections - // using the normal method - we want to do a smart update whereby elements - // are removed from the right place. But we do need to clear the cache - clearCache( root, keypath ); - - // find dependants. If any are DOM sections, we do a smart update - // rather than a ractive.set() blunderbuss - smartUpdateQueue = []; - dumbUpdateQueue = []; - - for ( i=0; i<root._deps.length; i+=1 ) { // we can't cache root._deps.length as it may change! - depsByKeypath = root._deps[i]; - - if ( !depsByKeypath ) { - continue; + return function getMatchingKeypaths( ractive, pattern ) { + var keys, key, matchingKeypaths; + keys = pattern.split( '.' ); + matchingKeypaths = [ '' ]; + while ( key = keys.shift() ) { + if ( key === '*' ) { + // expand to find all valid child keypaths + matchingKeypaths = matchingKeypaths.reduce( expand, [] ); + } else { + if ( matchingKeypaths[ 0 ] === '' ) { + // first key + matchingKeypaths[ 0 ] = key; + } else { + matchingKeypaths = matchingKeypaths.map( concatenate( key ) ); + } } + } + return matchingKeypaths; - deps = depsByKeypath[ keypath ]; - - if ( deps ) { - queueDependants( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue ); - - // we may have some deferred evaluators to process - processDeferredUpdates( root ); - - while ( smartUpdateQueue.length ) { - smartUpdateQueue.pop().smartUpdate( methodName, args ); + function expand( matchingKeypaths, keypath ) { + var value, key, childKeypath; + value = ractive.viewmodel.wrapped[ keypath ] ? ractive.viewmodel.wrapped[ keypath ].get() : ractive.get( keypath ); + for ( key in value ) { + if ( value.hasOwnProperty( key ) && ( key !== '_ractive' || !isArray( value ) ) ) { + // for benefit of IE8 + childKeypath = keypath ? keypath + '.' + key : key; + matchingKeypaths.push( childKeypath ); } - - while ( dumbUpdateQueue.length ) { - dumbUpdateQueue.pop().update(); - } } + return matchingKeypaths; } - // we may have some deferred attributes to process - processDeferredUpdates( root ); - - // Finally, notify direct dependants of upstream keypaths... - upstreamQueue = []; - - keys = splitKeypath( keypath ); - while ( keys.length ) { - keys.pop(); - upstreamQueue[ upstreamQueue.length ] = keys.join( '.' ); + function concatenate( key ) { + return function( keypath ) { + return keypath ? keypath + '.' + key : key; + }; } + }; + }( isArray ); - notifyMultipleDependants( root, upstreamQueue, true ); + /* Ractive/prototype/observe/getPattern.js */ + var Ractive$observe_getPattern = function( getMatchingKeypaths ) { - // length property has changed - notify dependants - // TODO in some cases (e.g. todo list example, when marking all as complete, then - // adding a new item (which should deactivate the 'all complete' checkbox - // but doesn't) this needs to happen before other updates. But doing so causes - // other mental problems. not sure what's going on... - notifyDependants( root, keypath + '.length', true ); + return function getPattern( ractive, pattern ) { + var matchingKeypaths, values; + matchingKeypaths = getMatchingKeypaths( ractive, pattern ); + values = {}; + matchingKeypaths.forEach( function( keypath ) { + values[ keypath ] = ractive.get( keypath ); + } ); + return values; }; + }( getMatchingKeypaths ); - // TODO can we get rid of this whole queueing nonsense? - queueDependants = function ( root, keypath, deps, smartUpdateQueue, dumbUpdateQueue ) { - var k, dependant; + /* Ractive/prototype/observe/PatternObserver.js */ + var Ractive$observe_PatternObserver = function( runloop, isEqual, getPattern ) { - k = deps.length; - while ( k-- ) { - dependant = deps[k]; - - // references need to get processed before mustaches - if ( dependant.type === REFERENCE ) { - dependant.update(); - //dumbUpdateQueue[ dumbUpdateQueue.length ] = dependant; - } - - // is this a DOM section? - else if ( dependant.keypath === keypath && dependant.type === SECTION /*&& dependant.parentNode*/ ) { - smartUpdateQueue[ smartUpdateQueue.length ] = dependant; - + var PatternObserver, wildcard = /\*/, + slice = Array.prototype.slice; + PatternObserver = function( ractive, keypath, callback, options ) { + this.root = ractive; + this.callback = callback; + this.defer = options.defer; + this.keypath = keypath; + this.regex = new RegExp( '^' + keypath.replace( /\./g, '\\.' ).replace( /\*/g, '([^\\.]+)' ) + '$' ); + this.values = {}; + if ( this.defer ) { + this.proxies = []; + } + // default to root as context, but allow it to be overridden + this.context = options && options.context ? options.context : ractive; + }; + PatternObserver.prototype = { + init: function( immediate ) { + var values, keypath; + values = getPattern( this.root, this.keypath ); + if ( immediate !== false ) { + for ( keypath in values ) { + if ( values.hasOwnProperty( keypath ) ) { + this.update( keypath ); + } + } } else { - dumbUpdateQueue[ dumbUpdateQueue.length ] = dependant; + this.values = values; } + }, + update: function( keypath ) { + var this$0 = this; + var values; + if ( wildcard.test( keypath ) ) { + values = getPattern( this.root, keypath ); + for ( keypath in values ) { + if ( values.hasOwnProperty( keypath ) ) { + this.update( keypath ); + } + } + return; + } + // special case - array mutation should not trigger `array.*` + // pattern observer with `array.length` + if ( this.root.viewmodel.implicitChanges[ keypath ] ) { + return; + } + if ( this.defer && this.ready ) { + runloop.scheduleTask( function() { + return this$0.getProxy( keypath ).update(); + } ); + return; + } + this.reallyUpdate( keypath ); + }, + reallyUpdate: function( keypath ) { + var value, keys, args; + value = this.root.viewmodel.get( keypath ); + // Prevent infinite loops + if ( this.updating ) { + this.values[ keypath ] = value; + return; + } + this.updating = true; + if ( !isEqual( value, this.values[ keypath ] ) || !this.ready ) { + keys = slice.call( this.regex.exec( keypath ), 1 ); + args = [ + value, + this.values[ keypath ], + keypath + ].concat( keys ); + this.callback.apply( this.context, args ); + this.values[ keypath ] = value; + } + this.updating = false; + }, + getProxy: function( keypath ) { + var self = this; + if ( !this.proxies[ keypath ] ) { + this.proxies[ keypath ] = { + update: function() { + self.reallyUpdate( keypath ); + } + }; + } + return this.proxies[ keypath ]; } }; + return PatternObserver; + }( runloop, isEqual, Ractive$observe_getPattern ); - processRoots( array._ractive.roots ); - }; + /* Ractive/prototype/observe/getObserverFacade.js */ + var Ractive$observe_getObserverFacade = function( normaliseKeypath, Observer, PatternObserver ) { - - - - - - 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; - notifyArrayDependants( this, methodName, arguments ); - this._ractive.setting = false; - - return result; - }; - - defineProperty( 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]; - defineProperty( array, methodName, { - value: WrappedArrayProto[ methodName ], - configurable: true - }); + var wildcard = /\*/, + emptyObject = {}; + return function getObserverFacade( ractive, keypath, callback, options ) { + var observer, isPatternObserver, cancelled; + keypath = normaliseKeypath( keypath ); + options = options || emptyObject; + // pattern observers are treated differently + if ( wildcard.test( keypath ) ) { + observer = new PatternObserver( ractive, keypath, callback, options ); + ractive.viewmodel.patternObservers.push( observer ); + isPatternObserver = true; + } else { + observer = new Observer( ractive, keypath, callback, options ); } + ractive.viewmodel.register( keypath, observer, isPatternObserver ? 'patternObservers' : 'observers' ); + observer.init( options.init ); + // This flag allows observers to initialise even with undefined values + observer.ready = true; + return { + cancel: function() { + var index; + if ( cancelled ) { + return; + } + if ( isPatternObserver ) { + index = ractive.viewmodel.patternObservers.indexOf( observer ); + ractive.viewmodel.patternObservers.splice( index, 1 ); + ractive.viewmodel.unregister( keypath, observer, 'patternObservers' ); + } else { + ractive.viewmodel.unregister( keypath, observer, 'observers' ); + } + cancelled = true; + } + }; }; + }( normaliseKeypath, Ractive$observe_Observer, Ractive$observe_PatternObserver ); - unwrapArray = function ( array ) { - var i; + /* Ractive/prototype/observe.js */ + var Ractive$observe = function( isObject, getObserverFacade ) { - i = mutatorMethods.length; + return function Ractive$observe( keypath, callback, options ) { + var observers, map, keypaths, i; + // Allow a map of keypaths to handlers + if ( isObject( keypath ) ) { + options = callback; + map = keypath; + observers = []; + for ( keypath in map ) { + if ( map.hasOwnProperty( keypath ) ) { + callback = map[ keypath ]; + observers.push( this.observe( keypath, callback, options ) ); + } + } + return { + cancel: function() { + while ( observers.length ) { + observers.pop().cancel(); + } + } + }; + } + // Allow `ractive.observe( callback )` - i.e. observe entire model + if ( typeof keypath === 'function' ) { + options = callback; + callback = keypath; + keypath = ''; + return getObserverFacade( this, keypath, callback, options ); + } + keypaths = keypath.split( ' ' ); + // Single keypath + if ( keypaths.length === 1 ) { + return getObserverFacade( this, keypath, callback, options ); + } + // Multiple space-separated keypaths + observers = []; + i = keypaths.length; while ( i-- ) { - delete array[ mutatorMethods[i] ]; + keypath = keypaths[ i ]; + if ( keypath ) { + observers.push( getObserverFacade( this, keypath, callback, options ) ); + } } - - delete array._ractive; + return { + cancel: function() { + while ( observers.length ) { + observers.pop().cancel(); + } + } + }; }; - } + }( isObject, Ractive$observe_getObserverFacade ); -}()); -(function () { - - var propertyNames, determineNameAndNamespace, setStaticAttribute, determinePropertyName, isAttributeSelfUpdating, isAttributeBindable; - - // the property name equivalents for element attributes, where they differ - // from the lowercased attribute name - propertyNames = { - 'accept-charset': 'acceptCharset', - accesskey: 'accessKey', - bgcolor: 'bgColor', - 'class': 'className', - codebase: 'codeBase', - colspan: 'colSpan', - contenteditable: 'contentEditable', - datetime: 'dateTime', - dirname: 'dirName', - 'for': 'htmlFor', - 'http-equiv': 'httpEquiv', - ismap: 'isMap', - maxlength: 'maxLength', - novalidate: 'noValidate', - pubdate: 'pubDate', - readonly: 'readOnly', - rowspan: 'rowSpan', - tabindex: 'tabIndex', - usemap: 'useMap' + /* Ractive/prototype/shared/trim.js */ + var Ractive$shared_trim = function( str ) { + return str.trim(); }; - // Attribute - DomAttribute = function ( options ) { - - determineNameAndNamespace( this, options.name ); - - // if it's an empty attribute, or just a straight key-value pair, with no - // mustache shenanigans, set the attribute accordingly and go home - if ( options.value === null || typeof options.value === 'string' ) { - setStaticAttribute( this, options ); - return; - } - - // otherwise we need to do some work - this.root = options.root; - this.element = options.element; - this.parentNode = options.parentNode; - this.lcName = this.name.toLowerCase(); - - // share parentFragment with parent element - this.parentFragment = this.element.parentFragment; - - this.fragment = new StringFragment({ - descriptor: options.value, - root: this.root, - owner: this, - contextStack: options.contextStack - }); - - - // if we're not rendering (i.e. we're just stringifying), we can stop here - if ( !this.parentNode ) { - return; - } - - - // can we establish this attribute's property name equivalent? - determinePropertyName( this, options ); - - // determine whether this attribute can be marked as self-updating - this.selfUpdating = isAttributeSelfUpdating( this ); - - // if two-way binding is enabled, and we've got a dynamic `value` attribute, and this is an input or textarea, set up two-way binding - this.isBindable = isAttributeBindable( this ); - - if ( this.isBindable && this.propertyName === 'name' ) { - // name attribute is a special case - it is the only two-way attribute that updates - // the viewmodel based on the value of another attribute. For that reason it must wait - // until the node has been initialised, and the viewmodel has had its first two-way - // update, before updating itself (otherwise it may disable a checkbox or radio that - // was enabled in the template) - this.isTwowayNameAttr = true; - } - - // mark as ready - this.ready = true; + /* Ractive/prototype/shared/notEmptyString.js */ + var Ractive$shared_notEmptyString = function( str ) { + return str !== ''; }; - DomAttribute.prototype = { - bind: function ( lazy ) { - var self = this, node = this.parentNode, interpolator, keypath, index, options, option, i, len; + /* Ractive/prototype/off.js */ + var Ractive$off = function( trim, notEmptyString ) { - if ( !this.fragment ) { - return false; // report failure - } - - // TODO refactor this? Couldn't the interpolator have got a keypath via an expression? - // Check this is a suitable candidate for two-way binding - i.e. it is - // a single interpolator, which isn't an expression - if ( - this.fragment.items.length !== 1 || - this.fragment.items[0].type !== INTERPOLATOR || - ( !this.fragment.items[0].keypath && !this.fragment.items[0].ref ) - ) { - if ( this.root.debug ) { - if ( console && console.warn ) { - console.warn( 'Not a valid two-way data binding candidate - must be a single interpolator:', this.fragment.items ); - } + return function Ractive$off( eventName, callback ) { + var this$0 = this; + var eventNames; + // if no arguments specified, remove all callbacks + if ( !eventName ) { + // TODO use this code instead, once the following issue has been resolved + // in PhantomJS (tests are unpassable otherwise!) + // https://github.com/ariya/phantomjs/issues/11856 + // defineProperty( this, '_subs', { value: create( null ), configurable: true }); + for ( eventName in this._subs ) { + delete this._subs[ eventName ]; } - return false; // report failure + } else { + // Handle multiple space-separated event names + eventNames = eventName.split( ' ' ).map( trim ).filter( notEmptyString ); + eventNames.forEach( function( eventName ) { + var subscribers, index; + // If we have subscribers for this event... + if ( subscribers = this$0._subs[ eventName ] ) { + // ...if a callback was specified, only remove that + if ( callback ) { + index = subscribers.indexOf( callback ); + if ( index !== -1 ) { + subscribers.splice( index, 1 ); + } + } else { + this$0._subs[ eventName ] = []; + } + } + } ); } + return this; + }; + }( Ractive$shared_trim, Ractive$shared_notEmptyString ); - this.interpolator = this.fragment.items[0]; + /* Ractive/prototype/on.js */ + var Ractive$on = function( trim, notEmptyString ) { - // Hmmm. Not sure if this is the best way to handle this ambiguity... - // - // Let's say we were given `value="{{bar}}"`. If the context stack was - // context stack was `["foo"]`, and `foo.bar` *wasn't* `undefined`, the - // keypath would be `foo.bar`. Then, any user input would result in - // `foo.bar` being updated. - // - // If, however, `foo.bar` *was* undefined, and so was `bar`, we would be - // left with an unresolved partial keypath - so we are forced to make an - // assumption. That assumption is that the input in question should - // be forced to resolve to `bar`, and any user input would affect `bar` - // and not `foo.bar`. - // - // Did that make any sense? No? Oh. Sorry. Well the moral of the story is - // be explicit when using two-way data-binding about what keypath you're - // updating. Using it in lists is probably a recipe for confusion... - this.keypath = this.interpolator.keypath || this.interpolator.descriptor.r; - - - // select - if ( node.tagName === 'SELECT' && this.propertyName === 'value' ) { - // We need to know if one of the options was selected, so we - // can initialise the viewmodel. To do that we need to jump - // through a couple of hoops - options = node.getElementsByTagName( 'option' ); - - len = options.length; - for ( i=0; i<len; i+=1 ) { - option = options[i]; - if ( option.hasAttribute( 'selected' ) ) { // not option.selected - won't work here - this.root.set( this.keypath, option.value ); - break; + return function Ractive$on( eventName, callback ) { + var this$0 = this; + var self = this, + listeners, n, eventNames; + // allow mutliple listeners to be bound in one go + if ( typeof eventName === 'object' ) { + listeners = []; + for ( n in eventName ) { + if ( eventName.hasOwnProperty( n ) ) { + listeners.push( this.on( n, eventName[ n ] ) ); } } - } - - // checkboxes and radio buttons - if ( node.type === 'checkbox' || node.type === 'radio' ) { - // We might have a situation like this: - // - // <input type='radio' name='{{colour}}' value='red'> - // <input type='radio' name='{{colour}}' value='blue'> - // <input type='radio' name='{{colour}}' value='green'> - // - // In this case we want to set `colour` to the value of whichever option - // is checked. (We assume that a value attribute has been supplied.) - - if ( this.propertyName === 'name' ) { - // replace actual name attribute - node.name = '{{' + this.keypath + '}}'; - - this.updateViewModel = function () { - if ( node.checked ) { - self.root.set( self.keypath, node.value ); + return { + cancel: function() { + var listener; + while ( listener = listeners.pop() ) { + listener.cancel(); } - }; + } + }; + } + // Handle multiple space-separated event names + eventNames = eventName.split( ' ' ).map( trim ).filter( notEmptyString ); + eventNames.forEach( function( eventName ) { + ( this$0._subs[ eventName ] || ( this$0._subs[ eventName ] = [] ) ).push( callback ); + } ); + return { + cancel: function() { + self.off( eventName, callback ); } + }; + }; + }( Ractive$shared_trim, Ractive$shared_notEmptyString ); + /* shared/getNewIndices.js */ + var getNewIndices = function() { - // Or, we might have a situation like this: - // - // <input type='checkbox' checked='{{active}}'> - // - // Here, we want to set `active` to true or false depending on whether - // the input is checked. - - else if ( this.propertyName === 'checked' ) { - this.updateViewModel = function () { - self.root.set( self.keypath, node.checked ); - }; - } + var __export; + // This function takes an array, the name of a mutator method, and the + // arguments to call that mutator method with, and returns an array that + // maps the old indices to their new indices. + // So if you had something like this... + // + // array = [ 'a', 'b', 'c', 'd' ]; + // array.push( 'e' ); + // + // ...you'd get `[ 0, 1, 2, 3 ]` - in other words, none of the old indices + // have changed. If you then did this... + // + // array.unshift( 'z' ); + // + // ...the indices would be `[ 1, 2, 3, 4, 5 ]` - every item has been moved + // one higher to make room for the 'z'. If you removed an item, the new index + // would be -1... + // + // array.splice( 2, 2 ); + // + // ...this would result in [ 0, 1, -1, -1, 2, 3 ]. + // + // This information is used to enable fast, non-destructive shuffling of list + // sections when you do e.g. `ractive.splice( 'items', 2, 2 ); + __export = function getNewIndices( array, methodName, args ) { + var spliceArguments, len, newIndices = [], + removeStart, removeEnd, balance, i; + spliceArguments = getSpliceEquivalent( array, methodName, args ); + if ( !spliceArguments ) { + return null; } - - else { - // Otherwise we've probably got a situation like this: - // - // <input value='{{name}}'> - // - // in which case we just want to set `name` whenever the user enters text. - // The same applies to selects and textareas - this.updateViewModel = function () { - var value; - - value = node.value; - - // special cases - if ( value === '0' ) { - value = 0; + len = array.length; + balance = spliceArguments.length - 2 - spliceArguments[ 1 ]; + removeStart = Math.min( len, spliceArguments[ 0 ] ); + removeEnd = removeStart + spliceArguments[ 1 ]; + for ( i = 0; i < removeStart; i += 1 ) { + newIndices.push( i ); + } + for ( ; i < removeEnd; i += 1 ) { + newIndices.push( -1 ); + } + for ( ; i < len; i += 1 ) { + newIndices.push( i + balance ); + } + return newIndices; + }; + // The pop, push, shift an unshift methods can all be represented + // as an equivalent splice + function getSpliceEquivalent( array, methodName, args ) { + switch ( methodName ) { + case 'splice': + if ( args[ 0 ] !== undefined && args[ 0 ] < 0 ) { + args[ 0 ] = array.length + Math.max( args[ 0 ], -array.length ); } - - else if ( value !== '' ) { - value = +value || value; + while ( args.length < 2 ) { + args.push( 0 ); } - - // Note: we're counting on `this.root.set` recognising that `value` is - // already what it wants it to be, and short circuiting the process. - // Rather than triggering an infinite loop... - self.root.set( self.keypath, value ); - }; + // ensure we only remove elements that exist + args[ 1 ] = Math.min( args[ 1 ], array.length - args[ 0 ] ); + return args; + case 'sort': + case 'reverse': + return null; + case 'pop': + if ( array.length ) { + return [ + array.length - 1, + 1 + ]; + } + return null; + case 'push': + return [ + array.length, + 0 + ].concat( args ); + case 'shift': + return [ + 0, + 1 + ]; + case 'unshift': + return [ + 0, + 0 + ].concat( args ); } - + } + return __export; + }(); - // if we figured out how to bind changes to the viewmodel, add the event listeners - if ( this.updateViewModel ) { - this.twoway = true; + /* Ractive/prototype/shared/makeArrayMethod.js */ + var Ractive$shared_makeArrayMethod = function( isArray, runloop, getNewIndices ) { - this.boundEvents = [ 'change', 'click', 'blur' ]; // TODO click only in IE? - - if ( !lazy ) { - this.boundEvents[3] = 'input'; - - // this is a hack to see if we're in IE - if so, we probably need to add - // a keyup listener as well, since in IE8 the input event doesn't fire, - // and in IE9 it doesn't fire when text is deleted - if ( node.attachEvent ) { - this.boundEvents[4] = 'keyup'; - } + var arrayProto = Array.prototype; + return function( methodName ) { + return function( keypath ) { + var SLICE$0 = Array.prototype.slice; + var args = SLICE$0.call( arguments, 1 ); + var array, newIndices = [], + len, promise, result; + array = this.get( keypath ); + len = array.length; + if ( !isArray( array ) ) { + throw new Error( 'Called ractive.' + methodName + '(\'' + keypath + '\'), but \'' + keypath + '\' does not refer to an array' ); } - - i = this.boundEvents.length; - while ( i-- ) { - node.addEventListener( this.boundEvents[i], this.updateViewModel ); + newIndices = getNewIndices( array, methodName, args ); + result = arrayProto[ methodName ].apply( array, args ); + promise = runloop.start( this, true ).then( function() { + return result; + } ); + if ( !!newIndices ) { + this.viewmodel.smartUpdate( keypath, array, newIndices ); + } else { + this.viewmodel.mark( keypath ); } - } - }, + runloop.end(); + return promise; + }; + }; + }( isArray, runloop, getNewIndices ); - updateBindings: function () { - // if the fragment this attribute belongs to gets reassigned (as a result of - // as section being updated via an array shift, unshift or splice), this - // attribute needs to recognise that its keypath has changed - this.keypath = this.interpolator.keypath || this.interpolator.r; + /* Ractive/prototype/pop.js */ + var Ractive$pop = function( makeArrayMethod ) { - // if we encounter the special case described above, update the name attribute - if ( this.propertyName === 'name' ) { - // replace actual name attribute - this.parentNode.name = '{{' + this.keypath + '}}'; - } - }, + return makeArrayMethod( 'pop' ); + }( Ractive$shared_makeArrayMethod ); - teardown: function () { - var i; + /* Ractive/prototype/push.js */ + var Ractive$push = function( makeArrayMethod ) { - if ( this.boundEvents ) { - i = this.boundEvents.length; + return makeArrayMethod( 'push' ); + }( Ractive$shared_makeArrayMethod ); - while ( i-- ) { - this.parentNode.removeEventListener( this.boundEvents[i], this.updateViewModel ); + /* global/css.js */ + var global_css = function( circular, isClient, removeFromArray ) { + + var css, update, runloop, styleElement, head, styleSheet, inDom, prefix = '/* Ractive.js component styles */\n', + componentsInPage = {}, + styles = []; + if ( !isClient ) { + css = null; + } else { + circular.push( function() { + runloop = circular.runloop; + } ); + styleElement = document.createElement( 'style' ); + styleElement.type = 'text/css'; + head = document.getElementsByTagName( 'head' )[ 0 ]; + inDom = false; + // Internet Exploder won't let you use styleSheet.innerHTML - we have to + // use styleSheet.cssText instead + styleSheet = styleElement.styleSheet; + update = function() { + var css; + if ( styles.length ) { + css = prefix + styles.join( ' ' ); + if ( styleSheet ) { + styleSheet.cssText = css; + } else { + styleElement.innerHTML = css; + } + if ( !inDom ) { + head.appendChild( styleElement ); + inDom = true; + } + } else if ( inDom ) { + head.removeChild( styleElement ); + inDom = false; } - } + }; + css = { + add: function( Component ) { + if ( !Component.css ) { + return; + } + if ( !componentsInPage[ Component._guid ] ) { + // we create this counter so that we can in/decrement it as + // instances are added and removed. When all components are + // removed, the style is too + componentsInPage[ Component._guid ] = 0; + styles.push( Component.css ); + update(); + } + componentsInPage[ Component._guid ] += 1; + }, + remove: function( Component ) { + if ( !Component.css ) { + return; + } + componentsInPage[ Component._guid ] -= 1; + if ( !componentsInPage[ Component._guid ] ) { + removeFromArray( styles, Component.css ); + runloop.scheduleTask( update ); + } + } + }; + } + return css; + }( circular, isClient, removeFromArray ); - // ignore non-dynamic attributes - if ( this.fragment ) { - this.fragment.teardown(); - } - }, + /* Ractive/prototype/render.js */ + var Ractive$render = function( css, Hook, getElement, runloop ) { - bubble: function () { - // If an attribute's text fragment contains a single item, we can - // update the DOM immediately... - if ( this.selfUpdating ) { - this.update(); + var renderHook = new Hook( 'render' ), + completeHook = new Hook( 'complete' ); + return function Ractive$render( target, anchor ) { + var this$0 = this; + var promise, instances, transitionsEnabled; + // if `noIntro` is `true`, temporarily disable transitions + transitionsEnabled = this.transitionsEnabled; + if ( this.noIntro ) { + this.transitionsEnabled = false; } - - // otherwise we want to register it as a deferred attribute, to be - // updated once all the information is in, to prevent unnecessary - // DOM manipulation - else if ( !this.deferred && this.ready ) { - this.root._defAttrs[ this.root._defAttrs.length ] = this; - this.deferred = true; + promise = runloop.start( this, true ); + runloop.scheduleTask( function() { + return renderHook.fire( this$0 ); + }, true ); + if ( this.fragment.rendered ) { + throw new Error( 'You cannot call ractive.render() on an already rendered instance! Call ractive.unrender() first' ); } - }, - - update: function () { - var value, lowerCaseName; - - if ( !this.ready ) { - return this; // avoid items bubbling to the surface when we're still initialising + target = getElement( target ) || this.el; + anchor = getElement( anchor ) || this.anchor; + this.el = target; + this.anchor = anchor; + // Add CSS, if applicable + if ( this.constructor.css ) { + css.add( this.constructor ); } - - if ( this.twoway ) { - // TODO compare against previous? - - lowerCaseName = this.lcName; - value = this.interpolator.value; - - // special case - if we have an element like this: - // - // <input type='radio' name='{{colour}}' value='red'> - // - // and `colour` has been set to 'red', we don't want to change the name attribute - // to red, we want to indicate that this is the selected option, by setting - // input.checked = true - if ( lowerCaseName === 'name' && ( this.parentNode.type === 'checkbox' || this.parentNode.type === 'radio' ) ) { - if ( value === this.parentNode.value ) { - this.parentNode.checked = true; - } else { - this.parentNode.checked = false; - } - - return this; + if ( target ) { + if ( !( instances = target.__ractive_instances__ ) ) { + target.__ractive_instances__ = [ this ]; + } else { + instances.push( this ); } - - // don't programmatically update focused element - if ( doc.activeElement === this.parentNode ) { - return this; + if ( anchor ) { + target.insertBefore( this.fragment.render(), anchor ); + } else { + target.appendChild( this.fragment.render() ); } } + runloop.end(); + this.transitionsEnabled = transitionsEnabled; + // It is now more problematic to know if the complete hook + // would fire. Method checking is straight-forward, but would + // also require preflighting event subscriptions. Which seems + // like more work then just letting the promise happen. + // But perhaps I'm wrong about that... + promise.then( function() { + return completeHook.fire( this$0 ); + } ); + return promise; + }; + }( global_css, Ractive$shared_hooks_Hook, getElement, runloop ); - value = this.fragment.getValue(); + /* virtualdom/Fragment/prototype/bubble.js */ + var virtualdom_Fragment$bubble = function Fragment$bubble() { + this.dirtyValue = this.dirtyArgs = true; + if ( this.bound && typeof this.owner.bubble === 'function' ) { + this.owner.bubble(); + } + }; - if ( value === undefined ) { - value = ''; + /* virtualdom/Fragment/prototype/detach.js */ + var virtualdom_Fragment$detach = function Fragment$detach() { + var docFrag; + if ( this.items.length === 1 ) { + return this.items[ 0 ].detach(); + } + docFrag = document.createDocumentFragment(); + this.items.forEach( function( item ) { + var node = item.detach(); + // TODO The if {...} wasn't previously required - it is now, because we're + // forcibly detaching everything to reorder sections after an update. That's + // a non-ideal brute force approach, implemented to get all the tests to pass + // - as soon as it's replaced with something more elegant, this should + // revert to `docFrag.appendChild( item.detach() )` + if ( node ) { + docFrag.appendChild( node ); } + } ); + return docFrag; + }; - if ( value !== this.value ) { - if ( this.useProperty ) { - this.parentNode[ this.propertyName ] = value; - return this; + /* virtualdom/Fragment/prototype/find.js */ + var virtualdom_Fragment$find = function Fragment$find( selector ) { + var i, len, item, queryResult; + if ( this.items ) { + len = this.items.length; + for ( i = 0; i < len; i += 1 ) { + item = this.items[ i ]; + if ( item.find && ( queryResult = item.find( selector ) ) ) { + return queryResult; } + } + return null; + } + }; - if ( this.namespace ) { - this.parentNode.setAttributeNS( this.namespace, this.name, value ); - return this; + /* virtualdom/Fragment/prototype/findAll.js */ + var virtualdom_Fragment$findAll = function Fragment$findAll( selector, query ) { + var i, len, item; + if ( this.items ) { + len = this.items.length; + for ( i = 0; i < len; i += 1 ) { + item = this.items[ i ]; + if ( item.findAll ) { + item.findAll( selector, query ); } + } + } + return query; + }; - if ( this.lcName === 'id' ) { - if ( this.value !== undefined ) { - this.root.nodes[ this.value ] = undefined; - } - - this.root.nodes[ value ] = this.parentNode; + /* virtualdom/Fragment/prototype/findAllComponents.js */ + var virtualdom_Fragment$findAllComponents = function Fragment$findAllComponents( selector, query ) { + var i, len, item; + if ( this.items ) { + len = this.items.length; + for ( i = 0; i < len; i += 1 ) { + item = this.items[ i ]; + if ( item.findAllComponents ) { + item.findAllComponents( selector, query ); } - - this.parentNode.setAttribute( this.name, value ); - - this.value = value; } - - return this; - }, - - toString: function () { - var str; - - if ( this.value === null ) { - return this.name; - } - - // TODO don't use JSON.stringify? - - if ( !this.fragment ) { - return this.name + '=' + JSON.stringify( this.value ); - } - - // TODO deal with boolean attributes correctly - str = this.fragment.toString(); - - return this.name + '=' + JSON.stringify( str ); } + return query; }; - - // Helper functions - determineNameAndNamespace = function ( attribute, name ) { - var colonIndex, namespacePrefix; - - // are we dealing with a namespaced attribute, e.g. xlink:href? - colonIndex = name.indexOf( ':' ); - if ( colonIndex !== -1 ) { - - // looks like we are, yes... - namespacePrefix = name.substr( 0, colonIndex ); - - // ...unless it's a namespace *declaration*, which we ignore (on the assumption - // that only valid namespaces will be used) - if ( namespacePrefix !== 'xmlns' ) { - name = name.substring( colonIndex + 1 ); - - attribute.name = name; - attribute.namespace = namespaces[ namespacePrefix ]; - - if ( !attribute.namespace ) { - throw 'Unknown namespace ("' + namespacePrefix + '")'; + /* virtualdom/Fragment/prototype/findComponent.js */ + var virtualdom_Fragment$findComponent = function Fragment$findComponent( selector ) { + var len, i, item, queryResult; + if ( this.items ) { + len = this.items.length; + for ( i = 0; i < len; i += 1 ) { + item = this.items[ i ]; + if ( item.findComponent && ( queryResult = item.findComponent( selector ) ) ) { + return queryResult; } - - return; } + return null; } - - attribute.name = name; }; - setStaticAttribute = function ( attribute, options ) { - if ( options.parentNode ) { - if ( attribute.namespace ) { - options.parentNode.setAttributeNS( attribute.namespace, options.name, options.value ); + /* virtualdom/Fragment/prototype/findNextNode.js */ + var virtualdom_Fragment$findNextNode = function Fragment$findNextNode( item ) { + var index = item.index, + node; + if ( this.items[ index + 1 ] ) { + node = this.items[ index + 1 ].firstNode(); + } else if ( this.owner === this.root ) { + if ( !this.owner.component ) { + // TODO but something else could have been appended to + // this.root.el, no? + node = null; } else { - options.parentNode.setAttribute( options.name, options.value ); + node = this.owner.component.findNextNode(); } - - if ( options.name.toLowerCase() === 'id' ) { - options.root.nodes[ options.value ] = options.parentNode; - } + } else { + node = this.owner.findNextNode( this ); } - - attribute.value = options.value; + return node; }; - determinePropertyName = function ( attribute, options ) { - var lowerCaseName, propertyName; - - if ( attribute.parentNode && !attribute.namespace && ( !options.parentNode.namespaceURI || options.parentNode.namespaceURI === namespaces.html ) ) { - lowerCaseName = attribute.lcName; - propertyName = propertyNames[ lowerCaseName ] || lowerCaseName; - - if ( options.parentNode[ propertyName ] !== undefined ) { - attribute.propertyName = propertyName; - } - - // is attribute a boolean attribute or 'value'? If so we're better off doing e.g. - // node.selected = true rather than node.setAttribute( 'selected', '' ) - if ( typeof options.parentNode[ propertyName ] === 'boolean' || propertyName === 'value' ) { - attribute.useProperty = true; - } + /* virtualdom/Fragment/prototype/firstNode.js */ + var virtualdom_Fragment$firstNode = function Fragment$firstNode() { + if ( this.items && this.items[ 0 ] ) { + return this.items[ 0 ].firstNode(); } + return null; }; - isAttributeSelfUpdating = function ( attribute ) { - var i, item, containsInterpolator; - - i = attribute.fragment.items.length; - while ( i-- ) { - item = attribute.fragment.items[i]; - if ( item.type === TEXT ) { - continue; + /* virtualdom/Fragment/prototype/getNode.js */ + var virtualdom_Fragment$getNode = function Fragment$getNode() { + var fragment = this; + do { + if ( fragment.pElement ) { + return fragment.pElement.node; } + } while ( fragment = fragment.parent ); + return this.root.detached || this.root.el; + }; - // we can only have one interpolator and still be self-updating - if ( item.type === INTERPOLATOR ) { - if ( containsInterpolator ) { - return false; + /* virtualdom/Fragment/prototype/getValue.js */ + var virtualdom_Fragment$getValue = function( parseJSON ) { + + var __export; + var empty = {}; + __export = function Fragment$getValue() { + var options = arguments[ 0 ]; + if ( options === void 0 ) + options = empty; + var asArgs, values, source, parsed, cachedResult, dirtyFlag, result; + asArgs = options.args; + cachedResult = asArgs ? 'argsList' : 'value'; + dirtyFlag = asArgs ? 'dirtyArgs' : 'dirtyValue'; + if ( this[ dirtyFlag ] ) { + source = processItems( this.items, values = {}, this.root._guid ); + parsed = parseJSON( asArgs ? '[' + source + ']' : source, values ); + if ( !parsed ) { + result = asArgs ? [ this.toString() ] : this.toString(); } else { - containsInterpolator = true; - continue; + result = parsed.value; } + this[ cachedResult ] = result; + this[ dirtyFlag ] = false; } + return this[ cachedResult ]; + }; - // anything that isn't text or an interpolator (i.e. a section) - // and we can't self-update - return false; + function processItems( items, values, guid, counter ) { + counter = counter || 0; + return items.map( function( item ) { + var placeholderId, wrapped, value; + if ( item.text ) { + return item.text; + } + if ( item.fragments ) { + return item.fragments.map( function( fragment ) { + return processItems( fragment.items, values, guid, counter ); + } ).join( '' ); + } + placeholderId = guid + '-' + counter++; + if ( wrapped = item.root.viewmodel.wrapped[ item.keypath ] ) { + value = wrapped.value; + } else { + value = item.getValue(); + } + values[ placeholderId ] = value; + return '${' + placeholderId + '}'; + } ).join( '' ); } + return __export; + }( parseJSON ); - return true; - }; + /* utils/escapeHtml.js */ + var escapeHtml = function() { - isAttributeBindable = function ( attribute ) { - var tagName, propertyName; + var lessThan = /</g; + var greaterThan = />/g; + var amp = /&/g; + return function escapeHtml( str ) { + return str.replace( amp, '&amp;' ).replace( lessThan, '&lt;' ).replace( greaterThan, '&gt;' ); + }; + }(); - if ( !attribute.root.twoway ) { - return false; + /* utils/detachNode.js */ + var detachNode = function detachNode( node ) { + if ( node && node.parentNode ) { + node.parentNode.removeChild( node ); } - - tagName = attribute.element.descriptor.e.toLowerCase(); - propertyName = attribute.propertyName; - - return ( - ( propertyName === 'name' || propertyName === 'value' || propertyName === 'checked' ) && - ( tagName === 'input' || tagName === 'textarea' || tagName === 'select' ) - ); + return node; }; -}()); -// Element -DomElement = function ( options, docFrag ) { + /* virtualdom/items/shared/detach.js */ + var detach = function( detachNode ) { - var parentFragment, - descriptor, - namespace, - eventName, - eventNames, - i, - attr, - attrName, - lcName, - attrValue, - bindable, - twowayNameAttr, - parentNode, - root, - transition, - transitionName, - transitionParams, - transitionManager, - intro; + return function() { + return detachNode( this.node ); + }; + }( detachNode ); - this.type = ELEMENT; + /* virtualdom/items/Text.js */ + var Text = function( types, escapeHtml, detach ) { - // stuff we'll need later - parentFragment = this.parentFragment = options.parentFragment; - descriptor = this.descriptor = options.descriptor; - - this.root = root = parentFragment.root; - this.parentNode = parentFragment.parentNode; - this.index = options.index; - - this.eventListeners = []; - this.customEventListeners = []; - - // get namespace, if we're actually rendering (not server-side stringifying) - if ( this.parentNode ) { - if ( descriptor.a && descriptor.a.xmlns ) { - namespace = descriptor.a.xmlns; - - // check it's a string! - if ( typeof namespace !== 'string' ) { - throw new Error( 'Namespace attribute cannot contain mustaches' ); + var Text = function( options ) { + this.type = types.TEXT; + this.text = options.template; + }; + Text.prototype = { + detach: detach, + firstNode: function() { + return this.node; + }, + render: function() { + if ( !this.node ) { + this.node = document.createTextNode( this.text ); + } + return this.node; + }, + toString: function( escape ) { + return escape ? escapeHtml( this.text ) : this.text; + }, + unrender: function( shouldDestroy ) { + if ( shouldDestroy ) { + return this.detach(); + } } - } else { - namespace = ( descriptor.e.toLowerCase() === 'svg' ? namespaces.svg : this.parentNode.namespaceURI ); + }; + return Text; + }( types, escapeHtml, detach ); + + /* virtualdom/items/shared/unbind.js */ + var unbind = function unbind() { + if ( this.registered ) { + // this was registered as a dependant + this.root.viewmodel.unregister( this.keypath, this ); } - + if ( this.resolver ) { + this.resolver.unbind(); + } + }; - // create the DOM node - this.node = doc.createElementNS( namespace, descriptor.e ); - } + /* virtualdom/items/shared/Mustache/getValue.js */ + var getValue = function Mustache$getValue() { + return this.value; + }; + /* virtualdom/items/shared/utils/startsWithKeypath.js */ + var startsWithKeypath = function startsWithKeypath( target, keypath ) { + return target && keypath && target.substr( 0, keypath.length + 1 ) === keypath + '.'; + }; - // append children, if there are any - if ( descriptor.f ) { - if ( typeof descriptor.f === 'string' && ( !this.node || ( !this.node.namespaceURI || this.node.namespaceURI === namespaces.html ) ) ) { - // great! we can use innerHTML - this.html = descriptor.f; + /* virtualdom/items/shared/utils/getNewKeypath.js */ + var getNewKeypath = function( startsWithKeypath ) { - if ( docFrag ) { - this.node.innerHTML = this.html; + return function getNewKeypath( targetKeypath, oldKeypath, newKeypath ) { + // exact match + if ( targetKeypath === oldKeypath ) { + return newKeypath !== undefined ? newKeypath : null; } - } + // partial match based on leading keypath segments + if ( startsWithKeypath( targetKeypath, oldKeypath ) ) { + return newKeypath === null ? newKeypath : targetKeypath.replace( oldKeypath + '.', newKeypath + '.' ); + } + }; + }( startsWithKeypath ); - else { - // once again, everyone has to suffer because of IE bloody 8 - if ( descriptor.e === 'style' && this.node.styleSheet !== undefined ) { - this.fragment = new StringFragment({ - descriptor: descriptor.f, - root: root, - contextStack: parentFragment.contextStack, - owner: this - }); + /* virtualdom/items/shared/Resolvers/ReferenceResolver.js */ + var ReferenceResolver = function( runloop, resolveRef, getNewKeypath ) { - if ( docFrag ) { - this.bubble = function () { - this.node.styleSheet.cssText = this.fragment.toString(); - }; + var ReferenceResolver = function( owner, ref, callback ) { + var keypath; + this.ref = ref; + this.resolved = false; + this.root = owner.root; + this.parentFragment = owner.parentFragment; + this.callback = callback; + keypath = resolveRef( owner.root, ref, owner.parentFragment ); + if ( keypath !== undefined ) { + this.resolve( keypath ); + } else { + runloop.addUnresolved( this ); + } + }; + ReferenceResolver.prototype = { + resolve: function( keypath ) { + this.resolved = true; + this.keypath = keypath; + this.callback( keypath ); + }, + forceResolution: function() { + this.resolve( this.ref ); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + var keypath; + if ( this.keypath !== undefined ) { + keypath = getNewKeypath( this.keypath, oldKeypath, newKeypath ); + // was a new keypath created? + if ( keypath !== undefined ) { + // resolve it + this.resolve( keypath ); + } } + }, + unbind: function() { + if ( !this.resolved ) { + runloop.removeUnresolved( this ); + } } + }; + return ReferenceResolver; + }( runloop, resolveRef, getNewKeypath ); - else { - this.fragment = new DomFragment({ - descriptor: descriptor.f, - root: root, - parentNode: this.node, - contextStack: parentFragment.contextStack, - owner: this - }); + /* virtualdom/items/shared/Resolvers/SpecialResolver.js */ + var SpecialResolver = function() { - if ( docFrag ) { - this.node.appendChild( this.fragment.docFrag ); + var SpecialResolver = function( owner, ref, callback ) { + this.parentFragment = owner.parentFragment; + this.ref = ref; + this.callback = callback; + this.rebind(); + }; + SpecialResolver.prototype = { + rebind: function() { + var ref = this.ref, + fragment = this.parentFragment; + if ( ref === '@keypath' ) { + while ( fragment ) { + if ( !!fragment.context ) { + return this.callback( '@' + fragment.context ); + } + fragment = fragment.parent; + } } - } - } - } + if ( ref === '@index' || ref === '@key' ) { + while ( fragment ) { + if ( fragment.index !== undefined ) { + return this.callback( '@' + fragment.index ); + } + fragment = fragment.parent; + } + } + throw new Error( 'Unknown special reference "' + ref + '" - valid references are @index, @key and @keypath' ); + }, + unbind: function() {} + }; + return SpecialResolver; + }(); + /* virtualdom/items/shared/Resolvers/IndexResolver.js */ + var IndexResolver = function() { - // create event proxies - if ( docFrag && descriptor.v ) { - for ( eventName in descriptor.v ) { - if ( hasOwn.call( descriptor.v, eventName ) ) { - eventNames = eventName.split( '-' ); - i = eventNames.length; - - while ( i-- ) { - this.addEventProxy( eventNames[i], descriptor.v[ eventName ], parentFragment.contextStack ); + var IndexResolver = function( owner, ref, callback ) { + this.parentFragment = owner.parentFragment; + this.ref = ref; + this.callback = callback; + this.rebind(); + }; + IndexResolver.prototype = { + rebind: function() { + var ref = this.ref, + indexRefs = this.parentFragment.indexRefs, + index = indexRefs[ ref ]; + if ( index !== undefined ) { + this.callback( '@' + index ); } - } - } - } + }, + unbind: function() {} + }; + return IndexResolver; + }(); + /* virtualdom/items/shared/Resolvers/createReferenceResolver.js */ + var createReferenceResolver = function( ReferenceResolver, SpecialResolver, IndexResolver ) { - // set attributes - this.attributes = []; - bindable = []; // save these till the end + return function createReferenceResolver( owner, ref, callback ) { + var indexRefs, index; + if ( ref.charAt( 0 ) === '@' ) { + return new SpecialResolver( owner, ref, callback ); + } + indexRefs = owner.parentFragment.indexRefs; + if ( indexRefs && ( index = indexRefs[ ref ] ) !== undefined ) { + return new IndexResolver( owner, ref, callback ); + } + return new ReferenceResolver( owner, ref, callback ); + }; + }( ReferenceResolver, SpecialResolver, IndexResolver ); - for ( attrName in descriptor.a ) { - if ( hasOwn.call( descriptor.a, attrName ) ) { - attrValue = descriptor.a[ attrName ]; - - attr = new DomAttribute({ - element: this, - name: attrName, - value: ( attrValue === undefined ? null : attrValue ), - root: root, - parentNode: this.node, - contextStack: parentFragment.contextStack - }); + /* shared/getFunctionFromString.js */ + var getFunctionFromString = function() { - this.attributes[ this.attributes.length ] = attr; - - if ( attr.isBindable ) { - bindable.push( attr ); + var cache = {}; + return function getFunctionFromString( str, i ) { + var fn, args; + if ( cache[ str ] ) { + return cache[ str ]; } - - if ( attr.isTwowayNameAttr ) { - twowayNameAttr = attr; - } else { - attr.update(); + args = []; + while ( i-- ) { + args[ i ] = '_' + i; } - } - } + fn = new Function( args.join( ',' ), 'return(' + str + ')' ); + cache[ str ] = fn; + return fn; + }; + }(); - // if we're actually rendering (i.e. not server-side stringifying), proceed - if ( docFrag ) { - while ( bindable.length ) { - bindable.pop().bind( this.root.lazy ); - } + /* virtualdom/items/shared/Resolvers/ExpressionResolver.js */ + var ExpressionResolver = function( defineProperty, isNumeric, createReferenceResolver, getFunctionFromString ) { - if ( twowayNameAttr ) { - twowayNameAttr.updateViewModel(); - twowayNameAttr.update(); - } + var __export; + var ExpressionResolver, bind = Function.prototype.bind; + ExpressionResolver = function( owner, parentFragment, expression, callback ) { + var resolver = this, + ractive, indexRefs; + ractive = owner.root; + resolver.root = ractive; + resolver.parentFragment = parentFragment; + resolver.callback = callback; + resolver.owner = owner; + resolver.str = expression.s; + resolver.keypaths = []; + indexRefs = parentFragment.indexRefs; + // Create resolvers for each reference + resolver.pending = expression.r.length; + resolver.refResolvers = expression.r.map( function( ref, i ) { + return createReferenceResolver( resolver, ref, function( keypath ) { + resolver.resolve( i, keypath ); + } ); + } ); + resolver.ready = true; + resolver.bubble(); + }; + ExpressionResolver.prototype = { + bubble: function() { + if ( !this.ready ) { + return; + } + this.uniqueString = getUniqueString( this.str, this.keypaths ); + this.keypath = getKeypath( this.uniqueString ); + this.createEvaluator(); + this.callback( this.keypath ); + }, + unbind: function() { + var resolver; + while ( resolver = this.refResolvers.pop() ) { + resolver.unbind(); + } + }, + resolve: function( index, keypath ) { + this.keypaths[ index ] = keypath; + this.bubble(); + }, + createEvaluator: function() { + var this$0 = this; + var self = this, + computation, valueGetters, signature, keypath, fn; + computation = this.root.viewmodel.computations[ this.keypath ]; + // only if it doesn't exist yet! + if ( !computation ) { + fn = getFunctionFromString( this.str, this.refResolvers.length ); + valueGetters = this.keypaths.map( function( keypath ) { + var value; + if ( keypath === 'undefined' ) { + return function() { + return undefined; + }; + } + // 'special' keypaths encode a value + if ( keypath[ 0 ] === '@' ) { + value = keypath.slice( 1 ); + return isNumeric( value ) ? function() { + return +value; + } : function() { + return value; + }; + } + return function() { + var value = this$0.root.viewmodel.get( keypath ); + if ( typeof value === 'function' ) { + value = wrapFunction( value, self.root ); + } + return value; + }; + } ); + signature = { + deps: this.keypaths.filter( isValidDependency ), + get: function() { + var args = valueGetters.map( call ); + return fn.apply( null, args ); + } + }; + computation = this.root.viewmodel.compute( this.keypath, signature ); + } else { + this.root.viewmodel.mark( this.keypath ); + } + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + // TODO only bubble once, no matter how many references are affected by the rebind + this.refResolvers.forEach( function( r ) { + return r.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } ); + } + }; + __export = ExpressionResolver; - docFrag.appendChild( this.node ); - - // trigger intro transition - if ( descriptor.t1 ) { - executeTransition( descriptor.t1, root, this, parentFragment.contextStack, true ); + function call( value ) { + return value.call(); } - } -}; -DomElement.prototype = { - addEventProxy: function ( triggerEventName, proxyDescriptor, contextStack ) { - var self = this, root = this.root, proxyName, proxyArgs, dynamicArgs, reuseable, definition, listener, fragment, handler, comboKey; - - // Note the current context - this can be useful with event handlers - if ( !this.node._ractive ) { - defineProperty( this.node, '_ractive', { value: { - keypath: ( contextStack.length ? contextStack[ contextStack.length - 1 ] : '' ), - index: this.parentFragment.indexRefs - } }); + function getUniqueString( str, keypaths ) { + // get string that is unique to this expression + return str.replace( /_([0-9]+)/g, function( match, $1 ) { + var keypath, value; + keypath = keypaths[ $1 ]; + if ( keypath === undefined ) { + return 'undefined'; + } + if ( keypath[ 0 ] === '@' ) { + value = keypath.slice( 1 ); + return isNumeric( value ) ? value : '"' + value + '"'; + } + return keypath; + } ); } - if ( typeof proxyDescriptor === 'string' ) { - proxyName = proxyDescriptor; - } else { - proxyName = proxyDescriptor.n; + function getKeypath( uniqueString ) { + // Sanitize by removing any periods or square brackets. Otherwise + // we can't split the keypath into keys! + return '${' + uniqueString.replace( /[\.\[\]]/g, '-' ) + '}'; } - // This key uniquely identifies this trigger+proxy name combo on this element - comboKey = triggerEventName + '=' + proxyName; - - if ( proxyDescriptor.a ) { - proxyArgs = proxyDescriptor.a; + function isValidDependency( keypath ) { + return keypath !== undefined && keypath[ 0 ] !== '@'; } - else if ( proxyDescriptor.d ) { - dynamicArgs = true; - - proxyArgs = new StringFragment({ - descriptor: proxyDescriptor.d, - root: this.root, - owner: this, - contextStack: contextStack - }); - - if ( !this.proxyFrags ) { - this.proxyFrags = []; + function wrapFunction( fn, ractive ) { + var wrapped, prop, key; + if ( fn._noWrap ) { + return fn; } - this.proxyFrags[ this.proxyFrags.length ] = proxyArgs; - } - - if ( proxyArgs !== undefined ) { - // store arguments on the element, so we can reuse the same handler - // with multiple elements - if ( this.node._ractive[ comboKey ] ) { - throw new Error( 'You cannot have two proxy events with the same trigger event (' + comboKey + ')' ); + prop = '__ractive_' + ractive._guid; + wrapped = fn[ prop ]; + if ( wrapped ) { + return wrapped; + } else if ( /this/.test( fn.toString() ) ) { + defineProperty( fn, prop, { + value: bind.call( fn, ractive ) + } ); + // Add properties/methods to wrapped function + for ( key in fn ) { + if ( fn.hasOwnProperty( key ) ) { + fn[ prop ][ key ] = fn[ key ]; + } + } + return fn[ prop ]; } - - this.node._ractive[ comboKey ] = { - dynamic: dynamicArgs, - payload: proxyArgs - }; + defineProperty( fn, '__ractive_nowrap', { + value: fn + } ); + return fn.__ractive_nowrap; } + return __export; + }( defineProperty, isNumeric, createReferenceResolver, getFunctionFromString, legacy ); - // Is this a custom event? - if ( definition = ( root.eventDefinitions[ triggerEventName ] || Ractive.eventDefinitions[ triggerEventName ] ) ) { - // If the proxy is a string (e.g. <a proxy-click='select'>{{item}}</a>) then - // we can reuse the handler. This eliminates the need for event delegation - if ( !root._customProxies[ comboKey ] ) { - root._customProxies[ comboKey ] = function ( proxyEvent ) { - var args, payload; + /* virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js */ + var MemberResolver = function( types, createReferenceResolver, ExpressionResolver ) { - if ( !proxyEvent.node ) { - throw new Error( 'Proxy event definitions must fire events with a `node` property' ); - } - - proxyEvent.keypath = proxyEvent.node._ractive.keypath; - proxyEvent.context = root.get( proxyEvent.keypath ); - proxyEvent.index = proxyEvent.node._ractive.index; - - if ( proxyEvent.node._ractive[ comboKey ] ) { - args = proxyEvent.node._ractive[ comboKey ]; - payload = args.dynamic ? args.payload.toJson() : args.payload; - } - - root.fire( proxyName, proxyEvent, payload ); - }; + var MemberResolver = function( template, resolver, parentFragment ) { + var member = this, + keypath; + member.resolver = resolver; + member.root = resolver.root; + member.parentFragment = parentFragment; + member.viewmodel = resolver.root.viewmodel; + if ( typeof template === 'string' ) { + member.value = template; + } else if ( template.t === types.REFERENCE ) { + member.refResolver = createReferenceResolver( this, template.n, function( keypath ) { + member.resolve( keypath ); + } ); + } else { + new ExpressionResolver( resolver, parentFragment, template, function( keypath ) { + member.resolve( keypath ); + } ); } - - handler = root._customProxies[ comboKey ]; - - // Use custom event. Apply definition to this node - listener = definition( this.node, handler ); - this.customEventListeners[ this.customEventListeners.length ] = listener; - - return; - } - - // If not, we just need to check it is a valid event for this element - // warn about invalid event handlers, if we're in debug mode - if ( this.node[ 'on' + triggerEventName ] !== undefined && root.debug ) { - if ( console && console.warn ) { - console.warn( 'Invalid event handler (' + triggerEventName + ')' ); + }; + MemberResolver.prototype = { + resolve: function( keypath ) { + if ( this.keypath ) { + this.viewmodel.unregister( this.keypath, this ); + } + this.keypath = keypath; + this.value = this.viewmodel.get( keypath ); + this.bind(); + this.resolver.bubble(); + }, + bind: function() { + this.viewmodel.register( this.keypath, this ); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + if ( this.refResolver ) { + this.refResolver.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + }, + setValue: function( value ) { + this.value = value; + this.resolver.bubble(); + }, + unbind: function() { + if ( this.keypath ) { + this.viewmodel.unregister( this.keypath, this ); + } + if ( this.unresolved ) { + this.unresolved.unbind(); + } + }, + forceResolution: function() { + if ( this.refResolver ) { + this.refResolver.forceResolution(); + } } - } + }; + return MemberResolver; + }( types, createReferenceResolver, ExpressionResolver ); - if ( !root._proxies[ comboKey ] ) { - root._proxies[ comboKey ] = function ( event ) { - var args, payload, proxyEvent = { - node: this, - original: event, - keypath: this._ractive.keypath, - context: root.get( this._ractive.keypath ), - index: this._ractive.index - }; + /* virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/ReferenceExpressionResolver.js */ + var ReferenceExpressionResolver = function( resolveRef, ReferenceResolver, MemberResolver ) { - if ( this._ractive && this._ractive[ comboKey ] ) { - args = this._ractive[ comboKey ]; - payload = args.dynamic ? args.payload.toJson() : args.payload; + var ReferenceExpressionResolver = function( mustache, template, callback ) { + var this$0 = this; + var resolver = this, + ractive, ref, keypath, parentFragment; + resolver.parentFragment = parentFragment = mustache.parentFragment; + resolver.root = ractive = mustache.root; + resolver.mustache = mustache; + resolver.ref = ref = template.r; + resolver.callback = callback; + resolver.unresolved = []; + // Find base keypath + if ( keypath = resolveRef( ractive, ref, parentFragment ) ) { + resolver.base = keypath; + } else { + resolver.baseResolver = new ReferenceResolver( this, ref, function( keypath ) { + resolver.base = keypath; + resolver.baseResolver = null; + resolver.bubble(); + } ); + } + // Find values for members, or mark them as unresolved + resolver.members = template.m.map( function( template ) { + return new MemberResolver( template, this$0, parentFragment ); + } ); + resolver.ready = true; + resolver.bubble(); + }; + ReferenceExpressionResolver.prototype = { + getKeypath: function() { + var values = this.members.map( getValue ); + if ( !values.every( isDefined ) || this.baseResolver ) { + return null; } - - root.fire( proxyName, proxyEvent, payload ); - }; - } - - handler = root._proxies[ comboKey ]; - - this.eventListeners[ this.eventListeners.length ] = { - n: triggerEventName, - h: handler + return this.base + '.' + values.join( '.' ); + }, + bubble: function() { + if ( !this.ready || this.baseResolver ) { + return; + } + this.callback( this.getKeypath() ); + }, + unbind: function() { + this.members.forEach( unbind ); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + var changed; + this.members.forEach( function( members ) { + if ( members.rebind( indexRef, newIndex, oldKeypath, newKeypath ) ) { + changed = true; + } + } ); + if ( changed ) { + this.bubble(); + } + }, + forceResolution: function() { + if ( this.baseResolver ) { + this.base = this.ref; + this.baseResolver.unbind(); + this.baseResolver = null; + } + this.members.forEach( function( m ) { + return m.forceResolution(); + } ); + this.bubble(); + } }; - this.node.addEventListener( triggerEventName, handler ); - }, - - teardown: function ( detach ) { - var self = this, tearThisDown, transitionManager, transitionName, transitionParams, listener, outro; - - // Children first. that way, any transitions on child elements will be - // handled by the current transitionManager - if ( self.fragment ) { - self.fragment.teardown( false ); + function getValue( member ) { + return member.value; } - while ( self.attributes.length ) { - self.attributes.pop().teardown(); + function isDefined( value ) { + return value != undefined; } - while ( self.eventListeners.length ) { - listener = self.eventListeners.pop(); - self.node.removeEventListener( listener.n, listener.h ); + function unbind( member ) { + member.unbind(); } + return ReferenceExpressionResolver; + }( resolveRef, ReferenceResolver, MemberResolver ); - while ( self.customEventListeners.length ) { - self.customEventListeners.pop().teardown(); - } + /* virtualdom/items/shared/Mustache/initialise.js */ + var initialise = function( types, createReferenceResolver, ReferenceExpressionResolver, ExpressionResolver ) { - if ( this.proxyFrags ) { - while ( this.proxyFrags.length ) { - this.proxyFrags.pop().teardown(); + return function Mustache$init( mustache, options ) { + var ref, parentFragment, template; + parentFragment = options.parentFragment; + template = options.template; + mustache.root = parentFragment.root; + mustache.parentFragment = parentFragment; + mustache.pElement = parentFragment.pElement; + mustache.template = options.template; + mustache.index = options.index || 0; + mustache.isStatic = options.template.s; + mustache.type = options.template.t; + mustache.registered = false; + // if this is a simple mustache, with a reference, we just need to resolve + // the reference to a keypath + if ( ref = template.r ) { + mustache.resolver = new createReferenceResolver( mustache, ref, resolve ); } - } + // if it's an expression, we have a bit more work to do + if ( options.template.x ) { + mustache.resolver = new ExpressionResolver( mustache, parentFragment, options.template.x, resolveAndRebindChildren ); + } + if ( options.template.rx ) { + mustache.resolver = new ReferenceExpressionResolver( mustache, options.template.rx, resolveAndRebindChildren ); + } + // Special case - inverted sections + if ( mustache.template.n === types.SECTION_UNLESS && !mustache.hasOwnProperty( 'value' ) ) { + mustache.setValue( undefined ); + } - if ( this.descriptor.t2 ) { - executeTransition( this.descriptor.t2, this.root, this, this.parentFragment.contextStack, false ); - } + function resolve( keypath ) { + mustache.resolve( keypath ); + } - if ( detach ) { - this.root._transitionManager.detachWhenReady( this.node ); - } - }, + function resolveAndRebindChildren( newKeypath ) { + var oldKeypath = mustache.keypath; + if ( newKeypath !== oldKeypath ) { + mustache.resolve( newKeypath ); + if ( oldKeypath !== undefined ) { + mustache.fragments && mustache.fragments.forEach( function( f ) { + f.rebind( null, null, oldKeypath, newKeypath ); + } ); + } + } + } + }; + }( types, createReferenceResolver, ReferenceExpressionResolver, ExpressionResolver ); - firstNode: function () { - return this.node; - }, + /* virtualdom/items/shared/Mustache/resolve.js */ + var resolve = function( isNumeric ) { - findNextNode: function ( fragment ) { - return null; - }, + return function Mustache$resolve( keypath ) { + var wasResolved, value, twowayBinding; + // 'Special' keypaths, e.g. @foo or @7, encode a value + if ( keypath && keypath[ 0 ] === '@' ) { + value = keypath.slice( 1 ); + if ( isNumeric( value ) ) { + value = +value; + } + this.keypath = keypath; + this.setValue( value ); + return; + } + // If we resolved previously, we need to unregister + if ( this.registered ) { + // undefined or null + this.root.viewmodel.unregister( this.keypath, this ); + this.registered = false; + wasResolved = true; + } + this.keypath = keypath; + // If the new keypath exists, we need to register + // with the viewmodel + if ( keypath != undefined ) { + // undefined or null + value = this.root.viewmodel.get( keypath ); + this.root.viewmodel.register( keypath, this ); + this.registered = true; + } + // Either way we need to queue up a render (`value` + // will be `undefined` if there's no keypath) + this.setValue( value ); + // Two-way bindings need to point to their new target keypath + if ( wasResolved && ( twowayBinding = this.twowayBinding ) ) { + twowayBinding.rebound(); + } + }; + }( isNumeric ); - bubble: function () { - // noop - just so event proxy and transition fragments have something to call! - }, - - toString: function () { - var str, i, len, attr; - - // TODO void tags - str = '' + - '<' + this.descriptor.e; - - len = this.attributes.length; - for ( i=0; i<len; i+=1 ) { - str += ' ' + this.attributes[i].toString(); + /* virtualdom/items/shared/Mustache/rebind.js */ + var rebind = function Mustache$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + // Children first + if ( this.fragments ) { + this.fragments.forEach( function( f ) { + return f.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } ); } - - str += '>'; - - if ( this.html ) { - str += this.html; - } else if ( this.fragment ) { - str += this.fragment.toString(); + // Expression mustache? + if ( this.resolver ) { + this.resolver.rebind( indexRef, newIndex, oldKeypath, newKeypath ); } + }; - str += '</' + this.descriptor.e + '>'; + /* virtualdom/items/shared/Mustache/_Mustache.js */ + var Mustache = function( getValue, init, resolve, rebind ) { - return str; - } -}; -DomFragment = function ( options ) { - if ( options.parentNode ) { - this.docFrag = doc.createDocumentFragment(); - } + return { + getValue: getValue, + init: init, + resolve: resolve, + rebind: rebind + }; + }( getValue, initialise, resolve, rebind ); - // if we have an HTML string, our job is easy. - if ( typeof options.descriptor === 'string' ) { - this.html = options.descriptor; + /* virtualdom/items/Interpolator.js */ + var Interpolator = function( types, runloop, escapeHtml, detachNode, isEqual, unbind, Mustache, detach ) { - if ( this.docFrag ) { - this.nodes = insertHtml( options.descriptor, this.docFrag ); - } - - return; // prevent the rest of the init sequence - } + var Interpolator = function( options ) { + this.type = types.INTERPOLATOR; + Mustache.init( this, options ); + }; + Interpolator.prototype = { + update: function() { + this.node.data = this.value == undefined ? '' : this.value; + }, + resolve: Mustache.resolve, + rebind: Mustache.rebind, + detach: detach, + unbind: unbind, + render: function() { + if ( !this.node ) { + this.node = document.createTextNode( this.value != undefined ? this.value : '' ); + } + return this.node; + }, + unrender: function( shouldDestroy ) { + if ( shouldDestroy ) { + detachNode( this.node ); + } + }, + getValue: Mustache.getValue, + // TEMP + setValue: function( value ) { + var wrapper; + // TODO is there a better way to approach this? + if ( wrapper = this.root.viewmodel.wrapped[ this.keypath ] ) { + value = wrapper.get(); + } + if ( !isEqual( value, this.value ) ) { + this.value = value; + this.parentFragment.bubble(); + if ( this.node ) { + runloop.addView( this ); + } + } + }, + firstNode: function() { + return this.node; + }, + toString: function( escape ) { + var string = this.value != undefined ? '' + this.value : ''; + return escape ? escapeHtml( string ) : string; + } + }; + return Interpolator; + }( types, runloop, escapeHtml, detachNode, isEqual, unbind, Mustache, detach ); - // otherwise we need to make a proper fragment - initFragment( this, options ); -}; + /* virtualdom/items/Section/prototype/bubble.js */ + var virtualdom_items_Section$bubble = function Section$bubble() { + this.parentFragment.bubble(); + }; -DomFragment.prototype = { - createItem: function ( options ) { - if ( typeof options.descriptor === 'string' ) { - return new DomText( options, this.docFrag ); + /* virtualdom/items/Section/prototype/detach.js */ + var virtualdom_items_Section$detach = function Section$detach() { + var docFrag; + if ( this.fragments.length === 1 ) { + return this.fragments[ 0 ].detach(); } + docFrag = document.createDocumentFragment(); + this.fragments.forEach( function( item ) { + docFrag.appendChild( item.detach() ); + } ); + return docFrag; + }; - switch ( options.descriptor.t ) { - case INTERPOLATOR: return new DomInterpolator( options, this.docFrag ); - case SECTION: return new DomSection( options, this.docFrag ); - case TRIPLE: return new DomTriple( options, this.docFrag ); - - case ELEMENT: return new DomElement( options, this.docFrag ); - case PARTIAL: return new DomPartial( options, this.docFrag ); - - default: throw new Error( 'WTF? not sure what happened here...' ); - } - }, - - teardown: function ( detach ) { - var node; - - // if this was built from HTML, we just need to remove the nodes - if ( detach && this.nodes ) { - while ( this.nodes.length ) { - node = this.nodes.pop(); - node.parentNode.removeChild( node ); + /* virtualdom/items/Section/prototype/find.js */ + var virtualdom_items_Section$find = function Section$find( selector ) { + var i, len, queryResult; + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + if ( queryResult = this.fragments[ i ].find( selector ) ) { + return queryResult; } - return; } + return null; + }; - // otherwise we need to do a proper teardown - if ( !this.items ) { - return; + /* virtualdom/items/Section/prototype/findAll.js */ + var virtualdom_items_Section$findAll = function Section$findAll( selector, query ) { + var i, len; + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + this.fragments[ i ].findAll( selector, query ); } + }; - while ( this.items.length ) { - this.items.pop().teardown( detach ); + /* virtualdom/items/Section/prototype/findAllComponents.js */ + var virtualdom_items_Section$findAllComponents = function Section$findAllComponents( selector, query ) { + var i, len; + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + this.fragments[ i ].findAllComponents( selector, query ); } - }, + }; - firstNode: function () { - if ( this.items && this.items[0] ) { - return this.items[0].firstNode(); - } else if ( this.nodes ) { - return this.nodes[0] || null; + /* virtualdom/items/Section/prototype/findComponent.js */ + var virtualdom_items_Section$findComponent = function Section$findComponent( selector ) { + var i, len, queryResult; + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + if ( queryResult = this.fragments[ i ].findComponent( selector ) ) { + return queryResult; + } } - return null; - }, + }; - findNextNode: function ( item ) { - var index = item.index; - - if ( this.items[ index + 1 ] ) { - return this.items[ index + 1 ].firstNode(); + /* virtualdom/items/Section/prototype/findNextNode.js */ + var virtualdom_items_Section$findNextNode = function Section$findNextNode( fragment ) { + if ( this.fragments[ fragment.index + 1 ] ) { + return this.fragments[ fragment.index + 1 ].firstNode(); } + return this.parentFragment.findNextNode( this ); + }; - // if this is the root fragment, and there are no more items, - // it means we're at the end - if ( this.owner === this.root ) { - return null; + /* virtualdom/items/Section/prototype/firstNode.js */ + var virtualdom_items_Section$firstNode = function Section$firstNode() { + var len, i, node; + if ( len = this.fragments.length ) { + for ( i = 0; i < len; i += 1 ) { + if ( node = this.fragments[ i ].firstNode() ) { + return node; + } + } } - - return this.owner.findNextNode( this ); - }, - - toString: function () { - var html, i, len, item; - - if ( this.html ) { - return this.html; - } - - html = ''; - - if ( !this.items ) { - return html; - } - - len = this.items.length; - - for ( i=0; i<len; i+=1 ) { - item = this.items[i]; - html += item.toString(); - } - - return html; - } -}; -// Interpolator -DomInterpolator = function ( options, docFrag ) { - this.type = INTERPOLATOR; - - if ( docFrag ) { - this.node = doc.createTextNode( '' ); - docFrag.appendChild( this.node ); - } - - // extend Mustache - initMustache( this, options ); -}; - -DomInterpolator.prototype = { - update: updateMustache, - resolve: resolveMustache, - - teardown: function ( detach ) { - teardown( this ); - - if ( detach ) { - this.parentNode.removeChild( this.node ); - } - }, - - render: function ( value ) { - if ( this.node ) { - this.node.data = ( value === undefined ? '' : value ); - } - }, - - firstNode: function () { - return this.node; - }, - - toString: function () { - var value = ( this.value !== undefined ? '' + this.value : '' ); - return value.replace( '<', '&lt;' ).replace( '>', '&gt;' ); - } -}; -// Partials -DomPartial = function ( options, docFrag ) { - var parentFragment = this.parentFragment = options.parentFragment, descriptor; - - this.type = PARTIAL; - this.name = options.descriptor.r; - - descriptor = getPartialDescriptor( parentFragment.root, options.descriptor.r ); - - this.fragment = new DomFragment({ - descriptor: descriptor, - root: parentFragment.root, - parentNode: parentFragment.parentNode, - contextStack: parentFragment.contextStack, - owner: this - }); - - if ( docFrag ) { - docFrag.appendChild( this.fragment.docFrag ); - } -}; - -DomPartial.prototype = { - findNextNode: function () { return this.parentFragment.findNextNode( this ); - }, + }; - teardown: function ( detach ) { - this.fragment.teardown( detach ); - }, + /* virtualdom/items/Section/prototype/shuffle.js */ + var virtualdom_items_Section$shuffle = function( types, runloop, circular ) { - toString: function () { - return this.fragment.toString(); - } -}; -// Section -DomSection = function ( options, docFrag ) { - this.type = SECTION; - - this.fragments = []; - this.length = 0; // number of times this section is rendered - - if ( docFrag ) { - this.docFrag = doc.createDocumentFragment(); - } - - this.initialising = true; - initMustache( this, options ); - - if ( docFrag ) { - docFrag.appendChild( this.docFrag ); - } - - this.initialising = false; -}; - -DomSection.prototype = { - update: updateMustache, - resolve: resolveMustache, - - smartUpdate: function ( methodName, args ) { - var fragmentOptions, i; - - if ( methodName === 'push' || methodName === 'unshift' || methodName === 'splice' ) { + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + return function Section$shuffle( newIndices ) { + var this$0 = this; + var section = this, + parentFragment, firstChange, i, newLength, reboundFragments, fragmentOptions, fragment; + // short circuit any double-updates, and ensure that this isn't applied to + // non-list sections + if ( this.shuffling || this.unbound || this.subtype && this.subtype !== types.SECTION_EACH ) { + return; + } + this.shuffling = true; + runloop.scheduleTask( function() { + return this$0.shuffling = false; + } ); + parentFragment = this.parentFragment; + reboundFragments = []; + // first, rebind existing fragments + newIndices.forEach( function rebindIfNecessary( newIndex, oldIndex ) { + var fragment, by, oldKeypath, newKeypath; + if ( newIndex === oldIndex ) { + reboundFragments[ newIndex ] = section.fragments[ oldIndex ]; + return; + } + fragment = section.fragments[ oldIndex ]; + if ( firstChange === undefined ) { + firstChange = oldIndex; + } + // does this fragment need to be torn down? + if ( newIndex === -1 ) { + section.fragmentsToUnrender.push( fragment ); + fragment.unbind(); + return; + } + // Otherwise, it needs to be rebound to a new index + by = newIndex - oldIndex; + oldKeypath = section.keypath + '.' + oldIndex; + newKeypath = section.keypath + '.' + newIndex; + fragment.rebind( section.template.i, newIndex, oldKeypath, newKeypath ); + fragment.index = newIndex; + reboundFragments[ newIndex ] = fragment; + } ); + newLength = this.root.get( this.keypath ).length; + // If nothing changed with the existing fragments, then we start adding + // new fragments at the end... + if ( firstChange === undefined ) { + // ...unless there are no new fragments to add + if ( this.length === newLength ) { + return; + } + firstChange = this.length; + } + this.length = this.fragments.length = newLength; + if ( this.rendered ) { + runloop.addView( this ); + } + // Prepare new fragment options fragmentOptions = { - descriptor: this.descriptor.f, - root: this.root, - parentNode: this.parentNode, - owner: this + template: this.template.f, + root: this.root, + owner: this }; - - if ( this.descriptor.i ) { - fragmentOptions.indexRef = this.descriptor.i; + if ( this.template.i ) { + fragmentOptions.indexRef = this.template.i; } - } + // Add as many new fragments as we need to, or add back existing + // (detached) fragments + for ( i = firstChange; i < newLength; i += 1 ) { + fragment = reboundFragments[ i ]; + if ( !fragment ) { + this.fragmentsToCreate.push( i ); + } + this.fragments[ i ] = fragment; + } + }; + }( types, runloop, circular ); - if ( this[ methodName ] ) { // if not, it's sort or reverse, which doesn't affect us (i.e. our length) - this[ methodName ]( fragmentOptions, args ); - } - }, + /* virtualdom/items/Section/prototype/render.js */ + var virtualdom_items_Section$render = function Section$render() { + var docFrag; + docFrag = this.docFrag = document.createDocumentFragment(); + this.update(); + this.rendered = true; + return docFrag; + }; - pop: function () { - // teardown last fragment - if ( this.length ) { - this.fragments.pop().teardown( true ); - this.length -= 1; - } - }, + /* utils/isArrayLike.js */ + var isArrayLike = function() { - push: function ( fragmentOptions, args ) { - var start, end, i; + var pattern = /^\[object (?:Array|FileList)\]$/, + toString = Object.prototype.toString; + return function isArrayLike( obj ) { + return pattern.test( toString.call( obj ) ); + }; + }(); - // append list item to context stack - start = this.length; - end = start + args.length; + /* virtualdom/items/Section/prototype/setValue.js */ + var virtualdom_items_Section$setValue = function( types, isArrayLike, isObject, runloop, circular ) { - for ( i=start; i<end; i+=1 ) { - fragmentOptions.contextStack = this.contextStack.concat( this.keypath + '.' + i ); - fragmentOptions.index = i; + var __export; + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + __export = function Section$setValue( value ) { + var this$0 = this; + var wrapper, fragmentOptions; + if ( this.updating ) { + // If a child of this section causes a re-evaluation - for example, an + // expression refers to a function that mutates the array that this + // section depends on - we'll end up with a double rendering bug (see + // https://github.com/ractivejs/ractive/issues/748). This prevents it. + return; + } + this.updating = true; + // with sections, we need to get the fake value if we have a wrapped object + if ( wrapper = this.root.viewmodel.wrapped[ this.keypath ] ) { + value = wrapper.get(); + } + // If any fragments are awaiting creation after a splice, + // this is the place to do it + if ( this.fragmentsToCreate.length ) { + fragmentOptions = { + template: this.template.f, + root: this.root, + pElement: this.pElement, + owner: this, + indexRef: this.template.i + }; + this.fragmentsToCreate.forEach( function( index ) { + var fragment; + fragmentOptions.context = this$0.keypath + '.' + index; + fragmentOptions.index = index; + fragment = new Fragment( fragmentOptions ); + this$0.fragmentsToRender.push( this$0.fragments[ index ] = fragment ); + } ); + this.fragmentsToCreate.length = 0; + } else if ( reevaluateSection( this, value ) ) { + this.bubble(); + if ( this.rendered ) { + runloop.addView( this ); + } + } + this.value = value; + this.updating = false; + }; - this.fragments[i] = this.createFragment( fragmentOptions ); + function reevaluateSection( section, value ) { + var fragmentOptions = { + template: section.template.f, + root: section.root, + pElement: section.parentFragment.pElement, + owner: section + }; + // If we already know the section type, great + // TODO can this be optimised? i.e. pick an reevaluateSection function during init + // and avoid doing this each time? + if ( section.subtype ) { + switch ( section.subtype ) { + case types.SECTION_IF: + return reevaluateConditionalSection( section, value, false, fragmentOptions ); + case types.SECTION_UNLESS: + return reevaluateConditionalSection( section, value, true, fragmentOptions ); + case types.SECTION_WITH: + return reevaluateContextSection( section, fragmentOptions ); + case types.SECTION_IF_WITH: + return reevaluateConditionalContextSection( section, value, fragmentOptions ); + case types.SECTION_EACH: + if ( isObject( value ) ) { + return reevaluateListObjectSection( section, value, fragmentOptions ); + } + } + } + // Otherwise we need to work out what sort of section we're dealing with + section.ordered = !!isArrayLike( value ); + // Ordered list section + if ( section.ordered ) { + return reevaluateListSection( section, value, fragmentOptions ); + } + // Unordered list, or context + if ( isObject( value ) || typeof value === 'function' ) { + // Index reference indicates section should be treated as a list + if ( section.template.i ) { + return reevaluateListObjectSection( section, value, fragmentOptions ); + } + // Otherwise, object provides context for contents + return reevaluateContextSection( section, fragmentOptions ); + } + // Conditional section + return reevaluateConditionalSection( section, value, false, fragmentOptions ); } - - this.length += args.length; - // append docfrag in front of next node - this.parentNode.insertBefore( this.docFrag, this.parentFragment.findNextNode( this ) ); - }, - - shift: function () { - this.splice( null, [ 0, 1 ] ); - }, - - unshift: function ( fragmentOptions, args ) { - this.splice( fragmentOptions, [ 0, 0 ].concat( new Array( args.length ) ) ); - }, - - splice: function ( fragmentOptions, args ) { - var insertionPoint, addedItems, removedItems, balance, i, start, end, spliceArgs, reassignStart, reassignEnd, reassignBy; - - if ( !args.length ) { - return; + function reevaluateListSection( section, value, fragmentOptions ) { + var i, length, fragment; + length = value.length; + if ( length === section.length ) { + // Nothing to do + return false; + } + // if the array is shorter than it was previously, remove items + if ( length < section.length ) { + section.fragmentsToUnrender = section.fragments.splice( length, section.length - length ); + section.fragmentsToUnrender.forEach( unbind ); + } else { + if ( length > section.length ) { + // add any new ones + for ( i = section.length; i < length; i += 1 ) { + // append list item to context stack + fragmentOptions.context = section.keypath + '.' + i; + fragmentOptions.index = i; + if ( section.template.i ) { + fragmentOptions.indexRef = section.template.i; + } + fragment = new Fragment( fragmentOptions ); + section.fragmentsToRender.push( section.fragments[ i ] = fragment ); + } + } + } + section.length = length; + return true; } - // figure out where the changes started... - start = +( args[0] < 0 ? this.length + args[0] : args[0] ); - - // ...and how many items were added to or removed from the array - addedItems = Math.max( 0, args.length - 2 ); - removedItems = ( args[1] !== undefined ? args[1] : this.length - start ); - - balance = addedItems - removedItems; - - if ( !balance ) { - // The array length hasn't changed - we don't need to add or remove anything - return; + function reevaluateListObjectSection( section, value, fragmentOptions ) { + var id, i, hasKey, fragment, changed; + hasKey = section.hasKey || ( section.hasKey = {} ); + // remove any fragments that should no longer exist + i = section.fragments.length; + while ( i-- ) { + fragment = section.fragments[ i ]; + if ( !( fragment.index in value ) ) { + changed = true; + fragment.unbind(); + section.fragmentsToUnrender.push( fragment ); + section.fragments.splice( i, 1 ); + hasKey[ fragment.index ] = false; + } + } + // add any that haven't been created yet + for ( id in value ) { + if ( !hasKey[ id ] ) { + changed = true; + fragmentOptions.context = section.keypath + '.' + id; + fragmentOptions.index = id; + if ( section.template.i ) { + fragmentOptions.indexRef = section.template.i; + } + fragment = new Fragment( fragmentOptions ); + section.fragmentsToRender.push( fragment ); + section.fragments.push( fragment ); + hasKey[ id ] = true; + } + } + section.length = section.fragments.length; + return changed; } - // If more items were removed than added, we need to remove some things from the DOM - if ( balance < 0 ) { - end = start - balance; - - for ( i=start; i<end; i+=1 ) { - this.fragments[i].teardown( true ); + function reevaluateConditionalContextSection( section, value, fragmentOptions ) { + if ( value ) { + return reevaluateContextSection( section, fragmentOptions ); + } else { + return removeSectionFragments( section ); } - - // Keep in sync - this.fragments.splice( start, -balance ); } - // Otherwise we need to add some things to the DOM - else { - end = start + balance; - - // Figure out where these new nodes need to be inserted - insertionPoint = ( this.fragments[ start ] ? this.fragments[ start ].firstNode() : this.parentFragment.findNextNode( this ) ); - - // Make room for the new fragments. (Just trust me, this works...) - spliceArgs = [ start, 0 ].concat( new Array( balance ) ); - this.fragments.splice.apply( this.fragments, spliceArgs ); - - for ( i=start; i<end; i+=1 ) { - fragmentOptions.contextStack = this.contextStack.concat( this.keypath + '.' + i ); - fragmentOptions.index = i; - - this.fragments[i] = this.createFragment( fragmentOptions ); + function reevaluateContextSection( section, fragmentOptions ) { + var fragment; + // ...then if it isn't rendered, render it, adding section.keypath to the context stack + // (if it is already rendered, then any children dependent on the context stack + // will update themselves without any prompting) + if ( !section.length ) { + // append this section to the context stack + fragmentOptions.context = section.keypath; + fragmentOptions.index = 0; + fragment = new Fragment( fragmentOptions ); + section.fragmentsToRender.push( section.fragments[ 0 ] = fragment ); + section.length = 1; + return true; } - - // Append docfrag in front of insertion point - this.parentNode.insertBefore( this.docFrag, insertionPoint ); } - this.length += balance; - - - // Now we need to reassign existing fragments (e.g. items.4 -> items.3 - the keypaths, - // context stacks and index refs will have changed) - reassignStart = ( start + addedItems ); - - reassignFragments( this.root, this, reassignStart, this.length, balance ); - }, - - teardown: function ( detach ) { - this.teardownFragments( detach ); - - teardown( this ); - }, - - firstNode: function () { - if ( this.fragments[0] ) { - return this.fragments[0].firstNode(); + function reevaluateConditionalSection( section, value, inverted, fragmentOptions ) { + var doRender, emptyArray, emptyObject, fragment, name; + emptyArray = isArrayLike( value ) && value.length === 0; + emptyObject = false; + if ( !isArrayLike( value ) && isObject( value ) ) { + emptyObject = true; + for ( name in value ) { + emptyObject = false; + break; + } + } + if ( inverted ) { + doRender = emptyArray || emptyObject || !value; + } else { + doRender = value && !emptyArray && !emptyObject; + } + if ( doRender ) { + if ( !section.length ) { + // no change to context stack + fragmentOptions.index = 0; + fragment = new Fragment( fragmentOptions ); + section.fragmentsToRender.push( section.fragments[ 0 ] = fragment ); + section.length = 1; + return true; + } + if ( section.length > 1 ) { + section.fragmentsToUnrender = section.fragments.splice( 1 ); + section.fragmentsToUnrender.forEach( unbind ); + return true; + } + } else { + return removeSectionFragments( section ); + } } - return this.parentFragment.findNextNode( this ); - }, - - findNextNode: function ( fragment ) { - if ( this.fragments[ fragment.index + 1 ] ) { - return this.fragments[ fragment.index + 1 ].firstNode(); + function removeSectionFragments( section ) { + if ( section.length ) { + section.fragmentsToUnrender = section.fragments.splice( 0, section.fragments.length ).filter( isRendered ); + section.fragmentsToUnrender.forEach( unbind ); + section.length = section.fragmentsToRender.length = 0; + return true; + } } - return this.parentFragment.findNextNode( this ); - }, - - teardownFragments: function ( detach ) { - while ( this.fragments.length ) { - this.fragments.shift().teardown( detach ); + function unbind( fragment ) { + fragment.unbind(); } - }, - render: function ( value ) { - - updateSection( this, value ); - - if ( !this.initialising ) { - // we need to insert the contents of our document fragment into the correct place - this.parentNode.insertBefore( this.docFrag, this.parentFragment.findNextNode( this ) ); + function isRendered( fragment ) { + return fragment.rendered; } - }, + return __export; + }( types, isArrayLike, isObject, runloop, circular ); - createFragment: function ( options ) { - var fragment = new DomFragment( options ); - - if ( this.docFrag ) { - this.docFrag.appendChild( fragment.docFrag ); - } - - return fragment; - }, - - toString: function () { + /* virtualdom/items/Section/prototype/toString.js */ + var virtualdom_items_Section$toString = function Section$toString( escape ) { var str, i, len; - str = ''; - i = 0; len = this.length; - - for ( i=0; i<len; i+=1 ) { - str += this.fragments[i].toString(); + for ( i = 0; i < len; i += 1 ) { + str += this.fragments[ i ].toString( escape ); } - return str; - } -}; -// Plain text -DomText = function ( options, docFrag ) { - this.type = TEXT; - this.descriptor = options.descriptor; + }; - if ( docFrag ) { - this.node = doc.createTextNode( options.descriptor ); - this.parentNode = options.parentFragment.parentNode; + /* virtualdom/items/Section/prototype/unbind.js */ + var virtualdom_items_Section$unbind = function( unbind ) { - docFrag.appendChild( this.node ); - } -}; + var __export; + __export = function Section$unbind() { + this.fragments.forEach( unbindFragment ); + unbind.call( this ); + this.length = 0; + this.unbound = true; + }; -DomText.prototype = { - teardown: function ( detach ) { - if ( detach ) { - this.parentNode.removeChild( this.node ); + function unbindFragment( fragment ) { + fragment.unbind(); } - }, + return __export; + }( unbind ); - firstNode: function () { - return this.node; - }, + /* virtualdom/items/Section/prototype/unrender.js */ + var virtualdom_items_Section$unrender = function() { - toString: function () { - return ( '' + this.descriptor ).replace( '<', '&lt;' ).replace( '>', '&gt;' ); - } -}; -// Triple -DomTriple = function ( options, docFrag ) { - this.type = TRIPLE; + var __export; + __export = function Section$unrender( shouldDestroy ) { + this.fragments.forEach( shouldDestroy ? unrenderAndDestroy : unrender ); + }; - if ( docFrag ) { - this.nodes = []; - this.docFrag = doc.createDocumentFragment(); - } - - this.initialising = true; - initMustache( this, options ); - if ( docFrag ) { - docFrag.appendChild( this.docFrag ); - } - this.initialising = false; -}; - -DomTriple.prototype = { - update: updateMustache, - resolve: resolveMustache, - - teardown: function ( detach ) { - - // remove child nodes from DOM - if ( detach ) { - while ( this.nodes.length ) { - this.parentNode.removeChild( this.nodes.pop() ); - } + function unrenderAndDestroy( fragment ) { + fragment.unrender( true ); } - teardown( this ); - }, - - firstNode: function () { - if ( this.nodes[0] ) { - return this.nodes[0]; + function unrender( fragment ) { + fragment.unrender( false ); } + return __export; + }(); - return this.parentFragment.findNextNode( this ); - }, - - render: function ( html ) { - // remove existing nodes - while ( this.nodes.length ) { - this.parentNode.removeChild( this.nodes.pop() ); + /* virtualdom/items/Section/prototype/update.js */ + var virtualdom_items_Section$update = function Section$update() { + var fragment, renderIndex, renderedFragments, anchor, target, i, len; + // `this.renderedFragments` is in the order of the previous render. + // If fragments have shuffled about, this allows us to quickly + // reinsert them in the correct place + renderedFragments = this.renderedFragments; + // Remove fragments that have been marked for destruction + while ( fragment = this.fragmentsToUnrender.pop() ) { + fragment.unrender( true ); + renderedFragments.splice( renderedFragments.indexOf( fragment ), 1 ); } - - if ( html === undefined ) { - this.nodes = []; - return; + // Render new fragments (but don't insert them yet) + while ( fragment = this.fragmentsToRender.shift() ) { + fragment.render(); } - - // get new nodes - this.nodes = insertHtml( html, this.docFrag ); - - if ( !this.initialising ) { - this.parentNode.insertBefore( this.docFrag, this.parentFragment.findNextNode( this ) ); + if ( this.rendered ) { + target = this.parentFragment.getNode(); } - }, - - toString: function () { - return ( this.value !== undefined ? this.value : '' ); - } -}; -StringFragment = function ( options ) { - initFragment( this, options ); -}; - -StringFragment.prototype = { - createItem: function ( options ) { - if ( typeof options.descriptor === 'string' ) { - return new StringText( options.descriptor ); + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + fragment = this.fragments[ i ]; + renderIndex = renderedFragments.indexOf( fragment, i ); + // search from current index - it's guaranteed to be the same or higher + if ( renderIndex === i ) { + // already in the right place. insert accumulated nodes (if any) and carry on + if ( this.docFrag.childNodes.length ) { + anchor = fragment.firstNode(); + target.insertBefore( this.docFrag, anchor ); + } + continue; + } + this.docFrag.appendChild( fragment.detach() ); + // update renderedFragments + if ( renderIndex !== -1 ) { + renderedFragments.splice( renderIndex, 1 ); + } + renderedFragments.splice( i, 0, fragment ); } - - switch ( options.descriptor.t ) { - case INTERPOLATOR: return new StringInterpolator( options ); - case TRIPLE: return new StringInterpolator( options ); - case SECTION: return new StringSection( options ); - - default: throw 'Something went wrong in a rather interesting way'; + if ( this.rendered && this.docFrag.childNodes.length ) { + anchor = this.parentFragment.findNextNode( this ); + target.insertBefore( this.docFrag, anchor ); } - }, + // Save the rendering order for next time + this.renderedFragments = this.fragments.slice(); + }; + /* virtualdom/items/Section/_Section.js */ + var Section = function( types, Mustache, bubble, detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, shuffle, render, setValue, toString, unbind, unrender, update ) { - bubble: function () { - this.owner.bubble(); - }, + var Section = function( options ) { + this.type = types.SECTION; + this.subtype = options.template.n; + this.inverted = this.subtype === types.SECTION_UNLESS; + this.pElement = options.pElement; + this.fragments = []; + this.fragmentsToCreate = []; + this.fragmentsToRender = []; + this.fragmentsToUnrender = []; + this.renderedFragments = []; + this.length = 0; + // number of times this section is rendered + Mustache.init( this, options ); + }; + Section.prototype = { + bubble: bubble, + detach: detach, + find: find, + findAll: findAll, + findAllComponents: findAllComponents, + findComponent: findComponent, + findNextNode: findNextNode, + firstNode: firstNode, + getValue: Mustache.getValue, + shuffle: shuffle, + rebind: Mustache.rebind, + render: render, + resolve: Mustache.resolve, + setValue: setValue, + toString: toString, + unbind: unbind, + unrender: unrender, + update: update + }; + return Section; + }( types, Mustache, virtualdom_items_Section$bubble, virtualdom_items_Section$detach, virtualdom_items_Section$find, virtualdom_items_Section$findAll, virtualdom_items_Section$findAllComponents, virtualdom_items_Section$findComponent, virtualdom_items_Section$findNextNode, virtualdom_items_Section$firstNode, virtualdom_items_Section$shuffle, virtualdom_items_Section$render, virtualdom_items_Section$setValue, virtualdom_items_Section$toString, virtualdom_items_Section$unbind, virtualdom_items_Section$unrender, virtualdom_items_Section$update ); - teardown: function () { - var numItems, i; - - numItems = this.items.length; - for ( i=0; i<numItems; i+=1 ) { - this.items[i].teardown(); + /* virtualdom/items/Triple/prototype/detach.js */ + var virtualdom_items_Triple$detach = function Triple$detach() { + var len, i; + if ( this.docFrag ) { + len = this.nodes.length; + for ( i = 0; i < len; i += 1 ) { + this.docFrag.appendChild( this.nodes[ i ] ); + } + return this.docFrag; } - }, + }; - getValue: function () { - var value; - - // Accommodate boolean attributes - if ( this.items.length === 1 && this.items[0].type === INTERPOLATOR ) { - value = this.items[0].value; - if ( value !== undefined ) { - return value; + /* virtualdom/items/Triple/prototype/find.js */ + var virtualdom_items_Triple$find = function( matches ) { + + return function Triple$find( selector ) { + var i, len, node, queryResult; + len = this.nodes.length; + for ( i = 0; i < len; i += 1 ) { + node = this.nodes[ i ]; + if ( node.nodeType !== 1 ) { + continue; + } + if ( matches( node, selector ) ) { + return node; + } + if ( queryResult = node.querySelector( selector ) ) { + return queryResult; + } } - } - - return this.toString(); - }, + return null; + }; + }( matches ); - toString: function () { - return this.items.join( '' ); - }, + /* virtualdom/items/Triple/prototype/findAll.js */ + var virtualdom_items_Triple$findAll = function( matches ) { - toJson: function () { - var str, json; + return function Triple$findAll( selector, queryResult ) { + var i, len, node, queryAllResult, numNodes, j; + len = this.nodes.length; + for ( i = 0; i < len; i += 1 ) { + node = this.nodes[ i ]; + if ( node.nodeType !== 1 ) { + continue; + } + if ( matches( node, selector ) ) { + queryResult.push( node ); + } + if ( queryAllResult = node.querySelectorAll( selector ) ) { + numNodes = queryAllResult.length; + for ( j = 0; j < numNodes; j += 1 ) { + queryResult.push( queryAllResult[ j ] ); + } + } + } + }; + }( matches ); - str = this.toString(); + /* virtualdom/items/Triple/prototype/firstNode.js */ + var virtualdom_items_Triple$firstNode = function Triple$firstNode() { + if ( this.rendered && this.nodes[ 0 ] ) { + return this.nodes[ 0 ]; + } + return this.parentFragment.findNextNode( this ); + }; + /* virtualdom/items/Triple/helpers/insertHtml.js */ + var insertHtml = function( namespaces, createElement ) { + + var __export; + var elementCache = {}, + ieBug, ieBlacklist; try { - json = JSON.parse( str ); + createElement( 'table' ).innerHTML = 'foo'; } catch ( err ) { - json = str; + ieBug = true; + ieBlacklist = { + TABLE: [ + '<table class="x">', + '</table>' + ], + THEAD: [ + '<table><thead class="x">', + '</thead></table>' + ], + TBODY: [ + '<table><tbody class="x">', + '</tbody></table>' + ], + TR: [ + '<table><tr class="x">', + '</tr></table>' + ], + SELECT: [ + '<select class="x">', + '</select>' + ] + }; } + __export = function( html, node, docFrag ) { + var container, nodes = [], + wrapper, selectedOption, child, i; + // render 0 and false + if ( html != null && html !== '' ) { + if ( ieBug && ( wrapper = ieBlacklist[ node.tagName ] ) ) { + container = element( 'DIV' ); + container.innerHTML = wrapper[ 0 ] + html + wrapper[ 1 ]; + container = container.querySelector( '.x' ); + if ( container.tagName === 'SELECT' ) { + selectedOption = container.options[ container.selectedIndex ]; + } + } else if ( node.namespaceURI === namespaces.svg ) { + container = element( 'DIV' ); + container.innerHTML = '<svg class="x">' + html + '</svg>'; + container = container.querySelector( '.x' ); + } else { + container = element( node.tagName ); + container.innerHTML = html; + if ( container.tagName === 'SELECT' ) { + selectedOption = container.options[ container.selectedIndex ]; + } + } + while ( child = container.firstChild ) { + nodes.push( child ); + docFrag.appendChild( child ); + } + // This is really annoying. Extracting <option> nodes from the + // temporary container <select> causes the remaining ones to + // become selected. So now we have to deselect them. IE8, you + // amaze me. You really do + // ...and now Chrome too + if ( node.tagName === 'SELECT' ) { + i = nodes.length; + while ( i-- ) { + if ( nodes[ i ] !== selectedOption ) { + nodes[ i ].selected = false; + } + } + } + } + return nodes; + }; - return json; - } -}; -// Interpolator or Triple -StringInterpolator = function ( options ) { - this.type = INTERPOLATOR; - initMustache( this, options ); -}; + function element( tagName ) { + return elementCache[ tagName ] || ( elementCache[ tagName ] = createElement( tagName ) ); + } + return __export; + }( namespaces, createElement ); -StringInterpolator.prototype = { - update: updateMustache, - resolve: resolveMustache, - - render: function ( value ) { - this.value = value; - this.parentFragment.bubble(); - }, - - teardown: function () { - teardown( this ); - }, - - toString: function () { - return ( this.value === undefined ? '' : this.value ); - } -}; -// Section -StringSection = function ( options ) { - this.type = SECTION; - this.fragments = []; - this.length = 0; - - initMustache( this, options ); -}; - -StringSection.prototype = { - update: updateMustache, - resolve: resolveMustache, - - teardown: function () { - this.teardownFragments(); - - teardown( this ); - }, - - teardownFragments: function () { - while ( this.fragments.length ) { - this.fragments.shift().teardown(); + /* utils/toArray.js */ + var toArray = function toArray( arrayLike ) { + var array = [], + i = arrayLike.length; + while ( i-- ) { + array[ i ] = arrayLike[ i ]; } - this.length = 0; - }, + return array; + }; - bubble: function () { - this.value = this.fragments.join( '' ); - this.parentFragment.bubble(); - }, + /* virtualdom/items/Triple/helpers/updateSelect.js */ + var updateSelect = function( toArray ) { - render: function ( value ) { - updateSection( this, value ); - this.parentFragment.bubble(); - }, + var __export; + __export = function updateSelect( parentElement ) { + var selectedOptions, option, value; + if ( !parentElement || parentElement.name !== 'select' || !parentElement.binding ) { + return; + } + selectedOptions = toArray( parentElement.node.options ).filter( isSelected ); + // If one of them had a `selected` attribute, we need to sync + // the model to the view + if ( parentElement.getAttribute( 'multiple' ) ) { + value = selectedOptions.map( function( o ) { + return o.value; + } ); + } else if ( option = selectedOptions[ 0 ] ) { + value = option.value; + } + if ( value !== undefined ) { + parentElement.binding.setValue( value ); + } + parentElement.bubble(); + }; - createFragment: function ( options ) { - return new StringFragment( options ); - }, - - toString: function () { - return this.fragments.join( '' ); - } -}; -// Plain text -StringText = function ( text ) { - this.type = TEXT; - this.text = text; -}; - -StringText.prototype = { - toString: function () { - return this.text; - }, - - teardown: function () {} // no-op -}; -getEl = function ( input ) { - var output; - - if ( typeof window === 'undefined' || !doc || !input ) { - return null; - } - - // We already have a DOM node - no work to do. (Duck typing alert!) - if ( input.nodeType ) { - return input; - } - - // Get node from string - if ( typeof input === 'string' ) { - // try ID first - output = doc.getElementById( input ); - - // then as selector, if possible - if ( !output && doc.querySelector ) { - output = doc.querySelector( input ); + function isSelected( option ) { + return option.selected; } + return __export; + }( toArray ); - // did it work? - if ( output.nodeType ) { - return output; - } - } + /* virtualdom/items/Triple/prototype/render.js */ + var virtualdom_items_Triple$render = function( insertHtml, updateSelect ) { - // If we've been given a collection (jQuery, Zepto etc), extract the first item - if ( input[0] && input[0].nodeType ) { - return input[0]; - } + return function Triple$render() { + if ( this.rendered ) { + throw new Error( 'Attempted to render an item that was already rendered' ); + } + this.docFrag = document.createDocumentFragment(); + this.nodes = insertHtml( this.value, this.parentFragment.getNode(), this.docFrag ); + // Special case - we're inserting the contents of a <select> + updateSelect( this.pElement ); + this.rendered = true; + return this.docFrag; + }; + }( insertHtml, updateSelect ); - return null; -}; -toString = Object.prototype.toString; + /* virtualdom/items/Triple/prototype/setValue.js */ + var virtualdom_items_Triple$setValue = function( runloop ) { -// thanks, http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ -isArray = function ( obj ) { - return toString.call( obj ) === '[object Array]'; -}; + return function Triple$setValue( value ) { + var wrapper; + // TODO is there a better way to approach this? + if ( wrapper = this.root.viewmodel.wrapped[ this.keypath ] ) { + value = wrapper.get(); + } + if ( value !== this.value ) { + this.value = value; + this.parentFragment.bubble(); + if ( this.rendered ) { + runloop.addView( this ); + } + } + }; + }( runloop ); -isEqual = function ( a, b ) { - if ( a === null && b === null ) { - return true; - } + /* virtualdom/items/Triple/prototype/toString.js */ + var virtualdom_items_Triple$toString = function( decodeCharacterReferences ) { - if ( typeof a === 'object' || typeof b === 'object' ) { - return false; - } + return function Triple$toString() { + return this.value != undefined ? decodeCharacterReferences( '' + this.value ) : ''; + }; + }( decodeCharacterReferences ); - return a === b; -}; + /* virtualdom/items/Triple/prototype/unrender.js */ + var virtualdom_items_Triple$unrender = function( detachNode ) { -// http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric -isNumeric = function ( n ) { - return !isNaN( parseFloat( n ) ) && isFinite( n ); -}; + return function Triple$unrender( shouldDestroy ) { + if ( this.rendered && shouldDestroy ) { + this.nodes.forEach( detachNode ); + this.rendered = false; + } + }; + }( detachNode ); -isObject = function ( obj ) { - return ( typeof obj === 'object' && toString.call( obj ) === '[object Object]' ); -}; -// We're not using a constructor here because it's convenient (and more -// efficient) to pass e.g. transitionManager.pop as a callback, rather -// than wrapping a prototype method in an anonymous function each time -makeTransitionManager = function ( root, callback ) { - var transitionManager, nodesToDetach, detachNodes, nodeHasNoTransitioningChildren; + /* virtualdom/items/Triple/prototype/update.js */ + var virtualdom_items_Triple$update = function( insertHtml, updateSelect ) { - nodesToDetach = []; - - // detach any nodes which a) need to be detached and b) have no child nodes - // which are actively transitioning. This will be called each time a - // transition completes - detachNodes = function () { - var i, node; - - i = nodesToDetach.length; - while ( i-- ) { - node = nodesToDetach[i]; - - // see if this node can be detached yet - if ( nodeHasNoTransitioningChildren( node ) ) { + return function Triple$update() { + var node, parentNode; + if ( !this.rendered ) { + return; + } + // Remove existing nodes + while ( this.nodes && this.nodes.length ) { + node = this.nodes.pop(); node.parentNode.removeChild( node ); - nodesToDetach.splice( i, 1 ); } - } - }; + // Insert new nodes + parentNode = this.parentFragment.getNode(); + this.nodes = insertHtml( this.value, parentNode, this.docFrag ); + parentNode.insertBefore( this.docFrag, this.parentFragment.findNextNode( this ) ); + // Special case - we're inserting the contents of a <select> + updateSelect( this.pElement ); + }; + }( insertHtml, updateSelect ); - nodeHasNoTransitioningChildren = function ( node ) { - var i, candidate; + /* virtualdom/items/Triple/_Triple.js */ + var Triple = function( types, Mustache, detach, find, findAll, firstNode, render, setValue, toString, unrender, update, unbind ) { - i = transitionManager.active.length; - while ( i-- ) { - candidate = transitionManager.active[i]; + var Triple = function( options ) { + this.type = types.TRIPLE; + Mustache.init( this, options ); + }; + Triple.prototype = { + detach: detach, + find: find, + findAll: findAll, + firstNode: firstNode, + getValue: Mustache.getValue, + rebind: Mustache.rebind, + render: render, + resolve: Mustache.resolve, + setValue: setValue, + toString: toString, + unbind: unbind, + unrender: unrender, + update: update + }; + return Triple; + }( types, Mustache, virtualdom_items_Triple$detach, virtualdom_items_Triple$find, virtualdom_items_Triple$findAll, virtualdom_items_Triple$firstNode, virtualdom_items_Triple$render, virtualdom_items_Triple$setValue, virtualdom_items_Triple$toString, virtualdom_items_Triple$unrender, virtualdom_items_Triple$update, unbind ); - if ( node.contains( candidate ) ) { - // fail as soon as possible - return false; + /* virtualdom/items/Element/prototype/bubble.js */ + var virtualdom_items_Element$bubble = function() { + this.parentFragment.bubble(); + }; + + /* virtualdom/items/Element/prototype/detach.js */ + var virtualdom_items_Element$detach = function Element$detach() { + var node = this.node, + parentNode; + if ( node ) { + // need to check for parent node - DOM may have been altered + // by something other than Ractive! e.g. jQuery UI... + if ( parentNode = node.parentNode ) { + parentNode.removeChild( node ); } + return node; } - - return true; }; - transitionManager = { - active: [], - info: { i: 0 }, - push: function ( node ) { - transitionManager.active[ transitionManager.active.length ] = node; - transitionManager.info.i += 1; - }, - pop: function ( node ) { - transitionManager.active.splice( transitionManager.active.indexOf( node ), 1 ); - - detachNodes(); + /* virtualdom/items/Element/prototype/find.js */ + var virtualdom_items_Element$find = function( matches ) { - if ( !transitionManager.active.length && transitionManager._ready ) { - transitionManager.complete(); + return function( selector ) { + if ( matches( this.node, selector ) ) { + return this.node; } - }, - complete: function () { - if ( callback ) { - callback.call( root ); + if ( this.fragment && this.fragment.find ) { + return this.fragment.find( selector ); } - }, - ready: function () { - detachNodes(); + }; + }( matches ); - transitionManager._ready = true; - if ( !transitionManager.active.length ) { - transitionManager.complete(); - } - }, - detachWhenReady: function ( node ) { - nodesToDetach[ nodesToDetach.length ] = node; + /* virtualdom/items/Element/prototype/findAll.js */ + var virtualdom_items_Element$findAll = function( selector, query ) { + // Add this node to the query, if applicable, and register the + // query on this element + if ( query._test( this, true ) && query.live ) { + ( this.liveQueries || ( this.liveQueries = [] ) ).push( query ); } + if ( this.fragment ) { + this.fragment.findAll( selector, query ); + } }; - return transitionManager; -}; -splitKeypath = function ( keypath ) { - var index, startIndex, keys, remaining, part; + /* virtualdom/items/Element/prototype/findAllComponents.js */ + var virtualdom_items_Element$findAllComponents = function( selector, query ) { + if ( this.fragment ) { + this.fragment.findAllComponents( selector, query ); + } + }; - // We should only have to do all the heavy regex stuff once... caching FTW - if ( keypathCache[ keypath ] ) { - return keypathCache[ keypath ].concat(); - } + /* virtualdom/items/Element/prototype/findComponent.js */ + var virtualdom_items_Element$findComponent = function( selector ) { + if ( this.fragment ) { + return this.fragment.findComponent( selector ); + } + }; - keys = []; - remaining = keypath; - - startIndex = 0; + /* virtualdom/items/Element/prototype/findNextNode.js */ + var virtualdom_items_Element$findNextNode = function Element$findNextNode() { + return null; + }; - // Split into keys - while ( remaining.length ) { - // Find next dot - index = remaining.indexOf( '.', startIndex ); + /* virtualdom/items/Element/prototype/firstNode.js */ + var virtualdom_items_Element$firstNode = function Element$firstNode() { + return this.node; + }; - // Final part? - if ( index === -1 ) { - part = remaining; - remaining = ''; + /* virtualdom/items/Element/prototype/getAttribute.js */ + var virtualdom_items_Element$getAttribute = function Element$getAttribute( name ) { + if ( !this.attributes || !this.attributes[ name ] ) { + return; } + return this.attributes[ name ].value; + }; - else { - // If this dot is preceded by a backslash, which isn't - // itself preceded by a backslash, we consider it escaped - if ( remaining.charAt( index - 1) === '\\' && remaining.charAt( index - 2 ) !== '\\' ) { - // we don't want to keep this part, we want to keep looking - // for the separator - startIndex = index + 1; - continue; + /* virtualdom/items/Element/shared/enforceCase.js */ + var enforceCase = function() { + + var svgCamelCaseElements, svgCamelCaseAttributes, createMap, map; + svgCamelCaseElements = 'altGlyph altGlyphDef altGlyphItem animateColor animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence foreignObject glyphRef linearGradient radialGradient textPath vkern'.split( ' ' ); + svgCamelCaseAttributes = 'attributeName attributeType baseFrequency baseProfile calcMode clipPathUnits contentScriptType contentStyleType diffuseConstant edgeMode externalResourcesRequired filterRes filterUnits glyphRef gradientTransform gradientUnits kernelMatrix kernelUnitLength keyPoints keySplines keyTimes lengthAdjust limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves pathLength patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits refX refY repeatCount repeatDur requiredExtensions requiredFeatures specularConstant specularExponent spreadMethod startOffset stdDeviation stitchTiles surfaceScale systemLanguage tableValues targetX targetY textLength viewBox viewTarget xChannelSelector yChannelSelector zoomAndPan'.split( ' ' ); + createMap = function( items ) { + var map = {}, + i = items.length; + while ( i-- ) { + map[ items[ i ].toLowerCase() ] = items[ i ]; } + return map; + }; + map = createMap( svgCamelCaseElements.concat( svgCamelCaseAttributes ) ); + return function( elementName ) { + var lowerCaseElementName = elementName.toLowerCase(); + return map[ lowerCaseElementName ] || lowerCaseElementName; + }; + }(); - // Otherwise, we have our next part - part = remaining.substr( 0, index ); - startIndex = 0; - } + /* virtualdom/items/Element/Attribute/prototype/bubble.js */ + var virtualdom_items_Element_Attribute$bubble = function( runloop, isEqual ) { - if ( /\[/.test( part ) ) { - keys = keys.concat( part.replace( /\[\s*([0-9]+)\s*\]/g, '.$1' ).split( '.' ) ); - } else { - keys[ keys.length ] = part; - } - - remaining = remaining.substring( index + 1 ); - } + return function Attribute$bubble() { + var value = this.fragment.getValue(); + // TODO this can register the attribute multiple times (see render test + // 'Attribute with nested mustaches') + if ( !isEqual( value, this.value ) ) { + // Need to clear old id from ractive.nodes + if ( this.name === 'id' && this.value ) { + delete this.root.nodes[ this.value ]; + } + this.value = value; + if ( this.name === 'value' && this.node ) { + // We need to store the value on the DOM like this so we + // can retrieve it later without it being coerced to a string + this.node._ractive.value = value; + } + if ( this.rendered ) { + runloop.addView( this ); + } + } + }; + }( runloop, isEqual ); - - keypathCache[ keypath ] = keys; - return keys.concat(); -}; -(function () { + /* config/booleanAttributes.js */ + var booleanAttributes = function() { - var getItem, - getText, - getMustache, - getElement, + // https://github.com/kangax/html-minifier/issues/63#issuecomment-37763316 + var booleanAttributes = /^(allowFullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultChecked|defaultMuted|defaultSelected|defer|disabled|draggable|enabled|formNoValidate|hidden|indeterminate|inert|isMap|itemScope|loop|multiple|muted|noHref|noResize|noShade|noValidate|noWrap|open|pauseOnExit|readOnly|required|reversed|scoped|seamless|selected|sortable|translate|trueSpeed|typeMustMatch|visible)$/i; + return booleanAttributes; + }(); - Fragment, - Text, - Mustache, - Section, - Element, - Expression, + /* virtualdom/items/Element/Attribute/helpers/determineNameAndNamespace.js */ + var determineNameAndNamespace = function( namespaces, enforceCase ) { - stringify, - jsonify; + return function( attribute, name ) { + var colonIndex, namespacePrefix; + // are we dealing with a namespaced attribute, e.g. xlink:href? + colonIndex = name.indexOf( ':' ); + if ( colonIndex !== -1 ) { + // looks like we are, yes... + namespacePrefix = name.substr( 0, colonIndex ); + // ...unless it's a namespace *declaration*, which we ignore (on the assumption + // that only valid namespaces will be used) + if ( namespacePrefix !== 'xmlns' ) { + name = name.substring( colonIndex + 1 ); + attribute.name = enforceCase( name ); + attribute.namespace = namespaces[ namespacePrefix.toLowerCase() ]; + attribute.namespacePrefix = namespacePrefix; + if ( !attribute.namespace ) { + throw 'Unknown namespace ("' + namespacePrefix + '")'; + } + return; + } + } + // SVG attribute names are case sensitive + attribute.name = attribute.element.namespace !== namespaces.html ? enforceCase( name ) : name; + }; + }( namespaces, enforceCase ); + /* virtualdom/items/Element/Attribute/helpers/getInterpolator.js */ + var getInterpolator = function( types ) { - getFragmentStubFromTokens = function ( tokens, options, preserveWhitespace ) { - var parser, stub; + return function getInterpolator( attribute ) { + var items = attribute.fragment.items; + if ( items.length !== 1 ) { + return; + } + if ( items[ 0 ].type === types.INTERPOLATOR ) { + return items[ 0 ]; + } + }; + }( types ); - parser = { - pos: 0, - tokens: tokens || [], - next: function () { - return parser.tokens[ parser.pos ]; - }, - options: options + /* virtualdom/items/Element/Attribute/helpers/determinePropertyName.js */ + var determinePropertyName = function( namespaces, booleanAttributes ) { + + var propertyNames = { + 'accept-charset': 'acceptCharset', + accesskey: 'accessKey', + bgcolor: 'bgColor', + 'class': 'className', + codebase: 'codeBase', + colspan: 'colSpan', + contenteditable: 'contentEditable', + datetime: 'dateTime', + dirname: 'dirName', + 'for': 'htmlFor', + 'http-equiv': 'httpEquiv', + ismap: 'isMap', + maxlength: 'maxLength', + novalidate: 'noValidate', + pubdate: 'pubDate', + readonly: 'readOnly', + rowspan: 'rowSpan', + tabindex: 'tabIndex', + usemap: 'useMap' }; + return function( attribute, options ) { + var propertyName; + if ( attribute.pNode && !attribute.namespace && ( !options.pNode.namespaceURI || options.pNode.namespaceURI === namespaces.html ) ) { + propertyName = propertyNames[ attribute.name ] || attribute.name; + if ( options.pNode[ propertyName ] !== undefined ) { + attribute.propertyName = propertyName; + } + // is attribute a boolean attribute or 'value'? If so we're better off doing e.g. + // node.selected = true rather than node.setAttribute( 'selected', '' ) + if ( booleanAttributes.test( propertyName ) || propertyName === 'value' ) { + attribute.useProperty = true; + } + } + }; + }( namespaces, booleanAttributes ); - stub = new Fragment( parser, preserveWhitespace ); + /* virtualdom/items/Element/Attribute/prototype/init.js */ + var virtualdom_items_Element_Attribute$init = function( types, booleanAttributes, determineNameAndNamespace, getInterpolator, determinePropertyName, circular ) { - return stub; - }; + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + return function Attribute$init( options ) { + this.type = types.ATTRIBUTE; + this.element = options.element; + this.root = options.root; + determineNameAndNamespace( this, options.name ); + // if it's an empty attribute, or just a straight key-value pair, with no + // mustache shenanigans, set the attribute accordingly and go home + if ( !options.value || typeof options.value === 'string' ) { + this.value = booleanAttributes.test( this.name ) ? true : options.value || ''; + return; + } + // otherwise we need to do some work + // share parentFragment with parent element + this.parentFragment = this.element.parentFragment; + this.fragment = new Fragment( { + template: options.value, + root: this.root, + owner: this + } ); + this.value = this.fragment.getValue(); + // Store a reference to this attribute's interpolator, if its fragment + // takes the form `{{foo}}`. This is necessary for two-way binding and + // for correctly rendering HTML later + this.interpolator = getInterpolator( this ); + this.isBindable = !!this.interpolator && !this.interpolator.isStatic; + // can we establish this attribute's property name equivalent? + determinePropertyName( this, options ); + // mark as ready + this.ready = true; + }; + }( types, booleanAttributes, determineNameAndNamespace, getInterpolator, determinePropertyName, circular ); - getItem = function ( parser, preserveWhitespace ) { - if ( !parser.next() ) { - return null; + /* virtualdom/items/Element/Attribute/prototype/rebind.js */ + var virtualdom_items_Element_Attribute$rebind = function Attribute$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + if ( this.fragment ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); } - - return getText( parser, preserveWhitespace ) - || getMustache( parser, preserveWhitespace ) - || getElement( parser, preserveWhitespace ); }; - getText = function ( parser, preserveWhitespace ) { - var next = parser.next(); + /* virtualdom/items/Element/Attribute/prototype/render.js */ + var virtualdom_items_Element_Attribute$render = function( namespaces, booleanAttributes ) { - if ( next.type === TEXT ) { - parser.pos += 1; - return new Text( next, preserveWhitespace ); - } + var propertyNames = { + 'accept-charset': 'acceptCharset', + 'accesskey': 'accessKey', + 'bgcolor': 'bgColor', + 'class': 'className', + 'codebase': 'codeBase', + 'colspan': 'colSpan', + 'contenteditable': 'contentEditable', + 'datetime': 'dateTime', + 'dirname': 'dirName', + 'for': 'htmlFor', + 'http-equiv': 'httpEquiv', + 'ismap': 'isMap', + 'maxlength': 'maxLength', + 'novalidate': 'noValidate', + 'pubdate': 'pubDate', + 'readonly': 'readOnly', + 'rowspan': 'rowSpan', + 'tabindex': 'tabIndex', + 'usemap': 'useMap' + }; + return function Attribute$render( node ) { + var propertyName; + this.node = node; + // should we use direct property access, or setAttribute? + if ( !node.namespaceURI || node.namespaceURI === namespaces.html ) { + propertyName = propertyNames[ this.name ] || this.name; + if ( node[ propertyName ] !== undefined ) { + this.propertyName = propertyName; + } + // is attribute a boolean attribute or 'value'? If so we're better off doing e.g. + // node.selected = true rather than node.setAttribute( 'selected', '' ) + if ( booleanAttributes.test( propertyName ) || propertyName === 'value' ) { + this.useProperty = true; + } + if ( propertyName === 'value' ) { + this.useProperty = true; + node._ractive.value = this.value; + } + } + this.rendered = true; + this.update(); + }; + }( namespaces, booleanAttributes ); - return null; - }; + /* virtualdom/items/Element/Attribute/prototype/toString.js */ + var virtualdom_items_Element_Attribute$toString = function( booleanAttributes ) { - getMustache = function ( parser, preserveWhitespace ) { - var next = parser.next(); - - if ( next.type === MUSTACHE || next.type === TRIPLE ) { - if ( next.mustacheType === SECTION || next.mustacheType === INVERTED ) { - return new Section( next, parser, preserveWhitespace ); + var __export; + __export = function Attribute$toString() { + var name = ( fragment = this ).name, + namespacePrefix = fragment.namespacePrefix, + value = fragment.value, + interpolator = fragment.interpolator, + fragment = fragment.fragment; + // Special case - select and textarea values (should not be stringified) + if ( name === 'value' && ( this.element.name === 'select' || this.element.name === 'textarea' ) ) { + return; } + // Special case - content editable + if ( name === 'value' && this.element.getAttribute( 'contenteditable' ) !== undefined ) { + return; + } + // Special case - radio names + if ( name === 'name' && this.element.name === 'input' && interpolator ) { + return 'name={{' + ( interpolator.keypath || interpolator.ref ) + '}}'; + } + // Boolean attributes + if ( booleanAttributes.test( name ) ) { + return value ? name : ''; + } + if ( fragment ) { + value = fragment.toString(); + } + if ( namespacePrefix ) { + name = namespacePrefix + ':' + name; + } + return value ? name + '="' + escape( value ) + '"' : name; + }; - return new Mustache( next, parser ); + function escape( value ) { + return value.replace( /&/g, '&amp;' ).replace( /"/g, '&quot;' ).replace( /'/g, '&#39;' ); } + return __export; + }( booleanAttributes ); - return null; + /* virtualdom/items/Element/Attribute/prototype/unbind.js */ + var virtualdom_items_Element_Attribute$unbind = function Attribute$unbind() { + // ignore non-dynamic attributes + if ( this.fragment ) { + this.fragment.unbind(); + } + if ( this.name === 'id' ) { + delete this.root.nodes[ this.value ]; + } }; - getElement = function ( parser, preserveWhitespace ) { - var next = parser.next(), stub; - - if ( next.type === TAG ) { - stub = new Element( next, parser, preserveWhitespace ); - - // sanitize - if ( parser.options.sanitize && parser.options.sanitize.elements ) { - if ( parser.options.sanitize.elements.indexOf( stub.lcTag ) !== -1 ) { - return null; + /* virtualdom/items/Element/Attribute/prototype/update/updateSelectValue.js */ + var virtualdom_items_Element_Attribute$update_updateSelectValue = function Attribute$updateSelect() { + var value = this.value, + options, option, optionValue, i; + if ( !this.locked ) { + this.node._ractive.value = value; + options = this.node.options; + i = options.length; + while ( i-- ) { + option = options[ i ]; + optionValue = option._ractive ? option._ractive.value : option.value; + // options inserted via a triple don't have _ractive + if ( optionValue == value ) { + // double equals as we may be comparing numbers with strings + option.selected = true; + break; } } - - return stub; } - - return null; }; - stringify = function ( items ) { - var str = '', itemStr, i, len; - - if ( !items ) { - return ''; - } - - for ( i=0, len=items.length; i<len; i+=1 ) { - itemStr = items[i].toString(); - - if ( itemStr === false ) { - return false; + /* utils/arrayContains.js */ + var arrayContains = function arrayContains( array, value ) { + for ( var i = 0, c = array.length; i < c; i++ ) { + if ( array[ i ] == value ) { + return true; } - - str += itemStr; } - - return str; + return false; }; - jsonify = function ( items, noStringify ) { - var str, json; + /* virtualdom/items/Element/Attribute/prototype/update/updateMultipleSelectValue.js */ + var virtualdom_items_Element_Attribute$update_updateMultipleSelectValue = function( arrayContains, isArray ) { - if ( !noStringify ) { - str = stringify( items ); - if ( str !== false ) { - return str; + return function Attribute$updateMultipleSelect() { + var value = this.value, + options, i, option, optionValue; + if ( !isArray( value ) ) { + value = [ value ]; } - } + options = this.node.options; + i = options.length; + while ( i-- ) { + option = options[ i ]; + optionValue = option._ractive ? option._ractive.value : option.value; + // options inserted via a triple don't have _ractive + option.selected = arrayContains( value, optionValue ); + } + }; + }( arrayContains, isArray ); - json = items.map( function ( item ) { - return item.toJson( noStringify ); - }); - - return json; + /* virtualdom/items/Element/Attribute/prototype/update/updateRadioName.js */ + var virtualdom_items_Element_Attribute$update_updateRadioName = function Attribute$updateRadioName() { + var node = ( value = this ).node, + value = value.value; + node.checked = value == node._ractive.value; }; + /* virtualdom/items/Element/Attribute/prototype/update/updateRadioValue.js */ + var virtualdom_items_Element_Attribute$update_updateRadioValue = function( runloop ) { + return function Attribute$updateRadioValue() { + var wasChecked, node = this.node, + binding, bindings, i; + wasChecked = node.checked; + node.value = this.element.getAttribute( 'value' ); + node.checked = this.element.getAttribute( 'value' ) === this.element.getAttribute( 'name' ); + // This is a special case - if the input was checked, and the value + // changed so that it's no longer checked, the twoway binding is + // most likely out of date. To fix it we have to jump through some + // hoops... this is a little kludgy but it works + if ( wasChecked && !node.checked && this.element.binding ) { + bindings = this.element.binding.siblings; + if ( i = bindings.length ) { + while ( i-- ) { + binding = bindings[ i ]; + if ( !binding.element.node ) { + // this is the initial render, siblings are still rendering! + // we'll come back later... + return; + } + if ( binding.element.node.checked ) { + runloop.addViewmodel( binding.root.viewmodel ); + return binding.handleChange(); + } + } + runloop.addViewmodel( binding.root.viewmodel ); + this.root.viewmodel.set( binding.keypath, undefined ); + } + } + }; + }( runloop ); - Fragment = function ( parser, preserveWhitespace ) { - var items, item; + /* virtualdom/items/Element/Attribute/prototype/update/updateCheckboxName.js */ + var virtualdom_items_Element_Attribute$update_updateCheckboxName = function( isArray ) { - items = this.items = []; + return function Attribute$updateCheckboxName() { + var element = ( value = this ).element, + node = value.node, + value = value.value, + valueAttribute, i; + valueAttribute = element.getAttribute( 'value' ); + if ( !isArray( value ) ) { + node.checked = value == valueAttribute; + } else { + i = value.length; + while ( i-- ) { + if ( valueAttribute == value[ i ] ) { + node.checked = true; + return; + } + } + node.checked = false; + } + }; + }( isArray ); - item = getItem( parser, preserveWhitespace ); - while ( item !== null ) { - items[ items.length ] = item; - item = getItem( parser, preserveWhitespace ); + /* virtualdom/items/Element/Attribute/prototype/update/updateClassName.js */ + var virtualdom_items_Element_Attribute$update_updateClassName = function Attribute$updateClassName() { + var node, value; + node = this.node; + value = this.value; + if ( value === undefined ) { + value = ''; } + node.className = value; }; - Fragment.prototype = { - toJson: function ( noStringify ) { - var json = jsonify( this.items, noStringify ); - return json; - }, + /* virtualdom/items/Element/Attribute/prototype/update/updateIdAttribute.js */ + var virtualdom_items_Element_Attribute$update_updateIdAttribute = function Attribute$updateIdAttribute() { + var node = ( value = this ).node, + value = value.value; + this.root.nodes[ value ] = node; + node.id = value; + }; - toString: function () { - var str = stringify( this.items ); - return str; + /* virtualdom/items/Element/Attribute/prototype/update/updateIEStyleAttribute.js */ + var virtualdom_items_Element_Attribute$update_updateIEStyleAttribute = function Attribute$updateIEStyleAttribute() { + var node, value; + node = this.node; + value = this.value; + if ( value === undefined ) { + value = ''; } + node.style.setAttribute( 'cssText', value ); }; + /* virtualdom/items/Element/Attribute/prototype/update/updateContentEditableValue.js */ + var virtualdom_items_Element_Attribute$update_updateContentEditableValue = function Attribute$updateContentEditableValue() { + var value = this.value; + if ( value === undefined ) { + value = ''; + } + if ( !this.locked ) { + this.node.innerHTML = value; + } + }; - // text - (function () { - var htmlEntities, decodeCharacterReferences, whitespace; + /* virtualdom/items/Element/Attribute/prototype/update/updateValue.js */ + var virtualdom_items_Element_Attribute$update_updateValue = function Attribute$updateValue() { + var node = ( value = this ).node, + value = value.value; + // store actual value, so it doesn't get coerced to a string + node._ractive.value = value; + // with two-way binding, only update if the change wasn't initiated by the user + // otherwise the cursor will often be sent to the wrong place + if ( !this.locked ) { + node.value = value == undefined ? '' : value; + } + }; - Text = function ( token, preserveWhitespace ) { - this.type = TEXT; - this.text = ( preserveWhitespace ? token.value : token.value.replace( whitespace, ' ' ) ); - }; + /* virtualdom/items/Element/Attribute/prototype/update/updateBoolean.js */ + var virtualdom_items_Element_Attribute$update_updateBoolean = function Attribute$updateBooleanAttribute() { + // with two-way binding, only update if the change wasn't initiated by the user + // otherwise the cursor will often be sent to the wrong place + if ( !this.locked ) { + this.node[ this.propertyName ] = this.value; + } + }; - Text.prototype = { - toJson: function () { - // this will be used as text, so we need to decode things like &amp; - return this.decoded || ( this.decoded = decodeCharacterReferences( this.text) ); - }, + /* virtualdom/items/Element/Attribute/prototype/update/updateEverythingElse.js */ + var virtualdom_items_Element_Attribute$update_updateEverythingElse = function( booleanAttributes ) { - toString: function () { - // this will be used as straight text - return this.text; + return function Attribute$updateEverythingElse() { + var node = ( fragment = this ).node, + namespace = fragment.namespace, + name = fragment.name, + value = fragment.value, + fragment = fragment.fragment; + if ( namespace ) { + node.setAttributeNS( namespace, name, ( fragment || value ).toString() ); + } else if ( !booleanAttributes.test( name ) ) { + node.setAttribute( name, ( fragment || value ).toString() ); + } else { + if ( value ) { + node.setAttribute( name, '' ); + } else { + node.removeAttribute( name ); + } } }; + }( booleanAttributes ); - htmlEntities = { quot: 34, amp: 38, apos: 39, lt: 60, gt: 62, nbsp: 160, iexcl: 161, cent: 162, pound: 163, curren: 164, yen: 165, brvbar: 166, sect: 167, uml: 168, copy: 169, ordf: 170, laquo: 171, not: 172, shy: 173, reg: 174, macr: 175, deg: 176, plusmn: 177, sup2: 178, sup3: 179, acute: 180, micro: 181, para: 182, middot: 183, cedil: 184, sup1: 185, ordm: 186, raquo: 187, frac14: 188, frac12: 189, frac34: 190, iquest: 191, Agrave: 192, Aacute: 193, Acirc: 194, Atilde: 195, Auml: 196, Aring: 197, AElig: 198, Ccedil: 199, Egrave: 200, Eacute: 201, Ecirc: 202, Euml: 203, Igrave: 204, Iacute: 205, Icirc: 206, Iuml: 207, ETH: 208, Ntilde: 209, Ograve: 210, Oacute: 211, Ocirc: 212, Otilde: 213, Ouml: 214, times: 215, Oslash: 216, Ugrave: 217, Uacute: 218, Ucirc: 219, Uuml: 220, Yacute: 221, THORN: 222, szlig: 223, agrave: 224, aacute: 225, acirc: 226, atilde: 227, auml: 228, aring: 229, aelig: 230, ccedil: 231, egrave: 232, eacute: 233, ecirc: 234, euml: 235, igrave: 236, iacute: 237, icirc: 238, iuml: 239, eth: 240, ntilde: 241, ograve: 242, oacute: 243, ocirc: 244, otilde: 245, ouml: 246, divide: 247, oslash: 248, ugrave: 249, uacute: 250, ucirc: 251, uuml: 252, yacute: 253, thorn: 254, yuml: 255, OElig: 338, oelig: 339, Scaron: 352, scaron: 353, Yuml: 376, fnof: 402, circ: 710, tilde: 732, Alpha: 913, Beta: 914, Gamma: 915, Delta: 916, Epsilon: 917, Zeta: 918, Eta: 919, Theta: 920, Iota: 921, Kappa: 922, Lambda: 923, Mu: 924, Nu: 925, Xi: 926, Omicron: 927, Pi: 928, Rho: 929, Sigma: 931, Tau: 932, Upsilon: 933, Phi: 934, Chi: 935, Psi: 936, Omega: 937, alpha: 945, beta: 946, gamma: 947, delta: 948, epsilon: 949, zeta: 950, eta: 951, theta: 952, iota: 953, kappa: 954, lambda: 955, mu: 956, nu: 957, xi: 958, omicron: 959, pi: 960, rho: 961, sigmaf: 962, sigma: 963, tau: 964, upsilon: 965, phi: 966, chi: 967, psi: 968, omega: 969, thetasym: 977, upsih: 978, piv: 982, ensp: 8194, emsp: 8195, thinsp: 8201, zwnj: 8204, zwj: 8205, lrm: 8206, rlm: 8207, ndash: 8211, mdash: 8212, lsquo: 8216, rsquo: 8217, sbquo: 8218, ldquo: 8220, rdquo: 8221, bdquo: 8222, dagger: 8224, Dagger: 8225, bull: 8226, hellip: 8230, permil: 8240, prime: 8242, Prime: 8243, lsaquo: 8249, rsaquo: 8250, oline: 8254, frasl: 8260, euro: 8364, image: 8465, weierp: 8472, real: 8476, trade: 8482, alefsym: 8501, larr: 8592, uarr: 8593, rarr: 8594, darr: 8595, harr: 8596, crarr: 8629, lArr: 8656, uArr: 8657, rArr: 8658, dArr: 8659, hArr: 8660, forall: 8704, part: 8706, exist: 8707, empty: 8709, nabla: 8711, isin: 8712, notin: 8713, ni: 8715, prod: 8719, sum: 8721, minus: 8722, lowast: 8727, radic: 8730, prop: 8733, infin: 8734, ang: 8736, and: 8743, or: 8744, cap: 8745, cup: 8746, 'int': 8747, there4: 8756, sim: 8764, cong: 8773, asymp: 8776, ne: 8800, equiv: 8801, le: 8804, ge: 8805, sub: 8834, sup: 8835, nsub: 8836, sube: 8838, supe: 8839, oplus: 8853, otimes: 8855, perp: 8869, sdot: 8901, lceil: 8968, rceil: 8969, lfloor: 8970, rfloor: 8971, lang: 9001, rang: 9002, loz: 9674, spades: 9824, clubs: 9827, hearts: 9829, diams: 9830 }; + /* virtualdom/items/Element/Attribute/prototype/update.js */ + var virtualdom_items_Element_Attribute$update = function( namespaces, noop, updateSelectValue, updateMultipleSelectValue, updateRadioName, updateRadioValue, updateCheckboxName, updateClassName, updateIdAttribute, updateIEStyleAttribute, updateContentEditableValue, updateValue, updateBoolean, updateEverythingElse ) { - decodeCharacterReferences = function ( html ) { - var result; - - // named entities - result = html.replace( /&([a-zA-Z]+);/, function ( match, name ) { - if ( htmlEntities[ name ] ) { - return String.fromCharCode( htmlEntities[ name ] ); + return function Attribute$update() { + var name = ( node = this ).name, + element = node.element, + node = node.node, + type, updateMethod; + if ( name === 'id' ) { + updateMethod = updateIdAttribute; + } else if ( name === 'value' ) { + // special case - selects + if ( element.name === 'select' && name === 'value' ) { + updateMethod = element.getAttribute( 'multiple' ) ? updateMultipleSelectValue : updateSelectValue; + } else if ( element.name === 'textarea' ) { + updateMethod = updateValue; + } else if ( element.getAttribute( 'contenteditable' ) != null ) { + updateMethod = updateContentEditableValue; + } else if ( element.name === 'input' ) { + type = element.getAttribute( 'type' ); + // type='file' value='{{fileList}}'> + if ( type === 'file' ) { + updateMethod = noop; + } else if ( type === 'radio' && element.binding && element.binding.name === 'name' ) { + updateMethod = updateRadioValue; + } else { + updateMethod = updateValue; + } } + } else if ( this.twoway && name === 'name' ) { + if ( node.type === 'radio' ) { + updateMethod = updateRadioName; + } else if ( node.type === 'checkbox' ) { + updateMethod = updateCheckboxName; + } + } else if ( name === 'style' && node.style.setAttribute ) { + updateMethod = updateIEStyleAttribute; + } else if ( name === 'class' && ( !node.namespaceURI || node.namespaceURI === namespaces.html ) ) { + updateMethod = updateClassName; + } else if ( this.useProperty ) { + updateMethod = updateBoolean; + } + if ( !updateMethod ) { + updateMethod = updateEverythingElse; + } + this.update = updateMethod; + this.update(); + }; + }( namespaces, noop, virtualdom_items_Element_Attribute$update_updateSelectValue, virtualdom_items_Element_Attribute$update_updateMultipleSelectValue, virtualdom_items_Element_Attribute$update_updateRadioName, virtualdom_items_Element_Attribute$update_updateRadioValue, virtualdom_items_Element_Attribute$update_updateCheckboxName, virtualdom_items_Element_Attribute$update_updateClassName, virtualdom_items_Element_Attribute$update_updateIdAttribute, virtualdom_items_Element_Attribute$update_updateIEStyleAttribute, virtualdom_items_Element_Attribute$update_updateContentEditableValue, virtualdom_items_Element_Attribute$update_updateValue, virtualdom_items_Element_Attribute$update_updateBoolean, virtualdom_items_Element_Attribute$update_updateEverythingElse ); - return match; - }); + /* virtualdom/items/Element/Attribute/_Attribute.js */ + var Attribute = function( bubble, init, rebind, render, toString, unbind, update ) { - // hex references - result = result.replace( /&#x([0-9]+);/, function ( match, hex ) { - return String.fromCharCode( parseInt( hex, 16 ) ); - }); + var Attribute = function( options ) { + this.init( options ); + }; + Attribute.prototype = { + bubble: bubble, + init: init, + rebind: rebind, + render: render, + toString: toString, + unbind: unbind, + update: update + }; + return Attribute; + }( virtualdom_items_Element_Attribute$bubble, virtualdom_items_Element_Attribute$init, virtualdom_items_Element_Attribute$rebind, virtualdom_items_Element_Attribute$render, virtualdom_items_Element_Attribute$toString, virtualdom_items_Element_Attribute$unbind, virtualdom_items_Element_Attribute$update ); - // decimal references - result = result.replace( /&#([0-9]+);/, function ( match, num ) { - return String.fromCharCode( num ); - }); + /* virtualdom/items/Element/prototype/init/createAttributes.js */ + var virtualdom_items_Element$init_createAttributes = function( Attribute ) { + return function( element, attributes ) { + var name, attribute, result = []; + for ( name in attributes ) { + if ( attributes.hasOwnProperty( name ) ) { + attribute = new Attribute( { + element: element, + name: name, + value: attributes[ name ], + root: element.root + } ); + result.push( result[ name ] = attribute ); + } + } return result; }; + }( Attribute ); - whitespace = /\s+/g; - }()); + /* virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js */ + var ConditionalAttribute = function( circular, namespaces, createElement, toArray ) { - - // mustache - (function () { - Mustache = function ( token, parser ) { - this.type = ( token.type === TRIPLE ? TRIPLE : token.mustacheType ); - - if ( token.ref ) { - this.ref = token.ref; + var __export; + var Fragment, div; + if ( typeof document !== 'undefined' ) { + div = createElement( 'div' ); + } + circular.push( function() { + Fragment = circular.Fragment; + } ); + var ConditionalAttribute = function( element, template ) { + this.element = element; + this.root = element.root; + this.parentFragment = element.parentFragment; + this.attributes = []; + this.fragment = new Fragment( { + root: element.root, + owner: this, + template: [ template ] + } ); + }; + ConditionalAttribute.prototype = { + bubble: function() { + if ( this.node ) { + this.update(); + } + this.element.bubble(); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + render: function( node ) { + this.node = node; + this.isSvg = node.namespaceURI === namespaces.svg; + this.update(); + }, + unbind: function() { + this.fragment.unbind(); + }, + update: function() { + var this$0 = this; + var str, attrs; + str = this.fragment.toString(); + attrs = parseAttributes( str, this.isSvg ); + // any attributes that previously existed but no longer do + // must be removed + this.attributes.filter( function( a ) { + return notIn( attrs, a ); + } ).forEach( function( a ) { + this$0.node.removeAttribute( a.name ); + } ); + attrs.forEach( function( a ) { + this$0.node.setAttribute( a.name, a.value ); + } ); + this.attributes = attrs; + }, + toString: function() { + return this.fragment.toString(); } - - if ( token.expression ) { - this.expr = new Expression( token.expression ); - } - - parser.pos += 1; }; + __export = ConditionalAttribute; - Mustache.prototype = { - toJson: function () { - var json; + function parseAttributes( str, isSvg ) { + var tag = isSvg ? 'svg' : 'div'; + div.innerHTML = '<' + tag + ' ' + str + '></' + tag + '>'; + return toArray( div.childNodes[ 0 ].attributes ); + } - if ( this.json ) { - return this.json; + function notIn( haystack, needle ) { + var i = haystack.length; + while ( i-- ) { + if ( haystack[ i ].name === needle.name ) { + return false; } + } + return true; + } + return __export; + }( circular, namespaces, createElement, toArray ); - json = { - t: this.type - }; + /* virtualdom/items/Element/prototype/init/createConditionalAttributes.js */ + var virtualdom_items_Element$init_createConditionalAttributes = function( ConditionalAttribute ) { - if ( this.ref ) { - json.r = this.ref; - } + return function( element, attributes ) { + if ( !attributes ) { + return []; + } + return attributes.map( function( a ) { + return new ConditionalAttribute( element, a ); + } ); + }; + }( ConditionalAttribute ); - if ( this.expr ) { - json.x = this.expr.toJson(); + /* utils/extend.js */ + var extend = function( target ) { + var SLICE$0 = Array.prototype.slice; + var sources = SLICE$0.call( arguments, 1 ); + var prop, source; + while ( source = sources.shift() ) { + for ( prop in source ) { + if ( source.hasOwnProperty( prop ) ) { + target[ prop ] = source[ prop ]; } + } + } + return target; + }; - this.json = json; - return json; - }, + /* virtualdom/items/Element/Binding/Binding.js */ + var Binding = function( runloop, warn, create, extend, removeFromArray ) { - toString: function () { - // mustaches cannot be stringified + var Binding = function( element ) { + var interpolator, keypath, value; + this.element = element; + this.root = element.root; + this.attribute = element.attributes[ this.name || 'value' ]; + interpolator = this.attribute.interpolator; + interpolator.twowayBinding = this; + if ( interpolator.keypath && interpolator.keypath.substr( 0, 2 ) === '${' ) { + warn( 'Two-way binding does not work with expressions (`' + interpolator.keypath.slice( 2, -1 ) + '`)' ); return false; } + // A mustache may be *ambiguous*. Let's say we were given + // `value="{{bar}}"`. If the context was `foo`, and `foo.bar` + // *wasn't* `undefined`, the keypath would be `foo.bar`. + // Then, any user input would result in `foo.bar` being updated. + // + // If, however, `foo.bar` *was* undefined, and so was `bar`, we would be + // left with an unresolved partial keypath - so we are forced to make an + // assumption. That assumption is that the input in question should + // be forced to resolve to `bar`, and any user input would affect `bar` + // and not `foo.bar`. + // + // Did that make any sense? No? Oh. Sorry. Well the moral of the story is + // be explicit when using two-way data-binding about what keypath you're + // updating. Using it in lists is probably a recipe for confusion... + if ( !interpolator.keypath ) { + interpolator.resolver.forceResolution(); + } + this.keypath = keypath = interpolator.keypath; + // initialise value, if it's undefined + if ( this.root.viewmodel.get( keypath ) === undefined && this.getInitialValue ) { + value = this.getInitialValue(); + if ( value !== undefined ) { + this.root.viewmodel.set( keypath, value ); + } + } }; + Binding.prototype = { + handleChange: function() { + var this$0 = this; + runloop.start( this.root ); + this.attribute.locked = true; + this.root.viewmodel.set( this.keypath, this.getValue() ); + runloop.scheduleTask( function() { + return this$0.attribute.locked = false; + } ); + runloop.end(); + }, + rebound: function() { + var bindings, oldKeypath, newKeypath; + oldKeypath = this.keypath; + newKeypath = this.attribute.interpolator.keypath; + // The attribute this binding is linked to has already done the work + if ( oldKeypath === newKeypath ) { + return; + } + removeFromArray( this.root._twowayBindings[ oldKeypath ], this ); + this.keypath = newKeypath; + bindings = this.root._twowayBindings[ newKeypath ] || ( this.root._twowayBindings[ newKeypath ] = [] ); + bindings.push( this ); + }, + unbind: function() {} + }; + Binding.extend = function( properties ) { + var Parent = this, + SpecialisedBinding; + SpecialisedBinding = function( element ) { + Binding.call( this, element ); + if ( this.init ) { + this.init(); + } + }; + SpecialisedBinding.prototype = create( Parent.prototype ); + extend( SpecialisedBinding.prototype, properties ); + SpecialisedBinding.extend = Binding.extend; + return SpecialisedBinding; + }; + return Binding; + }( runloop, warn, create, extend, removeFromArray ); + /* virtualdom/items/Element/Binding/shared/handleDomEvent.js */ + var handleDomEvent = function handleChange() { + this._ractive.binding.handleChange(); + }; - Section = function ( firstToken, parser, preserveWhitespace ) { - var next; + /* virtualdom/items/Element/Binding/ContentEditableBinding.js */ + var ContentEditableBinding = function( Binding, handleDomEvent ) { - this.ref = firstToken.ref; - this.indexRef = firstToken.indexRef; - - this.inverted = ( firstToken.mustacheType === INVERTED ); - - if ( firstToken.expression ) { - this.expr = new Expression( firstToken.expression ); + var ContentEditableBinding = Binding.extend( { + getInitialValue: function() { + return this.element.fragment ? this.element.fragment.toString() : ''; + }, + render: function() { + var node = this.element.node; + node.addEventListener( 'change', handleDomEvent, false ); + if ( !this.root.lazy ) { + node.addEventListener( 'input', handleDomEvent, false ); + if ( node.attachEvent ) { + node.addEventListener( 'keyup', handleDomEvent, false ); + } + } + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'input', handleDomEvent, false ); + node.removeEventListener( 'keyup', handleDomEvent, false ); + }, + getValue: function() { + return this.element.node.innerHTML; } + } ); + return ContentEditableBinding; + }( Binding, handleDomEvent ); - parser.pos += 1; + /* virtualdom/items/Element/Binding/shared/getSiblings.js */ + var getSiblings = function() { - this.items = []; - next = parser.next(); + var sets = {}; + return function getSiblings( id, group, keypath ) { + var hash = id + group + keypath; + return sets[ hash ] || ( sets[ hash ] = [] ); + }; + }(); - while ( next ) { - if ( next.mustacheType === CLOSING ) { - if ( ( next.ref === this.ref ) || ( next.expr && this.expr ) ) { - parser.pos += 1; - break; - } + /* virtualdom/items/Element/Binding/RadioBinding.js */ + var RadioBinding = function( runloop, removeFromArray, Binding, getSiblings, handleDomEvent ) { - else { - throw new Error( 'Could not parse template: Illegal closing section' ); - } + var RadioBinding = Binding.extend( { + name: 'checked', + init: function() { + this.siblings = getSiblings( this.root._guid, 'radio', this.element.getAttribute( 'name' ) ); + this.siblings.push( this ); + }, + render: function() { + var node = this.element.node; + node.addEventListener( 'change', handleDomEvent, false ); + if ( node.attachEvent ) { + node.addEventListener( 'click', handleDomEvent, false ); } - - this.items[ this.items.length ] = getItem( parser, preserveWhitespace ); - next = parser.next(); + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'click', handleDomEvent, false ); + }, + handleChange: function() { + runloop.start( this.root ); + this.siblings.forEach( function( binding ) { + binding.root.viewmodel.set( binding.keypath, binding.getValue() ); + } ); + runloop.end(); + }, + getValue: function() { + return this.element.node.checked; + }, + unbind: function() { + removeFromArray( this.siblings, this ); } - }; + } ); + return RadioBinding; + }( runloop, removeFromArray, Binding, getSiblings, handleDomEvent ); - Section.prototype = { - toJson: function ( noStringify ) { - var json, str, i, len, itemStr; + /* virtualdom/items/Element/Binding/RadioNameBinding.js */ + var RadioNameBinding = function( removeFromArray, Binding, handleDomEvent, getSiblings ) { - if ( this.json ) { - return this.json; + var RadioNameBinding = Binding.extend( { + name: 'name', + init: function() { + this.siblings = getSiblings( this.root._guid, 'radioname', this.keypath ); + this.siblings.push( this ); + this.radioName = true; + // so that ractive.updateModel() knows what to do with this + this.attribute.twoway = true; + }, + getInitialValue: function() { + if ( this.element.getAttribute( 'checked' ) ) { + return this.element.getAttribute( 'value' ); } - - json = { t: SECTION }; - - if ( this.ref ) { - json.r = this.ref; + }, + render: function() { + var node = this.element.node; + node.name = '{{' + this.keypath + '}}'; + node.checked = this.root.viewmodel.get( this.keypath ) == this.element.getAttribute( 'value' ); + node.addEventListener( 'change', handleDomEvent, false ); + if ( node.attachEvent ) { + node.addEventListener( 'click', handleDomEvent, false ); } - - if ( this.indexRef ) { - json.i = this.indexRef; + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'click', handleDomEvent, false ); + }, + getValue: function() { + var node = this.element.node; + return node._ractive ? node._ractive.value : node.value; + }, + handleChange: function() { + // If this <input> is the one that's checked, then the value of its + // `name` keypath gets set to its value + if ( this.element.node.checked ) { + Binding.prototype.handleChange.call( this ); } - - if ( this.inverted ) { - json.n = true; + }, + rebound: function( indexRef, newIndex, oldKeypath, newKeypath ) { + var node; + Binding.prototype.rebound.call( this, indexRef, newIndex, oldKeypath, newKeypath ); + if ( node = this.element.node ) { + node.name = '{{' + this.keypath + '}}'; } + }, + unbind: function() { + removeFromArray( this.siblings, this ); + } + } ); + return RadioNameBinding; + }( removeFromArray, Binding, handleDomEvent, getSiblings ); - if ( this.expr ) { - json.x = this.expr.toJson(); - } + /* virtualdom/items/Element/Binding/CheckboxNameBinding.js */ + var CheckboxNameBinding = function( isArray, arrayContains, removeFromArray, Binding, getSiblings, handleDomEvent ) { - if ( this.items.length ) { - json.f = jsonify( this.items, noStringify ); + var CheckboxNameBinding = Binding.extend( { + name: 'name', + getInitialValue: function() { + // This only gets called once per group (of inputs that + // share a name), because it only gets called if there + // isn't an initial value. By the same token, we can make + // a note of that fact that there was no initial value, + // and populate it using any `checked` attributes that + // exist (which users should avoid, but which we should + // support anyway to avoid breaking expectations) + this.noInitialValue = true; + return []; + }, + init: function() { + var existingValue, bindingValue; + this.checkboxName = true; + // so that ractive.updateModel() knows what to do with this + this.attribute.twoway = true; + // we set this property so that the attribute gets the correct update method + // Each input has a reference to an array containing it and its + // siblings, as two-way binding depends on being able to ascertain + // the status of all inputs within the group + this.siblings = getSiblings( this.root._guid, 'checkboxes', this.keypath ); + this.siblings.push( this ); + if ( this.noInitialValue ) { + this.siblings.noInitialValue = true; } - - this.json = json; - return json; + // If no initial value was set, and this input is checked, we + // update the model + if ( this.siblings.noInitialValue && this.element.getAttribute( 'checked' ) ) { + existingValue = this.root.viewmodel.get( this.keypath ); + bindingValue = this.element.getAttribute( 'value' ); + existingValue.push( bindingValue ); + } }, - - toString: function () { - // sections cannot be stringified - return false; + unbind: function() { + removeFromArray( this.siblings, this ); + }, + render: function() { + var node = this.element.node, + existingValue, bindingValue; + existingValue = this.root.viewmodel.get( this.keypath ); + bindingValue = this.element.getAttribute( 'value' ); + if ( isArray( existingValue ) ) { + this.isChecked = arrayContains( existingValue, bindingValue ); + } else { + this.isChecked = existingValue == bindingValue; + } + node.name = '{{' + this.keypath + '}}'; + node.checked = this.isChecked; + node.addEventListener( 'change', handleDomEvent, false ); + // in case of IE emergency, bind to click event as well + if ( node.attachEvent ) { + node.addEventListener( 'click', handleDomEvent, false ); + } + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'click', handleDomEvent, false ); + }, + changed: function() { + var wasChecked = !!this.isChecked; + this.isChecked = this.element.node.checked; + return this.isChecked === wasChecked; + }, + handleChange: function() { + this.isChecked = this.element.node.checked; + Binding.prototype.handleChange.call( this ); + }, + getValue: function() { + return this.siblings.filter( isChecked ).map( getValue ); } - }; - }()); + } ); + function isChecked( binding ) { + return binding.isChecked; + } - // element - (function () { - var voidElementNames, allElementNames, mapToLowerCase, svgCamelCaseElements, svgCamelCaseElementsMap, svgCamelCaseAttributes, svgCamelCaseAttributesMap, closedByParentClose, siblingsByTagName, sanitize, onlyAttrs, onlyProxies, filterAttrs, proxyPattern; + function getValue( binding ) { + return binding.element.getAttribute( 'value' ); + } + return CheckboxNameBinding; + }( isArray, arrayContains, removeFromArray, Binding, getSiblings, handleDomEvent ); - Element = function ( firstToken, parser, preserveWhitespace ) { - var closed, next, i, len, attrs, filtered, proxies, attr, getFrag, processProxy, item; + /* virtualdom/items/Element/Binding/CheckboxBinding.js */ + var CheckboxBinding = function( Binding, handleDomEvent ) { - this.lcTag = firstToken.name.toLowerCase(); - - // enforce lower case tag names by default. HTML doesn't care. SVG does, so if we see an SVG tag - // that should be camelcased, camelcase it - this.tag = ( svgCamelCaseElementsMap[ this.lcTag ] ? svgCamelCaseElementsMap[ this.lcTag ] : this.lcTag ); - - parser.pos += 1; - - // if this is a <pre> element, preserve whitespace within - preserveWhitespace = ( preserveWhitespace || this.lcTag === 'pre' ); - - if ( firstToken.attrs ) { - filtered = filterAttrs( firstToken.attrs ); - - attrs = filtered.attrs; - proxies = filtered.proxies; - - // remove event attributes (e.g. onclick='doSomething()') if we're sanitizing - if ( parser.options.sanitize && parser.options.sanitize.eventAttributes ) { - attrs = attrs.filter( sanitize ); + var CheckboxBinding = Binding.extend( { + name: 'checked', + render: function() { + var node = this.element.node; + node.addEventListener( 'change', handleDomEvent, false ); + if ( node.attachEvent ) { + node.addEventListener( 'click', handleDomEvent, false ); } + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'click', handleDomEvent, false ); + }, + getValue: function() { + return this.element.node.checked; + } + } ); + return CheckboxBinding; + }( Binding, handleDomEvent ); - getFrag = function ( attr ) { - var lcName = attr.name.toLowerCase(); + /* virtualdom/items/Element/Binding/SelectBinding.js */ + var SelectBinding = function( runloop, Binding, handleDomEvent ) { - return { - name: ( svgCamelCaseAttributesMap[ lcName ] ? svgCamelCaseAttributesMap[ lcName ] : lcName ), - value: getFragmentStubFromTokens( attr.value ) - }; - }; - - processProxy = function ( proxy ) { - var processed, domEventName, match, tokens, proxyName, proxyArgs, colonIndex, throwError; - - throwError = function () { - throw new Error( 'Illegal proxy event' ); - }; - - if ( !proxy.name || !proxy.value ) { - throwError(); + var SelectBinding = Binding.extend( { + getInitialValue: function() { + var options = this.element.options, + len, i, value, optionWasSelected; + if ( this.element.getAttribute( 'value' ) !== undefined ) { + return; + } + i = len = options.length; + if ( !len ) { + return; + } + // take the final selected option... + while ( i-- ) { + if ( options[ i ].getAttribute( 'selected' ) ) { + value = options[ i ].getAttribute( 'value' ); + optionWasSelected = true; + break; } - - processed = { domEventName: proxy.name }; - - tokens = proxy.value; - - // proxy event names must start with a string (no mustaches) - if ( tokens[0].type !== TEXT ) { - throwError(); - } - - colonIndex = tokens[0].value.indexOf( ':' ); - - // if no arguments are specified... - if ( colonIndex === -1 ) { - - // ...the proxy name must be string-only (no mustaches) - if ( tokens.length > 1 ) { - throwError(); + } + // or the first non-disabled option, if none are selected + if ( !optionWasSelected ) { + while ( ++i < len ) { + if ( !options[ i ].getAttribute( 'disabled' ) ) { + value = options[ i ].getAttribute( 'value' ); + break; } - - processed.name = tokens[0].value; } + } + // This is an optimisation (aka hack) that allows us to forgo some + // other more expensive work + if ( value !== undefined ) { + this.element.attributes.value.value = value; + } + return value; + }, + render: function() { + this.element.node.addEventListener( 'change', handleDomEvent, false ); + }, + unrender: function() { + this.element.node.removeEventListener( 'change', handleDomEvent, false ); + }, + // TODO this method is an anomaly... is it necessary? + setValue: function( value ) { + runloop.addViewmodel( this.root.viewmodel ); + this.root.viewmodel.set( this.keypath, value ); + }, + getValue: function() { + var options, i, len, option, optionValue; + options = this.element.node.options; + len = options.length; + for ( i = 0; i < len; i += 1 ) { + option = options[ i ]; + if ( options[ i ].selected ) { + optionValue = option._ractive ? option._ractive.value : option.value; + return optionValue; + } + } + }, + forceUpdate: function() { + var this$0 = this; + var value = this.getValue(); + if ( value !== undefined ) { + this.attribute.locked = true; + runloop.addViewmodel( this.root.viewmodel ); + runloop.scheduleTask( function() { + return this$0.attribute.locked = false; + } ); + this.root.viewmodel.set( this.keypath, value ); + } + } + } ); + return SelectBinding; + }( runloop, Binding, handleDomEvent ); - else { - processed.name = tokens[0].value.substr( 0, colonIndex ); - tokens[0].value = tokens[0].value.substring( colonIndex + 1 ); + /* utils/arrayContentsMatch.js */ + var arrayContentsMatch = function( isArray ) { - if ( !tokens[0].value ) { - tokens.shift(); - } + return function( a, b ) { + var i; + if ( !isArray( a ) || !isArray( b ) ) { + return false; + } + if ( a.length !== b.length ) { + return false; + } + i = a.length; + while ( i-- ) { + if ( a[ i ] !== b[ i ] ) { + return false; + } + } + return true; + }; + }( isArray ); - // can we parse it yet? - if ( tokens.length === 1 && tokens[0].type === TEXT ) { - try { - processed.args = JSON.parse( tokens[0].value ); - } catch ( err ) { - processed.args = tokens[0].value; - } - } + /* virtualdom/items/Element/Binding/MultipleSelectBinding.js */ + var MultipleSelectBinding = function( runloop, arrayContentsMatch, SelectBinding, handleDomEvent ) { - processed.dynamicArgs = getFragmentStubFromTokens( tokens ); + var MultipleSelectBinding = SelectBinding.extend( { + getInitialValue: function() { + return this.element.options.filter( function( option ) { + return option.getAttribute( 'selected' ); + } ).map( function( option ) { + return option.getAttribute( 'value' ); + } ); + }, + render: function() { + var valueFromModel; + this.element.node.addEventListener( 'change', handleDomEvent, false ); + valueFromModel = this.root.viewmodel.get( this.keypath ); + if ( valueFromModel === undefined ) { + // get value from DOM, if possible + this.handleChange(); + } + }, + unrender: function() { + this.element.node.removeEventListener( 'change', handleDomEvent, false ); + }, + setValue: function() { + throw new Error( 'TODO not implemented yet' ); + }, + getValue: function() { + var selectedValues, options, i, len, option, optionValue; + selectedValues = []; + options = this.element.node.options; + len = options.length; + for ( i = 0; i < len; i += 1 ) { + option = options[ i ]; + if ( option.selected ) { + optionValue = option._ractive ? option._ractive.value : option.value; + selectedValues.push( optionValue ); } - - return processed; - }; - - if ( attrs.length ) { - this.attributes = attrs.map( getFrag ); } - - if ( proxies.length ) { - this.proxies = proxies.map( processProxy ); + return selectedValues; + }, + handleChange: function() { + var attribute, previousValue, value; + attribute = this.attribute; + previousValue = attribute.value; + value = this.getValue(); + if ( previousValue === undefined || !arrayContentsMatch( value, previousValue ) ) { + SelectBinding.prototype.handleChange.call( this ); } - - // TODO rename this helper function - if ( filtered.intro ) { - this.intro = processProxy( filtered.intro ); + return this; + }, + forceUpdate: function() { + var this$0 = this; + var value = this.getValue(); + if ( value !== undefined ) { + this.attribute.locked = true; + runloop.addViewmodel( this.root.viewmodel ); + runloop.scheduleTask( function() { + return this$0.attribute.locked = false; + } ); + this.root.viewmodel.set( this.keypath, value ); } - - if ( filtered.outro ) { - this.outro = processProxy( filtered.outro ); + }, + updateModel: function() { + if ( this.attribute.value === undefined || !this.attribute.value.length ) { + this.root.viewmodel.set( this.keypath, this.initialValue ); } } + } ); + return MultipleSelectBinding; + }( runloop, arrayContentsMatch, SelectBinding, handleDomEvent ); - if ( firstToken.selfClosing ) { - this.selfClosing = true; - } + /* virtualdom/items/Element/Binding/FileListBinding.js */ + var FileListBinding = function( Binding, handleDomEvent ) { - if ( voidElementNames.indexOf( this.lcTag ) !== -1 ) { - this.isVoid = true; + var FileListBinding = Binding.extend( { + render: function() { + this.element.node.addEventListener( 'change', handleDomEvent, false ); + }, + unrender: function() { + this.element.node.removeEventListener( 'change', handleDomEvent, false ); + }, + getValue: function() { + return this.element.node.files; } + } ); + return FileListBinding; + }( Binding, handleDomEvent ); - // if self-closing or a void element, close - if ( this.selfClosing || this.isVoid ) { - return; - } + /* virtualdom/items/Element/Binding/GenericBinding.js */ + var GenericBinding = function( Binding, handleDomEvent ) { - this.siblings = siblingsByTagName[ this.lcTag ]; - - this.items = []; - - next = parser.next(); - while ( next ) { - - // section closing mustache should also close this element, e.g. - // <ul>{{#items}}<li>{{content}}{{/items}}</ul> - if ( next.mustacheType === CLOSING ) { - break; - } - - if ( next.type === TAG ) { - - // closing tag - if ( next.closing ) { - // it's a closing tag, which means this element is closed... - if ( next.name.toLowerCase() === this.lcTag ) { - parser.pos += 1; - } - - break; + var __export; + var GenericBinding, getOptions; + getOptions = { + evaluateWrapped: true + }; + GenericBinding = Binding.extend( { + getInitialValue: function() { + return ''; + }, + getValue: function() { + return this.element.node.value; + }, + render: function() { + var node = this.element.node; + node.addEventListener( 'change', handleDomEvent, false ); + if ( !this.root.lazy ) { + node.addEventListener( 'input', handleDomEvent, false ); + if ( node.attachEvent ) { + node.addEventListener( 'keyup', handleDomEvent, false ); } - - // sibling element, which closes this element implicitly - else if ( this.siblings && ( this.siblings.indexOf( next.name.toLowerCase() ) !== -1 ) ) { - break; - } - } + node.addEventListener( 'blur', handleBlur, false ); + }, + unrender: function() { + var node = this.element.node; + node.removeEventListener( 'change', handleDomEvent, false ); + node.removeEventListener( 'input', handleDomEvent, false ); + node.removeEventListener( 'keyup', handleDomEvent, false ); + node.removeEventListener( 'blur', handleBlur, false ); + } + } ); + __export = GenericBinding; - this.items[ this.items.length ] = getItem( parser ); + function handleBlur() { + var value; + handleDomEvent.call( this ); + value = this._ractive.root.viewmodel.get( this._ractive.binding.keypath, getOptions ); + this.value = value == undefined ? '' : value; + } + return __export; + }( Binding, handleDomEvent ); - next = parser.next(); + /* virtualdom/items/Element/Binding/NumericBinding.js */ + var NumericBinding = function( GenericBinding ) { + + return GenericBinding.extend( { + getInitialValue: function() { + return undefined; + }, + getValue: function() { + var value = parseFloat( this.element.node.value ); + return isNaN( value ) ? undefined : value; } + } ); + }( GenericBinding ); + /* virtualdom/items/Element/prototype/init/createTwowayBinding.js */ + var virtualdom_items_Element$init_createTwowayBinding = function( log, ContentEditableBinding, RadioBinding, RadioNameBinding, CheckboxNameBinding, CheckboxBinding, SelectBinding, MultipleSelectBinding, FileListBinding, NumericBinding, GenericBinding ) { - // if we're not preserving whitespace, we can eliminate inner leading and trailing whitespace - if ( !preserveWhitespace ) { - item = this.items[0]; - if ( item && item.type === TEXT ) { - item.text = item.text.replace( leadingWhitespace, '' ); - if ( !item.text ) { - this.items.shift(); + var __export; + __export = function createTwowayBinding( element ) { + var attributes = element.attributes, + type, Binding, bindName, bindChecked; + // if this is a late binding, and there's already one, it + // needs to be torn down + if ( element.binding ) { + element.binding.teardown(); + element.binding = null; + } + // contenteditable + if ( // if the contenteditable attribute is true or is bindable and may thus become true + ( element.getAttribute( 'contenteditable' ) || !!attributes.contenteditable && isBindable( attributes.contenteditable ) ) && isBindable( attributes.value ) ) { + Binding = ContentEditableBinding; + } else if ( element.name === 'input' ) { + type = element.getAttribute( 'type' ); + if ( type === 'radio' || type === 'checkbox' ) { + bindName = isBindable( attributes.name ); + bindChecked = isBindable( attributes.checked ); + // we can either bind the name attribute, or the checked attribute - not both + if ( bindName && bindChecked ) { + log.error( { + message: 'badRadioInputBinding' + } ); } - } - - item = this.items[ this.items.length - 1 ]; - if ( item && item.type === TEXT ) { - item.text = item.text.replace( trailingWhitespace, '' ); - if ( !item.text ) { - this.items.pop(); + if ( bindName ) { + Binding = type === 'radio' ? RadioNameBinding : CheckboxNameBinding; + } else if ( bindChecked ) { + Binding = type === 'radio' ? RadioBinding : CheckboxBinding; } + } else if ( type === 'file' && isBindable( attributes.value ) ) { + Binding = FileListBinding; + } else if ( isBindable( attributes.value ) ) { + Binding = type === 'number' || type === 'range' ? NumericBinding : GenericBinding; } + } else if ( element.name === 'select' && isBindable( attributes.value ) ) { + Binding = element.getAttribute( 'multiple' ) ? MultipleSelectBinding : SelectBinding; + } else if ( element.name === 'textarea' && isBindable( attributes.value ) ) { + Binding = GenericBinding; } + if ( Binding ) { + return new Binding( element ); + } }; - Element.prototype = { - toJson: function ( noStringify ) { - var json, name, value, str, itemStr, proxy, match, i, len; + function isBindable( attribute ) { + return attribute && attribute.isBindable; + } + return __export; + }( log, ContentEditableBinding, RadioBinding, RadioNameBinding, CheckboxNameBinding, CheckboxBinding, SelectBinding, MultipleSelectBinding, FileListBinding, NumericBinding, GenericBinding ); - json = { - t: ELEMENT, - e: this.tag - }; + /* virtualdom/items/Element/EventHandler/prototype/bubble.js */ + var virtualdom_items_Element_EventHandler$bubble = function EventHandler$bubble() { + var hasAction = this.getAction(); + if ( hasAction && !this.hasListener ) { + this.listen(); + } else if ( !hasAction && this.hasListener ) { + this.unrender(); + } + }; - if ( this.attributes && this.attributes.length ) { - json.a = {}; + /* virtualdom/items/Element/EventHandler/prototype/fire.js */ + var virtualdom_items_Element_EventHandler$fire = function( fireEvent ) { - len = this.attributes.length; - for ( i=0; i<len; i+=1 ) { - name = this.attributes[i].name; + return function EventHandler$fire( event ) { + fireEvent( this.root, this.getAction(), { + event: event + } ); + }; + }( Ractive$shared_fireEvent ); - if ( json.a[ name ] ) { - throw new Error( 'You cannot have multiple elements with the same name' ); - } + /* virtualdom/items/Element/EventHandler/prototype/getAction.js */ + var virtualdom_items_Element_EventHandler$getAction = function EventHandler$getAction() { + return this.action.toString().trim(); + }; - // empty attributes (e.g. autoplay, checked) - if( this.attributes[i].value === undefined ) { - value = null; - } + /* virtualdom/items/Element/EventHandler/prototype/init.js */ + var virtualdom_items_Element_EventHandler$init = function( getFunctionFromString, createReferenceResolver, circular, fireEvent, log ) { - value = jsonify( this.attributes[i].value.items, noStringify ); - - json.a[ name ] = value; + var __export; + var Fragment, getValueOptions = { + args: true + }, + eventPattern = /^event(?:\.(.+))?/; + circular.push( function() { + Fragment = circular.Fragment; + } ); + __export = function EventHandler$init( element, name, template ) { + var handler = this, + action, refs, ractive; + handler.element = element; + handler.root = element.root; + handler.name = name; + if ( name.indexOf( '*' ) !== -1 ) { + log.error( { + debug: this.root.debug, + message: 'noElementProxyEventWildcards', + args: { + element: element.tagName, + event: name } - } - - if ( this.items && this.items.length ) { - json.f = jsonify( this.items, noStringify ); - } - - if ( this.proxies && this.proxies.length ) { - json.v = {}; - - len = this.proxies.length; - for ( i=0; i<len; i+=1 ) { - proxy = this.proxies[i]; - - // TODO rename domEventName, since transitions use the same mechanism - if ( proxy.args ) { - json.v[ proxy.domEventName ] = { - n: proxy.name, - a: proxy.args - }; - } else if ( proxy.dynamicArgs ) { - json.v[ proxy.domEventName ] = { - n: proxy.name, - d: jsonify( proxy.dynamicArgs.items, noStringify ) - }; - } else { - json.v[ proxy.domEventName ] = proxy.name; - } - } - } - - if ( this.intro ) { - if ( this.intro.args ) { - json.t1 = { - n: this.intro.name, - a: this.intro.args + } ); + this.invalid = true; + } + if ( template.m ) { + refs = template.a.r; + // This is a method call + handler.method = template.m; + handler.keypaths = []; + handler.fn = getFunctionFromString( template.a.s, refs.length ); + handler.parentFragment = element.parentFragment; + ractive = handler.root; + // Create resolvers for each reference + handler.refResolvers = refs.map( function( ref, i ) { + var match; + // special case - the `event` object + if ( match = eventPattern.exec( ref ) ) { + handler.keypaths[ i ] = { + eventObject: true, + refinements: match[ 1 ] ? match[ 1 ].split( '.' ) : [] }; - } else if ( this.intro.dynamicArgs ) { - json.t1 = { - n: this.intro.name, - d: jsonify( this.intro.dynamicArgs.items, noStringify ) - }; - } else { - json.t1 = this.intro.name; + return null; } + return createReferenceResolver( handler, ref, function( keypath ) { + handler.resolve( i, keypath ); + } ); + } ); + this.fire = fireMethodCall; + } else { + // Get action ('foo' in 'on-click='foo') + action = template.n || template; + if ( typeof action !== 'string' ) { + action = new Fragment( { + template: action, + root: this.root, + owner: this + } ); } - - if ( this.outro ) { - if ( this.outro.args ) { - json.t2 = { - n: this.outro.name, - a: this.outro.args - }; - } else if ( this.outro.dynamicArgs ) { - json.t2 = { - n: this.outro.name, - d: jsonify( this.outro.dynamicArgs.items, noStringify ) - }; - } else { - json.t2 = this.outro.name; - } + this.action = action; + // Get parameters + if ( template.d ) { + this.dynamicParams = new Fragment( { + template: template.d, + root: this.root, + owner: this.element + } ); + this.fire = fireEventWithDynamicParams; + } else if ( template.a ) { + this.params = template.a; + this.fire = fireEventWithParams; } + } + }; - this.json = json; - return json; - }, - - toString: function () { - var str, i, len, attrStr, lcName, attrValueStr, fragStr, isVoid; - - if ( this.str !== undefined ) { - return this.str; + function fireMethodCall( event ) { + var ractive, values, args; + ractive = this.root; + if ( typeof ractive[ this.method ] !== 'function' ) { + throw new Error( 'Attempted to call a non-existent method ("' + this.method + '")' ); + } + values = this.keypaths.map( function( keypath ) { + var value, len, i; + if ( keypath === undefined ) { + // not yet resolved + return undefined; } - - // if this isn't an HTML element, it can't be stringified (since the only reason to stringify an - // element is to use with innerHTML, and SVG doesn't support that method. - // Note: table elements are excluded from this, because IE (of course) fucks up when you use - // innerHTML with tables - if ( allElementNames.indexOf( this.tag.toLowerCase() ) === -1 ) { - return ( this.str = false ); + // TODO the refinements stuff would be better handled at parse time + if ( keypath.eventObject ) { + value = event; + if ( len = keypath.refinements.length ) { + for ( i = 0; i < len; i += 1 ) { + value = value[ keypath.refinements[ i ] ]; + } + } + } else { + value = ractive.viewmodel.get( keypath ); } + return value; + } ); + ractive.event = event; + args = this.fn.apply( null, values ); + ractive[ this.method ].apply( ractive, args ); + delete ractive.event; + } - // see if children can be stringified (i.e. don't contain mustaches) - fragStr = stringify( this.items ); - if ( fragStr === false ) { - return ( this.str = false ); - } + function fireEventWithParams( event ) { + fireEvent( this.root, this.getAction(), { + event: event, + args: this.params + } ); + } - // do we have proxies or transitions? if so we can't use innerHTML - if ( this.proxies || this.intro || this.outro ) { - return ( this.str = false ); - } + function fireEventWithDynamicParams( event ) { + var args = this.dynamicParams.getValue( getValueOptions ); + // need to strip [] from ends if a string! + if ( typeof args === 'string' ) { + args = args.substr( 1, args.length - 2 ); + } + fireEvent( this.root, this.getAction(), { + event: event, + args: args + } ); + } + return __export; + }( getFunctionFromString, createReferenceResolver, circular, Ractive$shared_fireEvent, log ); - // is this a void element? - isVoid = ( voidElementNames.indexOf( this.tag.toLowerCase() ) !== -1 ); + /* virtualdom/items/Element/EventHandler/shared/genericHandler.js */ + var genericHandler = function genericHandler( event ) { + var storage, handler; + storage = this._ractive; + handler = storage.events[ event.type ]; + handler.fire( { + node: this, + original: event, + index: storage.index, + keypath: storage.keypath, + context: storage.root.get( storage.keypath ) + } ); + }; - str = '<' + this.tag; - - if ( this.attributes ) { - for ( i=0, len=this.attributes.length; i<len; i+=1 ) { + /* virtualdom/items/Element/EventHandler/prototype/listen.js */ + var virtualdom_items_Element_EventHandler$listen = function( config, genericHandler, log ) { - lcName = this.attributes[i].name.toLowerCase(); - - // does this look like a namespaced attribute? if so we can't stringify it - if ( lcName.indexOf( ':' ) !== -1 ) { - return ( this.str = false ); - } - - // if this element has an id attribute, it can't be stringified (since references are stored - // in ractive.nodes). Similarly, intro and outro transitions - if ( lcName === 'id' || lcName === 'intro' || lcName === 'outro' ) { - return ( this.str = false ); - } - - attrStr = ' ' + this.attributes[i].name; - - // empty attributes - if ( this.attributes[i].value !== undefined ) { - attrValueStr = this.attributes[i].value.toString(); - - if ( attrValueStr === false ) { - return ( this.str = false ); + var __export; + var customHandlers = {}, + touchEvents = { + touchstart: true, + touchmove: true, + touchend: true, + touchcancel: true, + //not w3c, but supported in some browsers + touchleave: true + }; + __export = function EventHandler$listen() { + var definition, name = this.name; + if ( this.invalid ) { + return; + } + if ( definition = config.registries.events.find( this.root, name ) ) { + this.custom = definition( this.node, getCustomHandler( name ) ); + } else { + // Looks like we're dealing with a standard DOM event... but let's check + if ( !( 'on' + name in this.node ) && !( window && 'on' + name in window ) ) { + // okay to use touch events if this browser doesn't support them + if ( !touchEvents[ name ] ) { + log.error( { + debug: this.root.debug, + message: 'missingPlugin', + args: { + plugin: 'event', + name: name } - - if ( attrValueStr !== '' ) { - attrStr += '='; - - // does it need to be quoted? - if ( /[\s"'=<>`]/.test( attrValueStr ) ) { - attrStr += '"' + attrValueStr.replace( /"/g, '&quot;' ) + '"'; - } else { - attrStr += attrValueStr; - } - } - } - - str += attrStr; + } ); } + return; } + this.node.addEventListener( name, genericHandler, false ); + } + this.hasListener = true; + }; - // if this isn't a void tag, but is self-closing, add a solidus. Aaaaand, we're done - if ( this.selfClosing && !isVoid ) { - str += '/>'; - return ( this.str = str ); - } + function getCustomHandler( name ) { + if ( !customHandlers[ name ] ) { + customHandlers[ name ] = function( event ) { + var storage = event.node._ractive; + event.index = storage.index; + event.keypath = storage.keypath; + event.context = storage.root.get( storage.keypath ); + storage.events[ name ].fire( event ); + }; + } + return customHandlers[ name ]; + } + return __export; + }( config, genericHandler, log ); - str += '>'; + /* virtualdom/items/Element/EventHandler/prototype/rebind.js */ + var virtualdom_items_Element_EventHandler$rebind = function EventHandler$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + var fragment; + if ( this.method ) { + fragment = this.element.parentFragment; + this.refResolvers.forEach( rebind ); + return; + } + if ( typeof this.action !== 'string' ) { + rebind( this.action ); + } + if ( this.dynamicParams ) { + rebind( this.dynamicParams ); + } - // void element? we're done - if ( isVoid ) { - return ( this.str = str ); - } + function rebind( thing ) { + thing && thing.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + }; - // if this has children, add them - str += fragStr; + /* virtualdom/items/Element/EventHandler/prototype/render.js */ + var virtualdom_items_Element_EventHandler$render = function EventHandler$render() { + this.node = this.element.node; + // store this on the node itself, so it can be retrieved by a + // universal handler + this.node._ractive.events[ this.name ] = this; + if ( this.method || this.getAction() ) { + this.listen(); + } + }; - str += '</' + this.tag + '>'; - return ( this.str = str ); + /* virtualdom/items/Element/EventHandler/prototype/resolve.js */ + var virtualdom_items_Element_EventHandler$resolve = function EventHandler$resolve( index, keypath ) { + this.keypaths[ index ] = keypath; + }; + + /* virtualdom/items/Element/EventHandler/prototype/unbind.js */ + var virtualdom_items_Element_EventHandler$unbind = function() { + + var __export; + __export = function EventHandler$unbind() { + if ( this.method ) { + this.refResolvers.forEach( unbind ); + return; } + // Tear down dynamic name + if ( typeof this.action !== 'string' ) { + this.action.unbind(); + } + // Tear down dynamic parameters + if ( this.dynamicParams ) { + this.dynamicParams.unbind(); + } }; + function unbind( x ) { + x.unbind(); + } + return __export; + }(); - voidElementNames = 'area base br col command embed hr img input keygen link meta param source track wbr'.split( ' ' ); - allElementNames = 'a abbr acronym address applet area b base basefont bdo big blockquote body br button caption center cite code col colgroup dd del dfn dir div dl dt em fieldset font form frame frameset h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd label legend li link map menu meta noframes noscript object ol optgroup option p param pre q s samp script select small span strike strong style sub sup textarea title tt u ul var article aside audio bdi canvas command data datagrid datalist details embed eventsource figcaption figure footer header hgroup keygen mark meter nav output progress ruby rp rt section source summary time track video wbr'.split( ' ' ); - closedByParentClose = 'li dd rt rp optgroup option tbody tfoot tr td th'.split( ' ' ); + /* virtualdom/items/Element/EventHandler/prototype/unrender.js */ + var virtualdom_items_Element_EventHandler$unrender = function( genericHandler ) { - svgCamelCaseElements = 'altGlyph altGlyphDef altGlyphItem animateColor animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence foreignObject glyphRef linearGradient radialGradient textPath vkern'.split( ' ' ); - svgCamelCaseAttributes = 'attributeName attributeType baseFrequency baseProfile calcMode clipPathUnits contentScriptType contentStyleType diffuseConstant edgeMode externalResourcesRequired filterRes filterUnits glyphRef glyphRef gradientTransform gradientTransform gradientUnits gradientUnits kernelMatrix kernelUnitLength kernelUnitLength kernelUnitLength keyPoints keySplines keyTimes lengthAdjust limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves pathLength patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits refX refY repeatCount repeatDur requiredExtensions requiredFeatures specularConstant specularExponent specularExponent spreadMethod spreadMethod startOffset stdDeviation stitchTiles surfaceScale surfaceScale systemLanguage tableValues targetX targetY textLength textLength viewBox viewTarget xChannelSelector yChannelSelector zoomAndPan'.split( ' ' ); - - mapToLowerCase = function ( items ) { - var map = {}, i = items.length; - while ( i-- ) { - map[ items[i].toLowerCase() ] = items[i]; + return function EventHandler$unrender() { + if ( this.custom ) { + this.custom.teardown(); + } else { + this.node.removeEventListener( this.name, genericHandler, false ); } - return map; + this.hasListener = false; }; + }( genericHandler ); - svgCamelCaseElementsMap = mapToLowerCase( svgCamelCaseElements ); - svgCamelCaseAttributesMap = mapToLowerCase( svgCamelCaseAttributes ); + /* virtualdom/items/Element/EventHandler/_EventHandler.js */ + var EventHandler = function( bubble, fire, getAction, init, listen, rebind, render, resolve, unbind, unrender ) { - siblingsByTagName = { - li: [ 'li' ], - dt: [ 'dt', 'dd' ], - dd: [ 'dt', 'dd' ], - p: 'address article aside blockquote dir div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr menu nav ol p pre section table ul'.split( ' ' ), - rt: [ 'rt', 'rp' ], - rp: [ 'rp', 'rt' ], - optgroup: [ 'optgroup' ], - option: [ 'option', 'optgroup' ], - thead: [ 'tbody', 'tfoot' ], - tbody: [ 'tbody', 'tfoot' ], - tr: [ 'tr' ], - td: [ 'td', 'th' ], - th: [ 'td', 'th' ] + var EventHandler = function( element, name, template ) { + this.init( element, name, template ); }; - - sanitize = function ( attr ) { - return attr.name.substr( 0, 2 ) !== 'on'; + EventHandler.prototype = { + bubble: bubble, + fire: fire, + getAction: getAction, + init: init, + listen: listen, + rebind: rebind, + render: render, + resolve: resolve, + unbind: unbind, + unrender: unrender }; + return EventHandler; + }( virtualdom_items_Element_EventHandler$bubble, virtualdom_items_Element_EventHandler$fire, virtualdom_items_Element_EventHandler$getAction, virtualdom_items_Element_EventHandler$init, virtualdom_items_Element_EventHandler$listen, virtualdom_items_Element_EventHandler$rebind, virtualdom_items_Element_EventHandler$render, virtualdom_items_Element_EventHandler$resolve, virtualdom_items_Element_EventHandler$unbind, virtualdom_items_Element_EventHandler$unrender ); - onlyAttrs = function ( attr ) { - return attr.name.substr( 0, 6 ) !== 'proxy-'; - }; + /* virtualdom/items/Element/prototype/init/createEventHandlers.js */ + var virtualdom_items_Element$init_createEventHandlers = function( EventHandler ) { - onlyProxies = function ( attr ) { - if ( attr.name.substr( 0, 6 ) === 'proxy-' ) { - attr.name = attr.name.substring( 6 ); - return true; + return function( element, template ) { + var i, name, names, handler, result = []; + for ( name in template ) { + if ( template.hasOwnProperty( name ) ) { + names = name.split( '-' ); + i = names.length; + while ( i-- ) { + handler = new EventHandler( element, names[ i ], template[ name ] ); + result.push( handler ); + } + } } - return false; + return result; }; + }( EventHandler ); - filterAttrs = function ( items ) { - var attrs, proxies, filtered, i, len, item; + /* virtualdom/items/Element/Decorator/_Decorator.js */ + var Decorator = function( log, circular, config ) { - filtered = {}; - attrs = []; - proxies = []; - - len = items.length; - for ( i=0; i<len; i+=1 ) { - item = items[i]; - - // Transition? - if ( item.name === 'intro' ) { - if ( filtered.intro ) { - throw new Error( 'An element can only have one intro transition' ); + var Fragment, getValueOptions, Decorator; + circular.push( function() { + Fragment = circular.Fragment; + } ); + getValueOptions = { + args: true + }; + Decorator = function( element, template ) { + var decorator = this, + ractive, name, fragment; + decorator.element = element; + decorator.root = ractive = element.root; + name = template.n || template; + if ( typeof name !== 'string' ) { + fragment = new Fragment( { + template: name, + root: ractive, + owner: element + } ); + name = fragment.toString(); + fragment.unbind(); + } + if ( template.a ) { + decorator.params = template.a; + } else if ( template.d ) { + decorator.fragment = new Fragment( { + template: template.d, + root: ractive, + owner: element + } ); + decorator.params = decorator.fragment.getValue( getValueOptions ); + decorator.fragment.bubble = function() { + this.dirtyArgs = this.dirtyValue = true; + decorator.params = this.getValue( getValueOptions ); + if ( decorator.ready ) { + decorator.update(); } - - filtered.intro = item; - } else if ( item.name === 'outro' ) { - if ( filtered.outro ) { - throw new Error( 'An element can only have one outro transition' ); + }; + } + decorator.fn = config.registries.decorators.find( ractive, name ); + if ( !decorator.fn ) { + log.error( { + debug: ractive.debug, + message: 'missingPlugin', + args: { + plugin: 'decorator', + name: name } - - filtered.outro = item; + } ); + } + }; + Decorator.prototype = { + init: function() { + var decorator = this, + node, result, args; + node = decorator.element.node; + if ( decorator.params ) { + args = [ node ].concat( decorator.params ); + result = decorator.fn.apply( decorator.root, args ); + } else { + result = decorator.fn.call( decorator.root, node ); } - - // Proxy? - else if ( item.name.substr( 0, 6 ) === 'proxy-' ) { - item.name = item.name.substring( 6 ); - proxies[ proxies.length ] = item; + if ( !result || !result.teardown ) { + throw new Error( 'Decorator definition must return an object with a teardown method' ); } - - // Attribute? - else { - attrs[ attrs.length ] = item; + // TODO does this make sense? + decorator.actual = result; + decorator.ready = true; + }, + update: function() { + if ( this.actual.update ) { + this.actual.update.apply( this.root, this.params ); + } else { + this.actual.teardown( true ); + this.init(); } + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + if ( this.fragment ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + }, + teardown: function( updating ) { + this.actual.teardown(); + if ( !updating && this.fragment ) { + this.fragment.unbind(); + } } + }; + return Decorator; + }( log, circular, config ); - filtered.attrs = attrs; - filtered.proxies = proxies; + /* virtualdom/items/Element/special/select/sync.js */ + var sync = function( toArray ) { - return filtered; + var __export; + __export = function syncSelect( selectElement ) { + var selectNode, selectValue, isMultiple, options, optionWasSelected; + selectNode = selectElement.node; + if ( !selectNode ) { + return; + } + options = toArray( selectNode.options ); + selectValue = selectElement.getAttribute( 'value' ); + isMultiple = selectElement.getAttribute( 'multiple' ); + // If the <select> has a specified value, that should override + // these options + if ( selectValue !== undefined ) { + options.forEach( function( o ) { + var optionValue, shouldSelect; + optionValue = o._ractive ? o._ractive.value : o.value; + shouldSelect = isMultiple ? valueContains( selectValue, optionValue ) : selectValue == optionValue; + if ( shouldSelect ) { + optionWasSelected = true; + } + o.selected = shouldSelect; + } ); + if ( !optionWasSelected ) { + if ( options[ 0 ] ) { + options[ 0 ].selected = true; + } + if ( selectElement.binding ) { + selectElement.binding.forceUpdate(); + } + } + } else if ( selectElement.binding ) { + selectElement.binding.forceUpdate(); + } }; - proxyPattern = /^([a-zA-Z_$][a-zA-Z_$0-9]*)(?::(.+))?$/; - }()); + function valueContains( selectValue, optionValue ) { + var i = selectValue.length; + while ( i-- ) { + if ( selectValue[ i ] == optionValue ) { + return true; + } + } + } + return __export; + }( toArray ); + /* virtualdom/items/Element/special/select/bubble.js */ + var bubble = function( runloop, syncSelect ) { - // expression - (function () { + return function bubbleSelect() { + var this$0 = this; + if ( !this.dirty ) { + this.dirty = true; + runloop.scheduleTask( function() { + syncSelect( this$0 ); + this$0.dirty = false; + } ); + } + this.parentFragment.bubble(); + }; + }( runloop, sync ); - var getRefs, stringify; + /* virtualdom/items/Element/special/option/findParentSelect.js */ + var findParentSelect = function findParentSelect( element ) { + do { + if ( element.name === 'select' ) { + return element; + } + } while ( element = element.parent ); + }; - Expression = function ( token ) { - this.refs = []; + /* virtualdom/items/Element/special/option/init.js */ + var init = function( findParentSelect ) { - getRefs( token, this.refs ); - this.str = stringify( token, this.refs ); - }; - - Expression.prototype = { - toJson: function () { - return { - r: this.refs, - s: this.str - }; + return function initOption( option, template ) { + option.select = findParentSelect( option.parent ); + // we might be inside a <datalist> element + if ( !option.select ) { + return; } + option.select.options.push( option ); + // If the value attribute is missing, use the element's content + if ( !template.a ) { + template.a = {}; + } + // ...as long as it isn't disabled + if ( template.a.value === undefined && !template.a.hasOwnProperty( 'disabled' ) ) { + template.a.value = template.f; + } + // If there is a `selected` attribute, but the <select> + // already has a value, delete it + if ( 'selected' in template.a && option.select.getAttribute( 'value' ) !== undefined ) { + delete template.a.selected; + } }; + }( findParentSelect ); + /* virtualdom/items/Element/prototype/init.js */ + var virtualdom_items_Element$init = function( types, enforceCase, createAttributes, createConditionalAttributes, createTwowayBinding, createEventHandlers, Decorator, bubbleSelect, initOption, circular ) { - // TODO maybe refactor this? - getRefs = function ( token, refs ) { - var i; - - if ( token.t === REFERENCE ) { - if ( refs.indexOf( token.n ) === -1 ) { - refs.unshift( token.n ); - } + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + return function Element$init( options ) { + var parentFragment, template, ractive, binding, bindings; + this.type = types.ELEMENT; + // stuff we'll need later + parentFragment = this.parentFragment = options.parentFragment; + template = this.template = options.template; + this.parent = options.pElement || parentFragment.pElement; + this.root = ractive = parentFragment.root; + this.index = options.index; + this.name = enforceCase( template.e ); + // Special case - <option> elements + if ( this.name === 'option' ) { + initOption( this, template ); } - - if ( token.o ) { - if ( isObject( token.o ) ) { - getRefs( token.o, refs ); - } else { - i = token.o.length; - while ( i-- ) { - getRefs( token.o[i], refs ); - } - } + // Special case - <select> elements + if ( this.name === 'select' ) { + this.options = []; + this.bubble = bubbleSelect; } - - if ( token.x ) { - getRefs( token.x, refs ); + // create attributes + this.attributes = createAttributes( this, template.a ); + this.conditionalAttributes = createConditionalAttributes( this, template.m ); + // append children, if there are any + if ( template.f ) { + this.fragment = new Fragment( { + template: template.f, + root: ractive, + owner: this, + pElement: this + } ); } - - if ( token.r ) { - getRefs( token.r, refs ); + // create twoway binding + if ( ractive.twoway && ( binding = createTwowayBinding( this, template.a ) ) ) { + this.binding = binding; + // register this with the root, so that we can do ractive.updateModel() + bindings = this.root._twowayBindings[ binding.keypath ] || ( this.root._twowayBindings[ binding.keypath ] = [] ); + bindings.push( binding ); } + // create event proxies + if ( template.v ) { + this.eventHandlers = createEventHandlers( this, template.v ); + } + // create decorator + if ( template.o ) { + this.decorator = new Decorator( this, template.o ); + } + // create transitions + this.intro = template.t0 || template.t1; + this.outro = template.t0 || template.t2; }; + }( types, enforceCase, virtualdom_items_Element$init_createAttributes, virtualdom_items_Element$init_createConditionalAttributes, virtualdom_items_Element$init_createTwowayBinding, virtualdom_items_Element$init_createEventHandlers, Decorator, bubble, init, circular ); + /* virtualdom/items/shared/utils/startsWith.js */ + var startsWith = function( startsWithKeypath ) { - stringify = function ( token, refs ) { - var map = function ( item ) { - return stringify( item, refs ); - }; + return function startsWith( target, keypath ) { + return target === keypath || startsWithKeypath( target, keypath ); + }; + }( startsWithKeypath ); - switch ( token.t ) { - case BOOLEAN_LITERAL: - case GLOBAL: - case NUMBER_LITERAL: - return token.v; + /* virtualdom/items/shared/utils/assignNewKeypath.js */ + var assignNewKeypath = function( startsWith, getNewKeypath ) { - case STRING_LITERAL: - return "'" + token.v.replace( /'/g, "\\'" ) + "'"; + return function assignNewKeypath( target, property, oldKeypath, newKeypath ) { + var existingKeypath = target[ property ]; + if ( !existingKeypath || startsWith( existingKeypath, newKeypath ) || !startsWith( existingKeypath, oldKeypath ) ) { + return; + } + target[ property ] = getNewKeypath( existingKeypath, oldKeypath, newKeypath ); + }; + }( startsWith, getNewKeypath ); - case ARRAY_LITERAL: - return '[' + token.m.map( map ).join( ',' ) + ']'; + /* virtualdom/items/Element/prototype/rebind.js */ + var virtualdom_items_Element$rebind = function( assignNewKeypath ) { - case PREFIX_OPERATOR: - return ( token.s === 'typeof' ? 'typeof ' : token.s ) + stringify( token.o, refs ); + return function Element$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + var i, storage, liveQueries, ractive; + if ( this.attributes ) { + this.attributes.forEach( rebind ); + } + if ( this.conditionalAttributes ) { + this.conditionalAttributes.forEach( rebind ); + } + if ( this.eventHandlers ) { + this.eventHandlers.forEach( rebind ); + } + if ( this.decorator ) { + rebind( this.decorator ); + } + // rebind children + if ( this.fragment ) { + rebind( this.fragment ); + } + // Update live queries, if necessary + if ( liveQueries = this.liveQueries ) { + ractive = this.root; + i = liveQueries.length; + while ( i-- ) { + liveQueries[ i ]._makeDirty(); + } + } + if ( this.node && ( storage = this.node._ractive ) ) { + // adjust keypath if needed + assignNewKeypath( storage, 'keypath', oldKeypath, newKeypath ); + if ( indexRef != undefined ) { + storage.index[ indexRef ] = newIndex; + } + } - case INFIX_OPERATOR: - return stringify( token.o[0], refs ) + token.s + stringify( token.o[1], refs ); + function rebind( thing ) { + thing.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + }; + }( assignNewKeypath ); - case INVOCATION: - return stringify( token.x, refs ) + '(' + ( token.o ? token.o.map( map ).join( ',' ) : '' ) + ')'; + /* virtualdom/items/Element/special/img/render.js */ + var render = function renderImage( img ) { + var loadHandler; + // if this is an <img>, and we're in a crap browser, we may need to prevent it + // from overriding width and height when it loads the src + if ( img.attributes.width || img.attributes.height ) { + img.node.addEventListener( 'load', loadHandler = function() { + var width = img.getAttribute( 'width' ), + height = img.getAttribute( 'height' ); + if ( width !== undefined ) { + img.node.setAttribute( 'width', width ); + } + if ( height !== undefined ) { + img.node.setAttribute( 'height', height ); + } + img.node.removeEventListener( 'load', loadHandler, false ); + }, false ); + } + }; - case BRACKETED: - return '(' + stringify( token.x, refs ) + ')'; + /* virtualdom/items/Element/Transition/prototype/init.js */ + var virtualdom_items_Element_Transition$init = function( log, config, circular ) { - case MEMBER: - return stringify( token.x, refs ) + stringify( token.r, refs ); - - case REFINEMENT: - return ( token.n ? '.' + token.n : '[' + stringify( token.x, refs ) + ']' ); - - case CONDITIONAL: - return stringify( token.o[0], refs ) + '?' + stringify( token.o[1], refs ) + ':' + stringify( token.o[2], refs ); - - case REFERENCE: - return '❖' + refs.indexOf( token.n ); - - default: - throw new Error( 'Could not stringify expression token. This error is unexpected' ); + var Fragment, getValueOptions = {}; + // TODO what are the options? + circular.push( function() { + Fragment = circular.Fragment; + } ); + return function Transition$init( element, template, isIntro ) { + var t = this, + ractive, name, fragment; + t.element = element; + t.root = ractive = element.root; + t.isIntro = isIntro; + name = template.n || template; + if ( typeof name !== 'string' ) { + fragment = new Fragment( { + template: name, + root: ractive, + owner: element + } ); + name = fragment.toString(); + fragment.unbind(); } + t.name = name; + if ( template.a ) { + t.params = template.a; + } else if ( template.d ) { + // TODO is there a way to interpret dynamic arguments without all the + // 'dependency thrashing'? + fragment = new Fragment( { + template: template.d, + root: ractive, + owner: element + } ); + t.params = fragment.getValue( getValueOptions ); + fragment.unbind(); + } + t._fn = config.registries.transitions.find( ractive, name ); + if ( !t._fn ) { + log.error( { + debug: ractive.debug, + message: 'missingPlugin', + args: { + plugin: 'transition', + name: name + } + } ); + return; + } }; - }()); + }( log, config, circular ); -}()); -(function () { - - var getStringMatch, - getRegexMatcher, - allowWhitespace, - - getMustache, - getTriple, - getTag, - getText, - getExpression, - - getDelimiter, - getDelimiterChange, - getName, - getMustacheRef, - getRefinement, - getDotRefinement, - getArrayRefinement, - getArrayMember, - - getSingleQuotedString, - getUnescapedSingleQuotedChars, - getDoubleQuotedString, - getUnescapedDoubleQuotedChars, - getEscapedChars, - getEscapedChar, - - fail; - - - getToken = function ( tokenizer ) { - var token = getMustache( tokenizer ) || - getTriple( tokenizer ) || - getTag( tokenizer ) || - getText( tokenizer ); - - return token; + /* utils/camelCase.js */ + var camelCase = function( hyphenatedStr ) { + return hyphenatedStr.replace( /-([a-zA-Z])/g, function( match, $1 ) { + return $1.toUpperCase(); + } ); }; + /* virtualdom/items/Element/Transition/helpers/prefix.js */ + var prefix = function( isClient, vendors, createElement, camelCase ) { - - // helpers - fail = function ( tokenizer, expected ) { - var remaining = tokenizer.remaining().substr( 0, 40 ); - if ( remaining.length === 40 ) { - remaining += '...'; + var prefix, prefixCache, testStyle; + if ( !isClient ) { + prefix = null; + } else { + prefixCache = {}; + testStyle = createElement( 'div' ).style; + prefix = function( prop ) { + var i, vendor, capped; + prop = camelCase( prop ); + if ( !prefixCache[ prop ] ) { + if ( testStyle[ prop ] !== undefined ) { + prefixCache[ prop ] = prop; + } else { + // test vendors... + capped = prop.charAt( 0 ).toUpperCase() + prop.substring( 1 ); + i = vendors.length; + while ( i-- ) { + vendor = vendors[ i ]; + if ( testStyle[ vendor + capped ] !== undefined ) { + prefixCache[ prop ] = vendor + capped; + break; + } + } + } + } + return prefixCache[ prop ]; + }; } - throw new Error( 'Tokenizer failed: unexpected string "' + remaining + '" (expected ' + expected + ')' ); - }; + return prefix; + }( isClient, vendors, createElement, camelCase ); - getStringMatch = function ( tokenizer, string ) { - var substr; + /* virtualdom/items/Element/Transition/prototype/getStyle.js */ + var virtualdom_items_Element_Transition$getStyle = function( legacy, isClient, isArray, prefix ) { - substr = tokenizer.str.substr( tokenizer.pos, string.length ); - - if ( substr === string ) { - tokenizer.pos += string.length; - return string; + var getStyle, getComputedStyle; + if ( !isClient ) { + getStyle = null; + } else { + getComputedStyle = window.getComputedStyle || legacy.getComputedStyle; + getStyle = function( props ) { + var computedStyle, styles, i, prop, value; + computedStyle = getComputedStyle( this.node ); + if ( typeof props === 'string' ) { + value = computedStyle[ prefix( props ) ]; + if ( value === '0px' ) { + value = 0; + } + return value; + } + if ( !isArray( props ) ) { + throw new Error( 'Transition$getStyle must be passed a string, or an array of strings representing CSS properties' ); + } + styles = {}; + i = props.length; + while ( i-- ) { + prop = props[ i ]; + value = computedStyle[ prefix( prop ) ]; + if ( value === '0px' ) { + value = 0; + } + styles[ prop ] = value; + } + return styles; + }; } + return getStyle; + }( legacy, isClient, isArray, prefix ); - return null; - }; + /* virtualdom/items/Element/Transition/prototype/setStyle.js */ + var virtualdom_items_Element_Transition$setStyle = function( prefix ) { - getRegexMatcher = function ( regex ) { - return function ( tokenizer ) { - var match = regex.exec( tokenizer.str.substring( tokenizer.pos ) ); - - if ( !match ) { - return null; + return function( style, value ) { + var prop; + if ( typeof style === 'string' ) { + this.node.style[ prefix( style ) ] = value; + } else { + for ( prop in style ) { + if ( style.hasOwnProperty( prop ) ) { + this.node.style[ prefix( prop ) ] = style[ prop ]; + } + } } - - tokenizer.pos += match[0].length; - return match[1] || match[0]; + return this; }; - }; + }( prefix ); - allowWhitespace = function ( tokenizer ) { - var match = leadingWhitespace.exec( tokenizer.str.substring( tokenizer.pos ) ); + /* shared/Ticker.js */ + var Ticker = function( warn, getTime, animations ) { - if ( !match ) { - return null; + var __export; + var Ticker = function( options ) { + var easing; + this.duration = options.duration; + this.step = options.step; + this.complete = options.complete; + // easing + if ( typeof options.easing === 'string' ) { + easing = options.root.easing[ options.easing ]; + if ( !easing ) { + warn( 'Missing easing function ("' + options.easing + '"). You may need to download a plugin from [TODO]' ); + easing = linear; + } + } else if ( typeof options.easing === 'function' ) { + easing = options.easing; + } else { + easing = linear; + } + this.easing = easing; + this.start = getTime(); + this.end = this.start + this.duration; + this.running = true; + animations.add( this ); + }; + Ticker.prototype = { + tick: function( now ) { + var elapsed, eased; + if ( !this.running ) { + return false; + } + if ( now > this.end ) { + if ( this.step ) { + this.step( 1 ); + } + if ( this.complete ) { + this.complete( 1 ); + } + return false; + } + elapsed = now - this.start; + eased = this.easing( elapsed / this.duration ); + if ( this.step ) { + this.step( eased ); + } + return true; + }, + stop: function() { + if ( this.abort ) { + this.abort(); + } + this.running = false; + } + }; + __export = Ticker; + + function linear( t ) { + return t; } + return __export; + }( warn, getTime, animations ); - tokenizer.pos += match[0].length; - return match[0]; - }; + /* virtualdom/items/Element/Transition/helpers/unprefix.js */ + var unprefix = function( vendors ) { + var unprefixPattern = new RegExp( '^-(?:' + vendors.join( '|' ) + ')-' ); + return function( prop ) { + return prop.replace( unprefixPattern, '' ); + }; + }( vendors ); - // shared - getDelimiter = getRegexMatcher( /^[^\s=]+/ ); + /* virtualdom/items/Element/Transition/helpers/hyphenate.js */ + var hyphenate = function( vendors ) { - getDelimiterChange = function ( tokenizer ) { - var start, opening, closing; + var vendorPattern = new RegExp( '^(?:' + vendors.join( '|' ) + ')([A-Z])' ); + return function( str ) { + var hyphenated; + if ( !str ) { + return ''; + } + if ( vendorPattern.test( str ) ) { + str = '-' + str; + } + hyphenated = str.replace( /[A-Z]/g, function( match ) { + return '-' + match.toLowerCase(); + } ); + return hyphenated; + }; + }( vendors ); - if ( !getStringMatch( tokenizer, '=' ) ) { - return null; - } + /* virtualdom/items/Element/Transition/prototype/animateStyle/createTransitions.js */ + var virtualdom_items_Element_Transition$animateStyle_createTransitions = function( isClient, warn, createElement, camelCase, interpolate, Ticker, prefix, unprefix, hyphenate ) { - start = tokenizer.pos; - - // allow whitespace before new opening delimiter - allowWhitespace( tokenizer ); - - opening = getDelimiter( tokenizer ); - if ( !opening ) { - tokenizer.pos = start; - return null; + var createTransitions, testStyle, TRANSITION, TRANSITIONEND, CSS_TRANSITIONS_ENABLED, TRANSITION_DURATION, TRANSITION_PROPERTY, TRANSITION_TIMING_FUNCTION, canUseCssTransitions = {}, + cannotUseCssTransitions = {}; + if ( !isClient ) { + createTransitions = null; + } else { + testStyle = createElement( 'div' ).style; + // determine some facts about our environment + ( function() { + if ( testStyle.transition !== undefined ) { + TRANSITION = 'transition'; + TRANSITIONEND = 'transitionend'; + CSS_TRANSITIONS_ENABLED = true; + } else if ( testStyle.webkitTransition !== undefined ) { + TRANSITION = 'webkitTransition'; + TRANSITIONEND = 'webkitTransitionEnd'; + CSS_TRANSITIONS_ENABLED = true; + } else { + CSS_TRANSITIONS_ENABLED = false; + } + }() ); + if ( TRANSITION ) { + TRANSITION_DURATION = TRANSITION + 'Duration'; + TRANSITION_PROPERTY = TRANSITION + 'Property'; + TRANSITION_TIMING_FUNCTION = TRANSITION + 'TimingFunction'; + } + createTransitions = function( t, to, options, changedProperties, resolve ) { + // Wait a beat (otherwise the target styles will be applied immediately) + // TODO use a fastdom-style mechanism? + setTimeout( function() { + var hashPrefix, jsTransitionsComplete, cssTransitionsComplete, checkComplete, transitionEndHandler; + checkComplete = function() { + if ( jsTransitionsComplete && cssTransitionsComplete ) { + // will changes to events and fire have an unexpected consequence here? + t.root.fire( t.name + ':end', t.node, t.isIntro ); + resolve(); + } + }; + // this is used to keep track of which elements can use CSS to animate + // which properties + hashPrefix = ( t.node.namespaceURI || '' ) + t.node.tagName; + t.node.style[ TRANSITION_PROPERTY ] = changedProperties.map( prefix ).map( hyphenate ).join( ',' ); + t.node.style[ TRANSITION_TIMING_FUNCTION ] = hyphenate( options.easing || 'linear' ); + t.node.style[ TRANSITION_DURATION ] = options.duration / 1000 + 's'; + transitionEndHandler = function( event ) { + var index; + index = changedProperties.indexOf( camelCase( unprefix( event.propertyName ) ) ); + if ( index !== -1 ) { + changedProperties.splice( index, 1 ); + } + if ( changedProperties.length ) { + // still transitioning... + return; + } + t.node.removeEventListener( TRANSITIONEND, transitionEndHandler, false ); + cssTransitionsComplete = true; + checkComplete(); + }; + t.node.addEventListener( TRANSITIONEND, transitionEndHandler, false ); + setTimeout( function() { + var i = changedProperties.length, + hash, originalValue, index, propertiesToTransitionInJs = [], + prop, suffix; + while ( i-- ) { + prop = changedProperties[ i ]; + hash = hashPrefix + prop; + if ( CSS_TRANSITIONS_ENABLED && !cannotUseCssTransitions[ hash ] ) { + t.node.style[ prefix( prop ) ] = to[ prop ]; + // If we're not sure if CSS transitions are supported for + // this tag/property combo, find out now + if ( !canUseCssTransitions[ hash ] ) { + originalValue = t.getStyle( prop ); + // if this property is transitionable in this browser, + // the current style will be different from the target style + canUseCssTransitions[ hash ] = t.getStyle( prop ) != to[ prop ]; + cannotUseCssTransitions[ hash ] = !canUseCssTransitions[ hash ]; + // Reset, if we're going to use timers after all + if ( cannotUseCssTransitions[ hash ] ) { + t.node.style[ prefix( prop ) ] = originalValue; + } + } + } + if ( !CSS_TRANSITIONS_ENABLED || cannotUseCssTransitions[ hash ] ) { + // we need to fall back to timer-based stuff + if ( originalValue === undefined ) { + originalValue = t.getStyle( prop ); + } + // need to remove this from changedProperties, otherwise transitionEndHandler + // will get confused + index = changedProperties.indexOf( prop ); + if ( index === -1 ) { + warn( 'Something very strange happened with transitions. If you see this message, please let @RactiveJS know. Thanks!' ); + } else { + changedProperties.splice( index, 1 ); + } + // TODO Determine whether this property is animatable at all + suffix = /[^\d]*$/.exec( to[ prop ] )[ 0 ]; + // ...then kick off a timer-based transition + propertiesToTransitionInJs.push( { + name: prefix( prop ), + interpolator: interpolate( parseFloat( originalValue ), parseFloat( to[ prop ] ) ), + suffix: suffix + } ); + } + } + // javascript transitions + if ( propertiesToTransitionInJs.length ) { + new Ticker( { + root: t.root, + duration: options.duration, + easing: camelCase( options.easing || '' ), + step: function( pos ) { + var prop, i; + i = propertiesToTransitionInJs.length; + while ( i-- ) { + prop = propertiesToTransitionInJs[ i ]; + t.node.style[ prop.name ] = prop.interpolator( pos ) + prop.suffix; + } + }, + complete: function() { + jsTransitionsComplete = true; + checkComplete(); + } + } ); + } else { + jsTransitionsComplete = true; + } + if ( !changedProperties.length ) { + // We need to cancel the transitionEndHandler, and deal with + // the fact that it will never fire + t.node.removeEventListener( TRANSITIONEND, transitionEndHandler, false ); + cssTransitionsComplete = true; + checkComplete(); + } + }, 0 ); + }, options.delay || 0 ); + }; } + return createTransitions; + }( isClient, warn, createElement, camelCase, interpolate, Ticker, prefix, unprefix, hyphenate ); - // allow whitespace (in fact, it's necessary...) - allowWhitespace( tokenizer ); + /* virtualdom/items/Element/Transition/prototype/animateStyle/visibility.js */ + var virtualdom_items_Element_Transition$animateStyle_visibility = function( vendors ) { - closing = getDelimiter( tokenizer ); - if ( !closing ) { - tokenizer.pos = start; - return null; + var hidden, vendor, prefix, i, visibility; + if ( typeof document !== 'undefined' ) { + hidden = 'hidden'; + visibility = {}; + if ( hidden in document ) { + prefix = ''; + } else { + i = vendors.length; + while ( i-- ) { + vendor = vendors[ i ]; + hidden = vendor + 'Hidden'; + if ( hidden in document ) { + prefix = vendor; + } + } + } + if ( prefix !== undefined ) { + document.addEventListener( prefix + 'visibilitychange', onChange ); + // initialise + onChange(); + } else { + // gah, we're in an old browser + if ( 'onfocusout' in document ) { + document.addEventListener( 'focusout', onHide ); + document.addEventListener( 'focusin', onShow ); + } else { + window.addEventListener( 'pagehide', onHide ); + window.addEventListener( 'blur', onHide ); + window.addEventListener( 'pageshow', onShow ); + window.addEventListener( 'focus', onShow ); + } + visibility.hidden = false; + } } - // allow whitespace before closing '=' - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '=' ) ) { - tokenizer.pos = start; - return null; + function onChange() { + visibility.hidden = document[ hidden ]; } - return [ opening, closing ]; - }; - - getName = getRegexMatcher( /^[a-zA-Z_$][a-zA-Z_$0-9]*/ ); - - getMustacheRef = function ( tokenizer ) { - var start, ref, member, dot, name; - - start = tokenizer.pos; - - dot = getStringMatch( tokenizer, '.' ) || ''; - name = getName( tokenizer ) || ''; - - if ( dot && !name ) { - return dot; + function onHide() { + visibility.hidden = true; } - ref = dot + name; - if ( !ref ) { - return null; + function onShow() { + visibility.hidden = false; } + return visibility; + }( vendors ); - member = getRefinement( tokenizer ); - while ( member !== null ) { - ref += member; - member = getRefinement( tokenizer ); + /* virtualdom/items/Element/Transition/prototype/animateStyle/_animateStyle.js */ + var virtualdom_items_Element_Transition$animateStyle__animateStyle = function( legacy, isClient, warn, Promise, prefix, createTransitions, visibility ) { + + var animateStyle, getComputedStyle, resolved; + if ( !isClient ) { + animateStyle = null; + } else { + getComputedStyle = window.getComputedStyle || legacy.getComputedStyle; + animateStyle = function( style, value, options, complete ) { + var t = this, + to; + // Special case - page isn't visible. Don't animate anything, because + // that way you'll never get CSS transitionend events + if ( visibility.hidden ) { + this.setStyle( style, value ); + return resolved || ( resolved = Promise.resolve() ); + } + if ( typeof style === 'string' ) { + to = {}; + to[ style ] = value; + } else { + to = style; + // shuffle arguments + complete = options; + options = value; + } + // As of 0.3.9, transition authors should supply an `option` object with + // `duration` and `easing` properties (and optional `delay`), plus a + // callback function that gets called after the animation completes + // TODO remove this check in a future version + if ( !options ) { + warn( 'The "' + t.name + '" transition does not supply an options object to `t.animateStyle()`. This will break in a future version of Ractive. For more info see https://github.com/RactiveJS/Ractive/issues/340' ); + options = t; + complete = t.complete; + } + var promise = new Promise( function( resolve ) { + var propertyNames, changedProperties, computedStyle, current, from, i, prop; + // Edge case - if duration is zero, set style synchronously and complete + if ( !options.duration ) { + t.setStyle( to ); + resolve(); + return; + } + // Get a list of the properties we're animating + propertyNames = Object.keys( to ); + changedProperties = []; + // Store the current styles + computedStyle = getComputedStyle( t.node ); + from = {}; + i = propertyNames.length; + while ( i-- ) { + prop = propertyNames[ i ]; + current = computedStyle[ prefix( prop ) ]; + if ( current === '0px' ) { + current = 0; + } + // we need to know if we're actually changing anything + if ( current != to[ prop ] ) { + // use != instead of !==, so we can compare strings with numbers + changedProperties.push( prop ); + // make the computed style explicit, so we can animate where + // e.g. height='auto' + t.node.style[ prefix( prop ) ] = current; + } + } + // If we're not actually changing anything, the transitionend event + // will never fire! So we complete early + if ( !changedProperties.length ) { + resolve(); + return; + } + createTransitions( t, to, options, changedProperties, resolve ); + } ); + // If a callback was supplied, do the honours + // TODO remove this check in future + if ( complete ) { + warn( 't.animateStyle returns a Promise as of 0.4.0. Transition authors should do t.animateStyle(...).then(callback)' ); + promise.then( complete ); + } + return promise; + }; } + return animateStyle; + }( legacy, isClient, warn, Promise, prefix, virtualdom_items_Element_Transition$animateStyle_createTransitions, virtualdom_items_Element_Transition$animateStyle_visibility ); - return ref; + /* utils/fillGaps.js */ + var fillGaps = function( target ) { + var SLICE$0 = Array.prototype.slice; + var sources = SLICE$0.call( arguments, 1 ); + sources.forEach( function( s ) { + for ( var key in s ) { + if ( s.hasOwnProperty( key ) && !( key in target ) ) { + target[ key ] = s[ key ]; + } + } + } ); + return target; }; - getRefinement = function ( tokenizer ) { - return getDotRefinement( tokenizer ) || getArrayRefinement( tokenizer ); - }; + /* virtualdom/items/Element/Transition/prototype/processParams.js */ + var virtualdom_items_Element_Transition$processParams = function( fillGaps ) { - getDotRefinement = getRegexMatcher( /^\.[a-zA-Z_$0-9]+/ ); + return function( params, defaults ) { + if ( typeof params === 'number' ) { + params = { + duration: params + }; + } else if ( typeof params === 'string' ) { + if ( params === 'slow' ) { + params = { + duration: 600 + }; + } else if ( params === 'fast' ) { + params = { + duration: 200 + }; + } else { + params = { + duration: 400 + }; + } + } else if ( !params ) { + params = {}; + } + return fillGaps( {}, params, defaults ); + }; + }( fillGaps ); - getArrayRefinement = function ( tokenizer ) { - var num = getArrayMember( tokenizer ); + /* virtualdom/items/Element/Transition/prototype/start.js */ + var virtualdom_items_Element_Transition$start = function() { - if ( num ) { - return '.' + num; + var __export; + __export = function Transition$start() { + var t = this, + node, originalStyle, completed; + node = t.node = t.element.node; + originalStyle = node.getAttribute( 'style' ); + // create t.complete() - we don't want this on the prototype, + // because we don't want `this` silliness when passing it as + // an argument + t.complete = function( noReset ) { + if ( completed ) { + return; + } + if ( !noReset && t.isIntro ) { + resetStyle( node, originalStyle ); + } + node._ractive.transition = null; + t._manager.remove( t ); + completed = true; + }; + // If the transition function doesn't exist, abort + if ( !t._fn ) { + t.complete(); + return; + } + t._fn.apply( t.root, [ t ].concat( t.params ) ); + }; + + function resetStyle( node, style ) { + if ( style ) { + node.setAttribute( 'style', style ); + } else { + // Next line is necessary, to remove empty style attribute! + // See http://stackoverflow.com/a/7167553 + node.getAttribute( 'style' ); + node.removeAttribute( 'style' ); + } } + return __export; + }(); - return null; - }; + /* virtualdom/items/Element/Transition/_Transition.js */ + var Transition = function( init, getStyle, setStyle, animateStyle, processParams, start, circular ) { - getArrayMember = getRegexMatcher( /^\[(0|[1-9][0-9]*)\]/ ); + var Fragment, Transition; + circular.push( function() { + Fragment = circular.Fragment; + } ); + Transition = function( owner, template, isIntro ) { + this.init( owner, template, isIntro ); + }; + Transition.prototype = { + init: init, + start: start, + getStyle: getStyle, + setStyle: setStyle, + animateStyle: animateStyle, + processParams: processParams + }; + return Transition; + }( virtualdom_items_Element_Transition$init, virtualdom_items_Element_Transition$getStyle, virtualdom_items_Element_Transition$setStyle, virtualdom_items_Element_Transition$animateStyle__animateStyle, virtualdom_items_Element_Transition$processParams, virtualdom_items_Element_Transition$start, circular ); - getSingleQuotedString = function ( tokenizer ) { - var start, string, escaped, unescaped, next; + /* virtualdom/items/Element/prototype/render.js */ + var virtualdom_items_Element$render = function( namespaces, isArray, warn, create, createElement, defineProperty, noop, runloop, getInnerContext, renderImage, Transition ) { - start = tokenizer.pos; + var __export; + var updateCss, updateScript; + updateCss = function() { + var node = this.node, + content = this.fragment.toString( false ); + // IE8 has no styleSheet unless there's a type text/css + if ( window && window.appearsToBeIELessEqual8 ) { + node.type = 'text/css'; + } + if ( node.styleSheet ) { + node.styleSheet.cssText = content; + } else { + while ( node.hasChildNodes() ) { + node.removeChild( node.firstChild ); + } + node.appendChild( document.createTextNode( content ) ); + } + }; + updateScript = function() { + if ( !this.node.type || this.node.type === 'text/javascript' ) { + warn( 'Script tag was updated. This does not cause the code to be re-evaluated!' ); + } + this.node.text = this.fragment.toString( false ); + }; + __export = function Element$render() { + var this$0 = this; + var root = this.root, + namespace, node; + namespace = getNamespace( this ); + node = this.node = createElement( this.name, namespace ); + // Is this a top-level node of a component? If so, we may need to add + // a data-rvcguid attribute, for CSS encapsulation + // NOTE: css no longer copied to instance, so we check constructor.css - + // we can enhance to handle instance, but this is more "correct" with current + // functionality + if ( root.constructor.css && this.parentFragment.getNode() === root.el ) { + this.node.setAttribute( 'data-rvcguid', root.constructor._guid ); + } + // Add _ractive property to the node - we use this object to store stuff + // related to proxy events, two-way bindings etc + defineProperty( this.node, '_ractive', { + value: { + proxy: this, + keypath: getInnerContext( this.parentFragment ), + index: this.parentFragment.indexRefs, + events: create( null ), + root: root + } + } ); + // Render attributes + this.attributes.forEach( function( a ) { + return a.render( node ); + } ); + this.conditionalAttributes.forEach( function( a ) { + return a.render( node ); + } ); + // Render children + if ( this.fragment ) { + // Special case - <script> element + if ( this.name === 'script' ) { + this.bubble = updateScript; + this.node.text = this.fragment.toString( false ); + // bypass warning initially + this.fragment.unrender = noop; + } else if ( this.name === 'style' ) { + this.bubble = updateCss; + this.bubble(); + this.fragment.unrender = noop; + } else if ( this.binding && this.getAttribute( 'contenteditable' ) ) { + this.fragment.unrender = noop; + } else { + this.node.appendChild( this.fragment.render() ); + } + } + // Add proxy event handlers + if ( this.eventHandlers ) { + this.eventHandlers.forEach( function( h ) { + return h.render(); + } ); + } + // deal with two-way bindings + if ( this.binding ) { + this.binding.render(); + this.node._ractive.binding = this.binding; + } + // Special case: if this is an <img>, and we're in a crap browser, we may + // need to prevent it from overriding width and height when it loads the src + if ( this.name === 'img' ) { + renderImage( this ); + } + // apply decorator(s) + if ( this.decorator && this.decorator.fn ) { + runloop.scheduleTask( function() { + return this$0.decorator.init(); + }, true ); + } + // trigger intro transition + if ( root.transitionsEnabled && this.intro ) { + var transition = new Transition( this, this.intro, true ); + runloop.registerTransition( transition ); + runloop.scheduleTask( function() { + return transition.start(); + }, true ); + this.transition = transition; + } + if ( this.name === 'option' ) { + processOption( this ); + } + if ( this.node.autofocus ) { + // Special case. Some browsers (*cough* Firefix *cough*) have a problem + // with dynamically-generated elements having autofocus, and they won't + // allow you to programmatically focus the element until it's in the DOM + runloop.scheduleTask( function() { + return this$0.node.focus(); + }, true ); + } + updateLiveQueries( this ); + return this.node; + }; - string = ''; - - escaped = getEscapedChars( tokenizer ); - if ( escaped ) { - string += escaped; + function getNamespace( element ) { + var namespace, xmlns, parent; + // Use specified namespace... + if ( xmlns = element.getAttribute( 'xmlns' ) ) { + namespace = xmlns; + } else if ( element.name === 'svg' ) { + namespace = namespaces.svg; + } else if ( parent = element.parent ) { + // ...or HTML, if the parent is a <foreignObject> + if ( parent.name === 'foreignObject' ) { + namespace = namespaces.html; + } else { + namespace = parent.node.namespaceURI; + } + } else { + namespace = element.root.el.namespaceURI; + } + return namespace; } - unescaped = getUnescapedSingleQuotedChars( tokenizer ); - if ( unescaped ) { - string += unescaped; - } - if ( string ) { - next = getSingleQuotedString( tokenizer ); - while ( next ) { - string += next; - next = getSingleQuotedString( tokenizer ); + function processOption( option ) { + var optionValue, selectValue, i; + if ( !option.select ) { + return; } + selectValue = option.select.getAttribute( 'value' ); + if ( selectValue === undefined ) { + return; + } + optionValue = option.getAttribute( 'value' ); + if ( option.select.node.multiple && isArray( selectValue ) ) { + i = selectValue.length; + while ( i-- ) { + if ( optionValue == selectValue[ i ] ) { + option.node.selected = true; + break; + } + } + } else { + option.node.selected = optionValue == selectValue; + } } - return string; - }; + function updateLiveQueries( element ) { + var instance, liveQueries, i, selector, query; + // Does this need to be added to any live queries? + instance = element.root; + do { + liveQueries = instance._liveQueries; + i = liveQueries.length; + while ( i-- ) { + selector = liveQueries[ i ]; + query = liveQueries[ '_' + selector ]; + if ( query._test( element ) ) { + // keep register of applicable selectors, for when we teardown + ( element.liveQueries || ( element.liveQueries = [] ) ).push( query ); + } + } + } while ( instance = instance._parent ); + } + return __export; + }( namespaces, isArray, warn, create, createElement, defineProperty, noop, runloop, getInnerContext, render, Transition ); - getUnescapedSingleQuotedChars = getRegexMatcher( /^[^\\']+/ ); + /* virtualdom/items/Element/prototype/toString.js */ + var virtualdom_items_Element$toString = function( voidElementNames, isArray, escapeHtml ) { - getDoubleQuotedString = function ( tokenizer ) { - var start, string, escaped, unescaped, next; + var __export; + __export = function() { + var str, escape; + str = '<' + ( this.template.y ? '!DOCTYPE' : this.template.e ); + str += this.attributes.map( stringifyAttribute ).join( '' ) + this.conditionalAttributes.map( stringifyAttribute ).join( '' ); + // Special case - selected options + if ( this.name === 'option' && optionIsSelected( this ) ) { + str += ' selected'; + } + // Special case - two-way radio name bindings + if ( this.name === 'input' && inputIsCheckedRadio( this ) ) { + str += ' checked'; + } + str += '>'; + // Special case - textarea + if ( this.name === 'textarea' && this.getAttribute( 'value' ) !== undefined ) { + str += escapeHtml( this.getAttribute( 'value' ) ); + } else if ( this.getAttribute( 'contenteditable' ) !== undefined ) { + str += this.getAttribute( 'value' ); + } + if ( this.fragment ) { + escape = this.name !== 'script' && this.name !== 'style'; + str += this.fragment.toString( escape ); + } + // add a closing tag if this isn't a void element + if ( !voidElementNames.test( this.template.e ) ) { + str += '</' + this.template.e + '>'; + } + return str; + }; - start = tokenizer.pos; - - string = ''; - - escaped = getEscapedChars( tokenizer ); - if ( escaped ) { - string += escaped; + function optionIsSelected( element ) { + var optionValue, selectValue, i; + optionValue = element.getAttribute( 'value' ); + if ( optionValue === undefined || !element.select ) { + return false; + } + selectValue = element.select.getAttribute( 'value' ); + if ( selectValue == optionValue ) { + return true; + } + if ( element.select.getAttribute( 'multiple' ) && isArray( selectValue ) ) { + i = selectValue.length; + while ( i-- ) { + if ( selectValue[ i ] == optionValue ) { + return true; + } + } + } } - unescaped = getUnescapedDoubleQuotedChars( tokenizer ); - if ( unescaped ) { - string += unescaped; + function inputIsCheckedRadio( element ) { + var attributes, typeAttribute, valueAttribute, nameAttribute; + attributes = element.attributes; + typeAttribute = attributes.type; + valueAttribute = attributes.value; + nameAttribute = attributes.name; + if ( !typeAttribute || typeAttribute.value !== 'radio' || !valueAttribute || !nameAttribute.interpolator ) { + return; + } + if ( valueAttribute.value === nameAttribute.interpolator.value ) { + return true; + } } - if ( !string ) { - return ''; + function stringifyAttribute( attribute ) { + var str = attribute.toString(); + return str ? ' ' + str : ''; } + return __export; + }( voidElementNames, isArray, escapeHtml ); - next = getDoubleQuotedString( tokenizer ); - while ( next !== '' ) { - string += next; - } + /* virtualdom/items/Element/special/option/unbind.js */ + var virtualdom_items_Element_special_option_unbind = function( removeFromArray ) { - return string; - }; + return function unbindOption( option ) { + if ( option.select ) { + removeFromArray( option.select.options, option ); + } + }; + }( removeFromArray ); - getUnescapedDoubleQuotedChars = getRegexMatcher( /^[^\\"]+/ ); + /* virtualdom/items/Element/prototype/unbind.js */ + var virtualdom_items_Element$unbind = function( unbindOption ) { - getEscapedChars = function ( tokenizer ) { - var chars = '', character; + var __export; + __export = function Element$unbind() { + if ( this.fragment ) { + this.fragment.unbind(); + } + if ( this.binding ) { + this.binding.unbind(); + } + if ( this.eventHandlers ) { + this.eventHandlers.forEach( unbind ); + } + // Special case - <option> + if ( this.name === 'option' ) { + unbindOption( this ); + } + this.attributes.forEach( unbind ); + this.conditionalAttributes.forEach( unbind ); + }; - character = getEscapedChar( tokenizer ); - while ( character ) { - chars += character; - character = getEscapedChar( tokenizer ); + function unbind( x ) { + x.unbind(); } + return __export; + }( virtualdom_items_Element_special_option_unbind ); - return chars || null; - }; + /* virtualdom/items/Element/prototype/unrender.js */ + var virtualdom_items_Element$unrender = function( runloop, Transition ) { - getEscapedChar = function ( tokenizer ) { - var character; + var __export; + __export = function Element$unrender( shouldDestroy ) { + var binding, bindings; + if ( this.transition ) { + this.transition.complete(); + } + // Detach as soon as we can + if ( this.name === 'option' ) { + // <option> elements detach immediately, so that + // their parent <select> element syncs correctly, and + // since option elements can't have transitions anyway + this.detach(); + } else if ( shouldDestroy ) { + runloop.detachWhenReady( this ); + } + // Children first. that way, any transitions on child elements will be + // handled by the current transitionManager + if ( this.fragment ) { + this.fragment.unrender( false ); + } + if ( binding = this.binding ) { + this.binding.unrender(); + this.node._ractive.binding = null; + bindings = this.root._twowayBindings[ binding.keypath ]; + bindings.splice( bindings.indexOf( binding ), 1 ); + } + // Remove event handlers + if ( this.eventHandlers ) { + this.eventHandlers.forEach( function( h ) { + return h.unrender(); + } ); + } + if ( this.decorator ) { + this.decorator.teardown(); + } + // trigger outro transition if necessary + if ( this.root.transitionsEnabled && this.outro ) { + var transition = new Transition( this, this.outro, false ); + runloop.registerTransition( transition ); + runloop.scheduleTask( function() { + return transition.start(); + } ); + } + // Remove this node from any live queries + if ( this.liveQueries ) { + removeFromLiveQueries( this ); + } + }; - if ( !getStringMatch( tokenizer, '\\' ) ) { - return null; + function removeFromLiveQueries( element ) { + var query, selector, i; + i = element.liveQueries.length; + while ( i-- ) { + query = element.liveQueries[ i ]; + selector = query.selector; + query._remove( element.node ); + } } + return __export; + }( runloop, Transition ); - character = tokenizer.str.charAt( tokenizer.pos ); - tokenizer.pos += 1; + /* virtualdom/items/Element/_Element.js */ + var Element = function( bubble, detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, getAttribute, init, rebind, render, toString, unbind, unrender ) { - return character; - }; + var Element = function( options ) { + this.init( options ); + }; + Element.prototype = { + bubble: bubble, + detach: detach, + find: find, + findAll: findAll, + findAllComponents: findAllComponents, + findComponent: findComponent, + findNextNode: findNextNode, + firstNode: firstNode, + getAttribute: getAttribute, + init: init, + rebind: rebind, + render: render, + toString: toString, + unbind: unbind, + unrender: unrender + }; + return Element; + }( virtualdom_items_Element$bubble, virtualdom_items_Element$detach, virtualdom_items_Element$find, virtualdom_items_Element$findAll, virtualdom_items_Element$findAllComponents, virtualdom_items_Element$findComponent, virtualdom_items_Element$findNextNode, virtualdom_items_Element$firstNode, virtualdom_items_Element$getAttribute, virtualdom_items_Element$init, virtualdom_items_Element$rebind, virtualdom_items_Element$render, virtualdom_items_Element$toString, virtualdom_items_Element$unbind, virtualdom_items_Element$unrender ); - + /* virtualdom/items/Partial/deIndent.js */ + var deIndent = function() { + var __export; + var empty = /^\s*$/, + leadingWhitespace = /^\s*/; + __export = function( str ) { + var lines, firstLine, lastLine, minIndent; + lines = str.split( '\n' ); + // remove first and last line, if they only contain whitespace + firstLine = lines[ 0 ]; + if ( firstLine !== undefined && empty.test( firstLine ) ) { + lines.shift(); + } + lastLine = lines[ lines.length - 1 ]; + if ( lastLine !== undefined && empty.test( lastLine ) ) { + lines.pop(); + } + minIndent = lines.reduce( reducer, null ); + if ( minIndent ) { + str = lines.map( function( line ) { + return line.replace( minIndent, '' ); + } ).join( '\n' ); + } + return str; + }; + function reducer( previous, line ) { + var lineIndent = leadingWhitespace.exec( line )[ 0 ]; + if ( previous === null || lineIndent.length < previous.length ) { + return lineIndent; + } + return previous; + } + return __export; + }(); - // mustache / triple - (function () { - var getMustacheContent, - getMustacheType, - getIndexRef, - mustacheTypes; + /* virtualdom/items/Partial/getPartialTemplate.js */ + var getPartialTemplate = function( log, config, parser, deIndent ) { - getMustache = function ( tokenizer ) { - var start = tokenizer.pos, content; - - if ( !getStringMatch( tokenizer, tokenizer.delimiters[0] ) ) { - return null; + var __export; + __export = function getPartialTemplate( ractive, name ) { + var partial; + // If the partial in instance or view heirarchy instances, great + if ( partial = getPartialFromRegistry( ractive, name ) ) { + return partial; } + // Does it exist on the page as a script tag? + partial = parser.fromId( name, { + noThrow: true + } ); + if ( partial ) { + // is this necessary? + partial = deIndent( partial ); + // parse and register to this ractive instance + var parsed = parser.parse( partial, parser.getParseOptions( ractive ) ); + // register (and return main partial if there are others in the template) + return ractive.partials[ name ] = parsed.t; + } + }; - // delimiter change? - content = getDelimiterChange( tokenizer ); - if ( content ) { - // find closing delimiter or abort... - if ( !getStringMatch( tokenizer, tokenizer.delimiters[1] ) ) { - tokenizer.pos = start; - return null; + function getPartialFromRegistry( ractive, name ) { + var partials = config.registries.partials; + // find first instance in the ractive or view hierarchy that has this partial + var instance = partials.findInstance( ractive, name ); + if ( !instance ) { + return; + } + var partial = instance.partials[ name ], + fn; + // partial is a function? + if ( typeof partial === 'function' ) { + fn = partial.bind( instance ); + fn.isOwner = instance.partials.hasOwnProperty( name ); + partial = fn( instance.data, parser ); + } + if ( !partial ) { + log.warn( { + debug: ractive.debug, + message: 'noRegistryFunctionReturn', + args: { + registry: 'partial', + name: name + } + } ); + return; + } + // If this was added manually to the registry, + // but hasn't been parsed, parse it now + if ( !parser.isParsed( partial ) ) { + // use the parseOptions of the ractive instance on which it was found + var parsed = parser.parse( partial, parser.getParseOptions( instance ) ); + // Partials cannot contain nested partials! + // TODO add a test for this + if ( parsed.p ) { + log.warn( { + debug: ractive.debug, + message: 'noNestedPartials', + args: { + rname: name + } + } ); } - - // ...then make the switch - tokenizer.delimiters = content; - return { type: MUSTACHE, mustacheType: DELIMCHANGE }; + // if fn, use instance to store result, otherwise needs to go + // in the correct point in prototype chain on instance or constructor + var target = fn ? instance : partials.findOwner( instance, name ); + // may be a template with partials, which need to be registered and main template extracted + target.partials[ name ] = partial = parsed.t; } - - content = getMustacheContent( tokenizer ); - - if ( content === null ) { - tokenizer.pos = start; - return null; + // store for reset + if ( fn ) { + partial._fn = fn; } + return partial.v ? partial.t : partial; + } + return __export; + }( log, config, parser, deIndent ); - // allow whitespace before closing delimiter - allowWhitespace( tokenizer ); + /* virtualdom/items/Partial/applyIndent.js */ + var applyIndent = function( string, indent ) { + var indented; + if ( !indent ) { + return string; + } + indented = string.split( '\n' ).map( function( line, notFirstLine ) { + return notFirstLine ? indent + line : line; + } ).join( '\n' ); + return indented; + }; - if ( !getStringMatch( tokenizer, tokenizer.delimiters[1] ) ) { - fail( tokenizer, '"' + tokenizer.delimiters[1] + '"' ); - } + /* virtualdom/items/Partial/_Partial.js */ + var Partial = function( log, types, getPartialTemplate, applyIndent, circular, runloop, Mustache, rebind, unbind ) { - return content; + var Partial, Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + Partial = function( options ) { + var parentFragment, template; + parentFragment = this.parentFragment = options.parentFragment; + this.root = parentFragment.root; + this.type = types.PARTIAL; + this.index = options.index; + this.name = options.template.r; + this.fragment = this.fragmentToRender = this.fragmentToUnrender = null; + Mustache.init( this, options ); + // If this didn't resolve, it most likely means we have a named partial + // (i.e. `{{>foo}}` means 'use the foo partial', not 'use the partial + // whose name is the value of `foo`') + if ( !this.keypath && ( template = getPartialTemplate( this.root, this.name ) ) ) { + unbind.call( this ); + // prevent any further changes + this.isNamed = true; + this.setTemplate( template ); + } }; - - getTriple = function ( tokenizer ) { - var start = tokenizer.pos, content; - - if ( !getStringMatch( tokenizer, tokenizer.tripleDelimiters[0] ) ) { - return null; + Partial.prototype = { + bubble: function() { + this.parentFragment.bubble(); + }, + detach: function() { + return this.fragment.detach(); + }, + find: function( selector ) { + return this.fragment.find( selector ); + }, + findAll: function( selector, query ) { + return this.fragment.findAll( selector, query ); + }, + findComponent: function( selector ) { + return this.fragment.findComponent( selector ); + }, + findAllComponents: function( selector, query ) { + return this.fragment.findAllComponents( selector, query ); + }, + firstNode: function() { + return this.fragment.firstNode(); + }, + findNextNode: function() { + return this.parentFragment.findNextNode( this ); + }, + getValue: function() { + return this.fragment.getValue(); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + rebind.call( this, indexRef, newIndex, oldKeypath, newKeypath ); + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + render: function() { + this.docFrag = document.createDocumentFragment(); + this.update(); + this.rendered = true; + return this.docFrag; + }, + resolve: Mustache.resolve, + setValue: function( value ) { + var template; + if ( value !== undefined && value === this.value ) { + // nothing has changed, so no work to be done + return; + } + template = getPartialTemplate( this.root, '' + value ); + // we may be here if we have a partial like `{{>foo}}` and `foo` is the + // name of both a data property (whose value ISN'T the name of a partial) + // and a partial. In those cases, this becomes a named partial + if ( !template && this.name && ( template = getPartialTemplate( this.root, this.name ) ) ) { + unbind.call( this ); + this.isNamed = true; + } + if ( !template ) { + log.error( { + debug: this.root.debug, + message: 'noTemplateForPartial', + args: { + name: this.name + } + } ); + } + this.setTemplate( template || [] ); + this.value = value; + this.bubble(); + if ( this.rendered ) { + runloop.addView( this ); + } + }, + setTemplate: function( template ) { + if ( this.fragment ) { + this.fragment.unbind(); + this.fragmentToUnrender = this.fragment; + } + this.fragment = new Fragment( { + template: template, + root: this.root, + owner: this, + pElement: this.parentFragment.pElement + } ); + this.fragmentToRender = this.fragment; + }, + toString: function( toString ) { + var string, previousItem, lastLine, match; + string = this.fragment.toString( toString ); + previousItem = this.parentFragment.items[ this.index - 1 ]; + if ( !previousItem || previousItem.type !== types.TEXT ) { + return string; + } + lastLine = previousItem.text.split( '\n' ).pop(); + if ( match = /^\s+$/.exec( lastLine ) ) { + return applyIndent( string, match[ 0 ] ); + } + return string; + }, + unbind: function() { + if ( !this.isNamed ) { + // dynamic partial - need to unbind self + unbind.call( this ); + } + if ( this.fragment ) { + this.fragment.unbind(); + } + }, + unrender: function( shouldDestroy ) { + if ( this.rendered ) { + if ( this.fragment ) { + this.fragment.unrender( shouldDestroy ); + } + this.rendered = false; + } + }, + update: function() { + var target, anchor; + if ( this.fragmentToUnrender ) { + this.fragmentToUnrender.unrender( true ); + this.fragmentToUnrender = null; + } + if ( this.fragmentToRender ) { + this.docFrag.appendChild( this.fragmentToRender.render() ); + this.fragmentToRender = null; + } + if ( this.rendered ) { + target = this.parentFragment.getNode(); + anchor = this.parentFragment.findNextNode( this ); + target.insertBefore( this.docFrag, anchor ); + } } + }; + return Partial; + }( log, types, getPartialTemplate, applyIndent, circular, runloop, Mustache, rebind, unbind ); - // delimiter change? - content = getDelimiterChange( tokenizer ); - if ( content ) { - // find closing delimiter or abort... - if ( !getStringMatch( tokenizer, tokenizer.delimiters[1] ) ) { - tokenizer.pos = start; - return null; - } + /* virtualdom/items/Component/getComponent.js */ + var getComponent = function( config, log, circular ) { - // ...then make the switch - tokenizer.tripleDelimiters = content; - return { type: DELIMCHANGE }; + var Ractive; + circular.push( function() { + Ractive = circular.Ractive; + } ); + // finds the component constructor in the registry or view hierarchy registries + return function getComponent( ractive, name ) { + var component, instance = config.registries.components.findInstance( ractive, name ); + if ( instance ) { + component = instance.components[ name ]; + // best test we have for not Ractive.extend + if ( !component._parent ) { + // function option, execute and store for reset + var fn = component.bind( instance ); + fn.isOwner = instance.components.hasOwnProperty( name ); + component = fn( instance.data ); + if ( !component ) { + log.warn( { + debug: ractive.debug, + message: 'noRegistryFunctionReturn', + args: { + registry: 'component', + name: name + } + } ); + return; + } + if ( typeof component === 'string' ) { + //allow string lookup + component = getComponent( ractive, component ); + } + component._fn = fn; + instance.components[ name ] = component; + } } + return component; + }; + }( config, log, circular ); - // allow whitespace between opening delimiter and reference - allowWhitespace( tokenizer ); + /* virtualdom/items/Component/prototype/detach.js */ + var virtualdom_items_Component$detach = function( Hook ) { - content = getMustacheContent( tokenizer, true ); + var detachHook = new Hook( 'detach' ); + return function Component$detach() { + var detached = this.instance.fragment.detach(); + detachHook.fire( this.instance ); + return detached; + }; + }( Ractive$shared_hooks_Hook ); - if ( content === null ) { - tokenizer.pos = start; - return null; - } + /* virtualdom/items/Component/prototype/find.js */ + var virtualdom_items_Component$find = function Component$find( selector ) { + return this.instance.fragment.find( selector ); + }; - // allow whitespace between reference and closing delimiter - allowWhitespace( tokenizer ); + /* virtualdom/items/Component/prototype/findAll.js */ + var virtualdom_items_Component$findAll = function Component$findAll( selector, query ) { + return this.instance.fragment.findAll( selector, query ); + }; - if ( !getStringMatch( tokenizer, tokenizer.tripleDelimiters[1] ) ) { - tokenizer.pos = start; - return null; - } + /* virtualdom/items/Component/prototype/findAllComponents.js */ + var virtualdom_items_Component$findAllComponents = function Component$findAllComponents( selector, query ) { + query._test( this, true ); + if ( this.instance.fragment ) { + this.instance.fragment.findAllComponents( selector, query ); + } + }; - return content; - }; + /* virtualdom/items/Component/prototype/findComponent.js */ + var virtualdom_items_Component$findComponent = function Component$findComponent( selector ) { + if ( !selector || selector === this.name ) { + return this.instance; + } + if ( this.instance.fragment ) { + return this.instance.fragment.findComponent( selector ); + } + return null; + }; - getMustacheContent = function ( tokenizer, isTriple ) { - var start, mustache, type, expr, i, remaining, index; + /* virtualdom/items/Component/prototype/findNextNode.js */ + var virtualdom_items_Component$findNextNode = function Component$findNextNode() { + return this.parentFragment.findNextNode( this ); + }; - start = tokenizer.pos; + /* virtualdom/items/Component/prototype/firstNode.js */ + var virtualdom_items_Component$firstNode = function Component$firstNode() { + if ( this.rendered ) { + return this.instance.fragment.firstNode(); + } + return null; + }; - mustache = { type: isTriple ? TRIPLE : MUSTACHE }; + /* virtualdom/items/Component/initialise/createModel/ComponentParameter.js */ + var ComponentParameter = function( runloop, circular ) { - // mustache type - if ( !isTriple ) { - type = getMustacheType( tokenizer ); - mustache.mustacheType = type || INTERPOLATOR; // default + var Fragment, ComponentParameter; + circular.push( function() { + Fragment = circular.Fragment; + } ); + ComponentParameter = function( component, key, value ) { + this.parentFragment = component.parentFragment; + this.component = component; + this.key = key; + this.fragment = new Fragment( { + template: value, + root: component.root, + owner: this + } ); + this.value = this.fragment.getValue(); + }; + ComponentParameter.prototype = { + bubble: function() { + if ( !this.dirty ) { + this.dirty = true; + runloop.addView( this ); + } + }, + update: function() { + var value = this.fragment.getValue(); + this.component.instance.viewmodel.set( this.key, value ); + runloop.addViewmodel( this.component.instance.viewmodel ); + this.value = value; + this.dirty = false; + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + unbind: function() { + this.fragment.unbind(); + } + }; + return ComponentParameter; + }( runloop, circular ); - // if it's a comment, allow any contents except '}}' - if ( type === COMMENT ) { - remaining = tokenizer.remaining(); - index = remaining.indexOf( tokenizer.delimiters[1] ); + /* virtualdom/items/Component/initialise/createModel/ReferenceExpressionParameter.js */ + var ReferenceExpressionParameter = function( ReferenceExpressionResolver, createComponentBinding ) { - if ( index !== -1 ) { - tokenizer.pos += index; - return mustache; + var ReferenceExpressionParameter = function( component, childKeypath, template, toBind ) { + var this$0 = this; + this.root = component.root; + this.parentFragment = component.parentFragment; + this.ready = false; + this.hash = null; + this.resolver = new ReferenceExpressionResolver( this, template, function( keypath ) { + // Are we updating an existing binding? + if ( this$0.binding || ( this$0.binding = component.bindings[ this$0.hash ] ) ) { + component.bindings[ this$0.hash ] = null; + this$0.binding.rebind( keypath ); + this$0.hash = keypath + '=' + childKeypath; + component.bindings[ this$0.hash ]; + } else { + if ( !this$0.ready ) { + // The child instance isn't created yet, we need to create the binding later + toBind.push( { + childKeypath: childKeypath, + parentKeypath: keypath + } ); + } else { + createComponentBinding( component, component.root, keypath, childKeypath ); } } + this$0.value = component.root.viewmodel.get( keypath ); + } ); + }; + ReferenceExpressionParameter.prototype = { + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + this.resolver.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + unbind: function() { + this.resolver.unbind(); } + }; + return ReferenceExpressionParameter; + }( ReferenceExpressionResolver, createComponentBinding ); - // allow whitespace - allowWhitespace( tokenizer ); + /* virtualdom/items/Component/initialise/createModel/_createModel.js */ + var createModel = function( types, parseJSON, resolveRef, ComponentParameter, ReferenceExpressionParameter ) { - // is this an expression? - if ( getStringMatch( tokenizer, '(' ) ) { - - // looks like it... - allowWhitespace( tokenizer ); - - expr = getExpression( tokenizer ); - - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, ')' ) ) { - fail( tokenizer, '")"' ); + var __export; + __export = function( component, defaultData, attributes, toBind ) { + var data = {}, + key, value; + // some parameters, e.g. foo="The value is {{bar}}", are 'complex' - in + // other words, we need to construct a string fragment to watch + // when they change. We store these so they can be torn down later + component.complexParameters = []; + for ( key in attributes ) { + if ( attributes.hasOwnProperty( key ) ) { + value = getValue( component, key, attributes[ key ], toBind ); + if ( value !== undefined || defaultData[ key ] === undefined ) { + data[ key ] = value; + } } - - mustache.expression = expr; } + return data; + }; - else { - // mustache reference - mustache.ref = getMustacheRef( tokenizer ); - if ( !mustache.ref ) { - tokenizer.pos = start; - return null; + function getValue( component, key, template, toBind ) { + var parameter, parsed, parentInstance, parentFragment, keypath, indexRef; + parentInstance = component.root; + parentFragment = component.parentFragment; + // If this is a static value, great + if ( typeof template === 'string' ) { + parsed = parseJSON( template ); + if ( !parsed ) { + return template; } + return parsed.value; } - - // optional index reference - i = getIndexRef( tokenizer ); - if ( i !== null ) { - mustache.indexRef = i; + // If null, we treat it as a boolean attribute (i.e. true) + if ( template === null ) { + return true; } + // Single interpolator? + if ( template.length === 1 && template[ 0 ].t === types.INTERPOLATOR ) { + // If it's a regular interpolator, we bind to it + if ( template[ 0 ].r ) { + // Is it an index reference? + if ( parentFragment.indexRefs && parentFragment.indexRefs[ indexRef = template[ 0 ].r ] !== undefined ) { + component.indexRefBindings[ indexRef ] = key; + return parentFragment.indexRefs[ indexRef ]; + } + // TODO what about references that resolve late? Should these be considered? + keypath = resolveRef( parentInstance, template[ 0 ].r, parentFragment ) || template[ 0 ].r; + // We need to set up bindings between parent and child, but + // we can't do it yet because the child instance doesn't exist + // yet - so we make a note instead + toBind.push( { + childKeypath: key, + parentKeypath: keypath + } ); + return parentInstance.viewmodel.get( keypath ); + } + // If it's a reference expression (e.g. `{{foo[bar]}}`), we need + // to watch the keypath and create/destroy bindings + if ( template[ 0 ].rx ) { + parameter = new ReferenceExpressionParameter( component, key, template[ 0 ].rx, toBind ); + component.complexParameters.push( parameter ); + parameter.ready = true; + return parameter.value; + } + } + // We have a 'complex parameter' - we need to create a full-blown string + // fragment in order to evaluate and observe its value + parameter = new ComponentParameter( component, key, template ); + component.complexParameters.push( parameter ); + return parameter.value; + } + return __export; + }( types, parseJSON, resolveRef, ComponentParameter, ReferenceExpressionParameter ); - return mustache; - }; + /* virtualdom/items/Component/initialise/createInstance.js */ + var createInstance = function( log ) { - mustacheTypes = { - '#': SECTION, - '^': INVERTED, - '/': CLOSING, - '>': PARTIAL, - '!': COMMENT, - '&': INTERPOLATOR - }; - - getMustacheType = function ( tokenizer ) { - var type = mustacheTypes[ tokenizer.str.charAt( tokenizer.pos ) ]; - - if ( !type ) { - return null; + return function( component, Component, data, contentDescriptor ) { + var instance, parentFragment, partials, ractive; + parentFragment = component.parentFragment; + ractive = component.root; + // Make contents available as a {{>content}} partial + partials = { + content: contentDescriptor || [] + }; + if ( Component.defaults.el ) { + log.warn( { + debug: ractive.debug, + message: 'defaultElSpecified', + args: { + name: component.name + } + } ); } - - tokenizer.pos += 1; - return type; + instance = new Component( { + el: null, + append: true, + data: data, + partials: partials, + magic: ractive.magic || Component.defaults.magic, + modifyArrays: ractive.modifyArrays, + _parent: ractive, + _component: component, + // need to inherit runtime parent adaptors + adapt: ractive.adapt, + yield: { + template: contentDescriptor, + instance: ractive + } + } ); + return instance; }; + }( log ); - getIndexRef = getRegexMatcher( /^\s*:\s*([a-zA-Z_$][a-zA-Z_$0-9]*)/ ); - }()); + /* virtualdom/items/Component/initialise/createBindings.js */ + var createBindings = function( createComponentBinding ) { - - // tag - (function () { - var getOpeningTag, - getClosingTag, - getTagName, - getAttributes, - getAttribute, - getAttributeName, - getAttributeValue, - getUnquotedAttributeValue, - getUnquotedAttributeValueToken, - getUnquotedAttributeValueText, - getSingleQuotedAttributeValue, - getSingleQuotedStringToken, - getDoubleQuotedAttributeValue, - getDoubleQuotedStringToken; - - getTag = function ( tokenizer ) { - return ( getOpeningTag( tokenizer ) || getClosingTag( tokenizer ) ); + return function createInitialComponentBindings( component, toBind ) { + toBind.forEach( function createInitialComponentBinding( pair ) { + var childValue, parentValue; + createComponentBinding( component, component.root, pair.parentKeypath, pair.childKeypath ); + childValue = component.instance.viewmodel.get( pair.childKeypath ); + parentValue = component.root.viewmodel.get( pair.parentKeypath ); + if ( childValue !== undefined && parentValue === undefined ) { + component.root.viewmodel.set( pair.parentKeypath, childValue ); + } + } ); }; + }( createComponentBinding ); - getOpeningTag = function ( tokenizer ) { - var start, tag, attrs; + /* virtualdom/items/Component/initialise/propagateEvents.js */ + var propagateEvents = function( circular, fireEvent, log ) { - start = tokenizer.pos; - - if ( !getStringMatch( tokenizer, '<' ) ) { - return null; + var __export; + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + __export = function propagateEvents( component, eventsDescriptor ) { + var eventName; + for ( eventName in eventsDescriptor ) { + if ( eventsDescriptor.hasOwnProperty( eventName ) ) { + propagateEvent( component.instance, component.root, eventName, eventsDescriptor[ eventName ] ); + } } + }; - tag = { - type: TAG - }; - - // tag name - tag.name = getTagName( tokenizer ); - if ( !tag.name ) { - tokenizer.pos = start; - return null; + function propagateEvent( childInstance, parentInstance, eventName, proxyEventName ) { + if ( typeof proxyEventName !== 'string' ) { + log.error( { + debug: parentInstance.debug, + message: 'noComponentEventArguments' + } ); } + childInstance.on( eventName, function() { + var event, args; + // semi-weak test, but what else? tag the event obj ._isEvent ? + if ( arguments.length && arguments[ 0 ] && arguments[ 0 ].node ) { + event = Array.prototype.shift.call( arguments ); + } + args = Array.prototype.slice.call( arguments ); + fireEvent( parentInstance, proxyEventName, { + event: event, + args: args + } ); + // cancel bubbling + return false; + } ); + } + return __export; + }( circular, Ractive$shared_fireEvent, log ); - // attributes - attrs = getAttributes( tokenizer ); - if ( attrs ) { - tag.attrs = attrs; + /* virtualdom/items/Component/initialise/updateLiveQueries.js */ + var updateLiveQueries = function( component ) { + var ancestor, query; + // If there's a live query for this component type, add it + ancestor = component.root; + while ( ancestor ) { + if ( query = ancestor._liveComponentQueries[ '_' + component.name ] ) { + query.push( component.instance ); } + ancestor = ancestor._parent; + } + }; - // self-closing solidus? - if ( getStringMatch( tokenizer, '/' ) ) { - tag.selfClosing = true; - } + /* virtualdom/items/Component/prototype/init.js */ + var virtualdom_items_Component$init = function( types, warn, createModel, createInstance, createBindings, propagateEvents, updateLiveQueries ) { - // closing angle bracket - if ( !getStringMatch( tokenizer, '>' ) ) { - tokenizer.pos = start; - return null; + return function Component$init( options, Component ) { + var parentFragment, root, data, toBind; + parentFragment = this.parentFragment = options.parentFragment; + root = parentFragment.root; + this.root = root; + this.type = types.COMPONENT; + this.name = options.template.e; + this.index = options.index; + this.indexRefBindings = {}; + this.bindings = []; + // even though only one yielder is allowed, we need to have an array of them + // as it's possible to cause a yielder to be created before the last one + // was destroyed in the same turn of the runloop + this.yielders = []; + if ( !Component ) { + throw new Error( 'Component "' + this.name + '" not found' ); } - - return tag; + // First, we need to create a model for the component - e.g. if we + // encounter <widget foo='bar'/> then we need to create a widget + // with `data: { foo: 'bar' }`. + // + // This may involve setting up some bindings, but we can't do it + // yet so we take some notes instead + toBind = []; + data = createModel( this, Component.defaults.data || {}, options.template.a, toBind ); + createInstance( this, Component, data, options.template.f ); + createBindings( this, toBind ); + propagateEvents( this, options.template.v ); + // intro, outro and decorator directives have no effect + if ( options.template.t1 || options.template.t2 || options.template.o ) { + warn( 'The "intro", "outro" and "decorator" directives have no effect on components' ); + } + updateLiveQueries( this ); }; + }( types, warn, createModel, createInstance, createBindings, propagateEvents, updateLiveQueries ); - getClosingTag = function ( tokenizer ) { - var start, tag; + /* virtualdom/items/Component/prototype/rebind.js */ + var virtualdom_items_Component$rebind = function( runloop, getNewKeypath ) { - start = tokenizer.pos; - - if ( !getStringMatch( tokenizer, '<' ) ) { - return null; + return function Component$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + var childInstance = this.instance, + parentInstance = childInstance._parent, + indexRefAlias, query; + this.bindings.forEach( function( binding ) { + var updated; + if ( binding.root !== parentInstance ) { + return; + } + if ( updated = getNewKeypath( binding.keypath, oldKeypath, newKeypath ) ) { + binding.rebind( updated ); + } + } ); + this.complexParameters.forEach( rebind ); + if ( this.yielders[ 0 ] ) { + rebind( this.yielders[ 0 ] ); } - - tag = { type: TAG, closing: true }; - - // closing solidus - if ( !getStringMatch( tokenizer, '/' ) ) { - throw new Error( 'Unexpected character ' + tokenizer.remaining().charAt( 0 ) + ' (expected "/")' ); + if ( indexRefAlias = this.indexRefBindings[ indexRef ] ) { + runloop.addViewmodel( childInstance.viewmodel ); + childInstance.viewmodel.set( indexRefAlias, newIndex ); } - - // tag name - tag.name = getTagName( tokenizer ); - if ( !tag.name ) { - throw new Error( 'Unexpected character ' + tokenizer.remaining().charAt( 0 ) + ' (expected tag name)' ); + if ( query = this.root._liveComponentQueries[ '_' + this.name ] ) { + query._makeDirty(); } - // closing angle bracket - if ( !getStringMatch( tokenizer, '>' ) ) { - throw new Error( 'Unexpected character ' + tokenizer.remaining().charAt( 0 ) + ' (expected ">")' ); + function rebind( x ) { + x.rebind( indexRef, newIndex, oldKeypath, newKeypath ); } - - return tag; }; + }( runloop, getNewKeypath ); - getTagName = getRegexMatcher( /^[a-zA-Z][a-zA-Z0-9]*/ ); + /* virtualdom/items/Component/prototype/render.js */ + var virtualdom_items_Component$render = function Component$render() { + var instance = this.instance; + instance.render( this.parentFragment.getNode() ); + this.rendered = true; + return instance.fragment.detach(); + }; - getAttributes = function ( tokenizer ) { - var start, attrs, attr; + /* virtualdom/items/Component/prototype/toString.js */ + var virtualdom_items_Component$toString = function Component$toString() { + return this.instance.fragment.toString(); + }; - start = tokenizer.pos; + /* virtualdom/items/Component/prototype/unbind.js */ + var virtualdom_items_Component$unbind = function( Hook, removeFromArray ) { - allowWhitespace( tokenizer ); - - attr = getAttribute( tokenizer ); - - if ( !attr ) { - tokenizer.pos = start; - return null; + var __export; + var teardownHook = new Hook( 'teardown' ); + __export = function Component$unbind() { + var instance = this.instance; + this.complexParameters.forEach( unbind ); + this.bindings.forEach( unbind ); + removeFromLiveComponentQueries( this ); + // teardown the instance + instance.fragment.unbind(); + instance.viewmodel.teardown(); + if ( instance.fragment.rendered && instance.el.__ractive_instances__ ) { + removeFromArray( instance.el.__ractive_instances__, instance ); } - - attrs = []; - - while ( attr !== null ) { - attrs[ attrs.length ] = attr; - - allowWhitespace( tokenizer ); - attr = getAttribute( tokenizer ); - } - - return attrs; + teardownHook.fire( instance ); }; - getAttribute = function ( tokenizer ) { - var attr, name, value; + function unbind( thing ) { + thing.unbind(); + } - name = getAttributeName( tokenizer ); - if ( !name ) { - return null; - } + function removeFromLiveComponentQueries( component ) { + var instance, query; + instance = component.root; + do { + if ( query = instance._liveComponentQueries[ '_' + component.name ] ) { + query._remove( component ); + } + } while ( instance = instance._parent ); + } + return __export; + }( Ractive$shared_hooks_Hook, removeFromArray ); - attr = { - name: name - }; + /* virtualdom/items/Component/prototype/unrender.js */ + var virtualdom_items_Component$unrender = function Component$unrender( shouldDestroy ) { + this.shouldDestroy = shouldDestroy; + this.instance.unrender(); + }; - value = getAttributeValue( tokenizer ); - if ( value ) { - attr.value = value; - } + /* virtualdom/items/Component/_Component.js */ + var Component = function( detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, init, rebind, render, toString, unbind, unrender ) { - return attr; + var Component = function( options, Constructor ) { + this.init( options, Constructor ); }; + Component.prototype = { + detach: detach, + find: find, + findAll: findAll, + findAllComponents: findAllComponents, + findComponent: findComponent, + findNextNode: findNextNode, + firstNode: firstNode, + init: init, + rebind: rebind, + render: render, + toString: toString, + unbind: unbind, + unrender: unrender + }; + return Component; + }( virtualdom_items_Component$detach, virtualdom_items_Component$find, virtualdom_items_Component$findAll, virtualdom_items_Component$findAllComponents, virtualdom_items_Component$findComponent, virtualdom_items_Component$findNextNode, virtualdom_items_Component$firstNode, virtualdom_items_Component$init, virtualdom_items_Component$rebind, virtualdom_items_Component$render, virtualdom_items_Component$toString, virtualdom_items_Component$unbind, virtualdom_items_Component$unrender ); - getAttributeName = getRegexMatcher( /^[^\s"'>\/=]+/ ); + /* virtualdom/items/Comment.js */ + var Comment = function( types, detach ) { - - - getAttributeValue = function ( tokenizer ) { - var start, value; - - start = tokenizer.pos; - - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '=' ) ) { - tokenizer.pos = start; - return null; + var Comment = function( options ) { + this.type = types.COMMENT; + this.value = options.template.c; + }; + Comment.prototype = { + detach: detach, + firstNode: function() { + return this.node; + }, + render: function() { + if ( !this.node ) { + this.node = document.createComment( this.value ); + } + return this.node; + }, + toString: function() { + return '<!--' + this.value + '-->'; + }, + unrender: function( shouldDestroy ) { + if ( shouldDestroy ) { + this.node.parentNode.removeChild( this.node ); + } } + }; + return Comment; + }( types, detach ); - value = getSingleQuotedAttributeValue( tokenizer ) || getDoubleQuotedAttributeValue( tokenizer ) || getUnquotedAttributeValue( tokenizer ); + /* virtualdom/items/Yielder.js */ + var Yielder = function( runloop, removeFromArray, circular ) { - if ( value === null ) { - tokenizer.pos = start; - return null; + var Fragment; + circular.push( function() { + Fragment = circular.Fragment; + } ); + var Yielder = function( options ) { + var componentInstance, component; + componentInstance = options.parentFragment.root; + this.component = component = componentInstance.component; + this.surrogateParent = options.parentFragment; + this.parentFragment = component.parentFragment; + this.fragment = new Fragment( { + owner: this, + root: componentInstance.yield.instance, + template: componentInstance.yield.template, + pElement: this.surrogateParent.pElement + } ); + component.yielders.push( this ); + runloop.scheduleTask( function() { + if ( component.yielders.length > 1 ) { + throw new Error( 'A component template can only have one {{yield}} declaration at a time' ); + } + } ); + }; + Yielder.prototype = { + detach: function() { + return this.fragment.detach(); + }, + find: function( selector ) { + return this.fragment.find( selector ); + }, + findAll: function( selector, query ) { + return this.fragment.findAll( selector, query ); + }, + findComponent: function( selector ) { + return this.fragment.findComponent( selector ); + }, + findAllComponents: function( selector, query ) { + return this.fragment.findAllComponents( selector, query ); + }, + findNextNode: function() { + return this.surrogateParent.findNextNode( this ); + }, + firstNode: function() { + return this.fragment.firstNode(); + }, + getValue: function( options ) { + return this.fragment.getValue( options ); + }, + render: function() { + return this.fragment.render(); + }, + unbind: function() { + this.fragment.unbind(); + }, + unrender: function( shouldDestroy ) { + this.fragment.unrender( shouldDestroy ); + removeFromArray( this.component.yielders, this ); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + toString: function() { + return this.fragment.toString(); } - - return value; }; + return Yielder; + }( runloop, removeFromArray, circular ); - getUnquotedAttributeValueText = getRegexMatcher( /^[^\s"'=<>`]+/ ); + /* virtualdom/Fragment/prototype/init/createItem.js */ + var virtualdom_Fragment$init_createItem = function( types, Text, Interpolator, Section, Triple, Element, Partial, getComponent, Component, Comment, Yielder ) { - getUnquotedAttributeValueToken = function ( tokenizer ) { - var start, text, index; - - start = tokenizer.pos; - - text = getUnquotedAttributeValueText( tokenizer ); - - if ( !text ) { - return null; + return function createItem( options ) { + if ( typeof options.template === 'string' ) { + return new Text( options ); } - - if ( ( index = text.indexOf( tokenizer.delimiters[0] ) ) !== -1 ) { - text = text.substr( 0, index ); - tokenizer.pos = start + text.length; + switch ( options.template.t ) { + case types.INTERPOLATOR: + if ( options.template.r === 'yield' ) { + return new Yielder( options ); + } + return new Interpolator( options ); + case types.SECTION: + return new Section( options ); + case types.TRIPLE: + return new Triple( options ); + case types.ELEMENT: + var constructor; + if ( constructor = getComponent( options.parentFragment.root, options.template.e ) ) { + return new Component( options, constructor ); + } + return new Element( options ); + case types.PARTIAL: + return new Partial( options ); + case types.COMMENT: + return new Comment( options ); + default: + throw new Error( 'Something very strange happened. Please file an issue at https://github.com/ractivejs/ractive/issues. Thanks!' ); } - - return { - type: TEXT, - value: text - }; }; + }( types, Text, Interpolator, Section, Triple, Element, Partial, getComponent, Component, Comment, Yielder ); - getUnquotedAttributeValue = function ( tokenizer ) { - var tokens, token; + /* virtualdom/Fragment/prototype/init.js */ + var virtualdom_Fragment$init = function( types, create, createItem ) { - tokens = []; - - token = getMustache( tokenizer ) || getUnquotedAttributeValueToken( tokenizer ); - while ( token !== null ) { - tokens[ tokens.length ] = token; - token = getMustache( tokenizer ) || getUnquotedAttributeValueToken( tokenizer ); + return function Fragment$init( options ) { + var this$0 = this; + var parentFragment, parentRefs, ref; + // The item that owns this fragment - an element, section, partial, or attribute + this.owner = options.owner; + parentFragment = this.parent = this.owner.parentFragment; + // inherited properties + this.root = options.root; + this.pElement = options.pElement; + this.context = options.context; + // If parent item is a section, this may not be the only fragment + // that belongs to it - we need to make a note of the index + if ( this.owner.type === types.SECTION ) { + this.index = options.index; } - - if ( !tokens.length ) { - return null; + // index references (the 'i' in {{#section:i}}...{{/section}}) need to cascade + // down the tree + if ( parentFragment ) { + parentRefs = parentFragment.indexRefs; + if ( parentRefs ) { + this.indexRefs = create( null ); + // avoids need for hasOwnProperty + for ( ref in parentRefs ) { + this.indexRefs[ ref ] = parentRefs[ ref ]; + } + } } - - return tokens; + if ( options.indexRef ) { + if ( !this.indexRefs ) { + this.indexRefs = {}; + } + this.indexRefs[ options.indexRef ] = options.index; + } + // Time to create this fragment's child items + // TEMP should this be happening? + if ( typeof options.template === 'string' ) { + options.template = [ options.template ]; + } else if ( !options.template ) { + options.template = []; + } + this.items = options.template.map( function( template, i ) { + return createItem( { + parentFragment: this$0, + pElement: options.pElement, + template: template, + index: i + } ); + } ); + this.value = this.argsList = null; + this.dirtyArgs = this.dirtyValue = true; + this.bound = true; }; + }( types, create, virtualdom_Fragment$init_createItem ); + /* virtualdom/Fragment/prototype/rebind.js */ + var virtualdom_Fragment$rebind = function( assignNewKeypath ) { - getSingleQuotedStringToken = function ( tokenizer ) { - var start, text, index; - - start = tokenizer.pos; - - text = getSingleQuotedString( tokenizer ); - - if ( !text ) { - return null; + return function Fragment$rebind( indexRef, newIndex, oldKeypath, newKeypath ) { + this.index = newIndex; + // assign new context keypath if needed + assignNewKeypath( this, 'context', oldKeypath, newKeypath ); + if ( this.indexRefs && this.indexRefs[ indexRef ] !== undefined ) { + this.indexRefs[ indexRef ] = newIndex; } - - if ( ( index = text.indexOf( tokenizer.delimiters[0] ) ) !== -1 ) { - text = text.substr( 0, index ); - tokenizer.pos = start + text.length; - } - - return { - type: TEXT, - value: text - }; + this.items.forEach( function( item ) { + if ( item.rebind ) { + item.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + } + } ); }; + }( assignNewKeypath ); - getSingleQuotedAttributeValue = function ( tokenizer ) { - var start, tokens, token; + /* virtualdom/Fragment/prototype/render.js */ + var virtualdom_Fragment$render = function Fragment$render() { + var result; + if ( this.items.length === 1 ) { + result = this.items[ 0 ].render(); + } else { + result = document.createDocumentFragment(); + this.items.forEach( function( item ) { + result.appendChild( item.render() ); + } ); + } + this.rendered = true; + return result; + }; - start = tokenizer.pos; + /* virtualdom/Fragment/prototype/toString.js */ + var virtualdom_Fragment$toString = function Fragment$toString( escape ) { + if ( !this.items ) { + return ''; + } + return this.items.map( function( item ) { + return item.toString( escape ); + } ).join( '' ); + }; - if ( !getStringMatch( tokenizer, "'" ) ) { - return null; - } + /* virtualdom/Fragment/prototype/unbind.js */ + var virtualdom_Fragment$unbind = function() { - tokens = []; - - token = getMustache( tokenizer ) || getSingleQuotedStringToken( tokenizer ); - while ( token !== null ) { - tokens[ tokens.length ] = token; - token = getMustache( tokenizer ) || getSingleQuotedStringToken( tokenizer ); + var __export; + __export = function Fragment$unbind() { + if ( !this.bound ) { + return; } + this.items.forEach( unbindItem ); + this.bound = false; + }; - if ( !getStringMatch( tokenizer, "'" ) ) { - tokenizer.pos = start; - return null; + function unbindItem( item ) { + if ( item.unbind ) { + item.unbind(); } + } + return __export; + }(); - return tokens; + /* virtualdom/Fragment/prototype/unrender.js */ + var virtualdom_Fragment$unrender = function Fragment$unrender( shouldDestroy ) { + if ( !this.rendered ) { + throw new Error( 'Attempted to unrender a fragment that was not rendered' ); + } + this.items.forEach( function( i ) { + return i.unrender( shouldDestroy ); + } ); + this.rendered = false; + }; + /* virtualdom/Fragment.js */ + var Fragment = function( bubble, detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, getNode, getValue, init, rebind, render, toString, unbind, unrender, circular ) { + + var Fragment = function( options ) { + this.init( options ); }; + Fragment.prototype = { + bubble: bubble, + detach: detach, + find: find, + findAll: findAll, + findAllComponents: findAllComponents, + findComponent: findComponent, + findNextNode: findNextNode, + firstNode: firstNode, + getNode: getNode, + getValue: getValue, + init: init, + rebind: rebind, + render: render, + toString: toString, + unbind: unbind, + unrender: unrender + }; + circular.Fragment = Fragment; + return Fragment; + }( virtualdom_Fragment$bubble, virtualdom_Fragment$detach, virtualdom_Fragment$find, virtualdom_Fragment$findAll, virtualdom_Fragment$findAllComponents, virtualdom_Fragment$findComponent, virtualdom_Fragment$findNextNode, virtualdom_Fragment$firstNode, virtualdom_Fragment$getNode, virtualdom_Fragment$getValue, virtualdom_Fragment$init, virtualdom_Fragment$rebind, virtualdom_Fragment$render, virtualdom_Fragment$toString, virtualdom_Fragment$unbind, virtualdom_Fragment$unrender, circular ); - getDoubleQuotedStringToken = function ( tokenizer ) { - var start, text, index; + /* Ractive/prototype/reset.js */ + var Ractive$reset = function( Hook, runloop, Fragment, config ) { - start = tokenizer.pos; - - text = getDoubleQuotedString( tokenizer ); - - if ( !text ) { - return null; + var shouldRerender = [ + 'template', + 'partials', + 'components', + 'decorators', + 'events' + ], + resetHook = new Hook( 'reset' ); + return function Ractive$reset( data, callback ) { + var promise, wrapper, changes, i, rerender; + if ( typeof data === 'function' && !callback ) { + callback = data; + data = {}; + } else { + data = data || {}; } - - if ( ( index = text.indexOf( tokenizer.delimiters[0] ) ) !== -1 ) { - text = text.substr( 0, index ); - tokenizer.pos = start + text.length; + if ( typeof data !== 'object' ) { + throw new Error( 'The reset method takes either no arguments, or an object containing new data' ); } - - return { - type: TEXT, - value: text - }; - }; - - getDoubleQuotedAttributeValue = function ( tokenizer ) { - var start, tokens, token; - - start = tokenizer.pos; - - if ( !getStringMatch( tokenizer, '"' ) ) { - return null; + // If the root object is wrapped, try and use the wrapper's reset value + if ( ( wrapper = this.viewmodel.wrapped[ '' ] ) && wrapper.reset ) { + if ( wrapper.reset( data ) === false ) { + // reset was rejected, we need to replace the object + this.data = data; + } + } else { + this.data = data; } - - tokens = []; - - token = getMustache( tokenizer ) || getDoubleQuotedStringToken( tokenizer ); - while ( token !== null ) { - tokens[ tokens.length ] = token; - token = getMustache( tokenizer ) || getDoubleQuotedStringToken( tokenizer ); + // reset config items and track if need to rerender + changes = config.reset( this ); + i = changes.length; + while ( i-- ) { + if ( shouldRerender.indexOf( changes[ i ] ) > -1 ) { + rerender = true; + break; + } } - - if ( !getStringMatch( tokenizer, '"' ) ) { - tokenizer.pos = start; - return null; + if ( rerender ) { + var component; + this.viewmodel.mark( '' ); + // Is this is a component, we need to set the `shouldDestroy` + // flag, otherwise it will assume by default that a parent node + // will be detached, and therefore it doesn't need to bother + // detaching its own nodes + if ( component = this.component ) { + component.shouldDestroy = true; + } + this.unrender(); + if ( component ) { + component.shouldDestroy = false; + } + // If the template changed, we need to destroy the parallel DOM + // TODO if we're here, presumably it did? + if ( this.fragment.template !== this.template ) { + this.fragment.unbind(); + this.fragment = new Fragment( { + template: this.template, + root: this, + owner: this + } ); + } + promise = this.render( this.el, this.anchor ); + } else { + promise = runloop.start( this, true ); + this.viewmodel.mark( '' ); + runloop.end(); } + resetHook.fire( this, data ); + if ( callback ) { + promise.then( callback ); + } + return promise; + }; + }( Ractive$shared_hooks_Hook, runloop, Fragment, config ); - return tokens; + /* Ractive/prototype/resetTemplate.js */ + var Ractive$resetTemplate = function( config, Fragment ) { + return function Ractive$resetTemplate( template ) { + var transitionsEnabled, component; + config.template.init( null, this, { + template: template + } ); + transitionsEnabled = this.transitionsEnabled; + this.transitionsEnabled = false; + // Is this is a component, we need to set the `shouldDestroy` + // flag, otherwise it will assume by default that a parent node + // will be detached, and therefore it doesn't need to bother + // detaching its own nodes + if ( component = this.component ) { + component.shouldDestroy = true; + } + this.unrender(); + if ( component ) { + component.shouldDestroy = false; + } + // remove existing fragment and create new one + this.fragment.unbind(); + this.fragment = new Fragment( { + template: this.template, + root: this, + owner: this + } ); + this.render( this.el, this.anchor ); + this.transitionsEnabled = transitionsEnabled; }; - }()); + }( config, Fragment ); + /* Ractive/prototype/reverse.js */ + var Ractive$reverse = function( makeArrayMethod ) { - // text - (function () { - getText = function ( tokenizer ) { - var minIndex, text; + return makeArrayMethod( 'reverse' ); + }( Ractive$shared_makeArrayMethod ); - minIndex = tokenizer.str.length; + /* Ractive/prototype/set.js */ + var Ractive$set = function( runloop, isObject, normaliseKeypath, getMatchingKeypaths ) { - // anything goes except opening delimiters or a '<' - [ tokenizer.delimiters[0], tokenizer.tripleDelimiters[0], '<' ].forEach( function ( substr ) { - var index = tokenizer.str.indexOf( substr, tokenizer.pos ); - - if ( index !== -1 ) { - minIndex = Math.min( index, minIndex ); + var wildcard = /\*/; + return function Ractive$set( keypath, value, callback ) { + var this$0 = this; + var map, promise; + promise = runloop.start( this, true ); + // Set multiple keypaths in one go + if ( isObject( keypath ) ) { + map = keypath; + callback = value; + for ( keypath in map ) { + if ( map.hasOwnProperty( keypath ) ) { + value = map[ keypath ]; + keypath = normaliseKeypath( keypath ); + this.viewmodel.set( keypath, value ); + } } - }); - - if ( minIndex === tokenizer.pos ) { - return null; + } else { + keypath = normaliseKeypath( keypath ); + if ( wildcard.test( keypath ) ) { + getMatchingKeypaths( this, keypath ).forEach( function( keypath ) { + this$0.viewmodel.set( keypath, value ); + } ); + } else { + this.viewmodel.set( keypath, value ); + } } - - text = tokenizer.str.substring( tokenizer.pos, minIndex ); - tokenizer.pos = minIndex; - - return { - type: TEXT, - value: text - }; - + runloop.end(); + if ( callback ) { + promise.then( callback.bind( this ) ); + } + return promise; }; - }()); + }( runloop, isObject, normaliseKeypath, getMatchingKeypaths ); + /* Ractive/prototype/shift.js */ + var Ractive$shift = function( makeArrayMethod ) { - // expression - (function () { - var getExpressionList, - makePrefixSequenceMatcher, - makeInfixSequenceMatcher, - getRightToLeftSequenceMatcher, - getBracketedExpression, - getPrimary, - getMember, - getInvocation, - getTypeOf, - getLogicalOr, - getConditional, - - getDigits, - getExponent, - getFraction, - getInteger, - - getReference, - getRefinement, + return makeArrayMethod( 'shift' ); + }( Ractive$shared_makeArrayMethod ); - getLiteral, - getArrayLiteral, - getBooleanLiteral, - getNumberLiteral, - getStringLiteral, - getObjectLiteral, - getGlobal, + /* Ractive/prototype/sort.js */ + var Ractive$sort = function( makeArrayMethod ) { - getKeyValuePairs, - getKeyValuePair, - getKey, + return makeArrayMethod( 'sort' ); + }( Ractive$shared_makeArrayMethod ); - globals; + /* Ractive/prototype/splice.js */ + var Ractive$splice = function( makeArrayMethod ) { - getExpression = function ( tokenizer ) { + return makeArrayMethod( 'splice' ); + }( Ractive$shared_makeArrayMethod ); - var start, expression, fns, fn, i, len; + /* Ractive/prototype/subtract.js */ + var Ractive$subtract = function( add ) { - start = tokenizer.pos; - - // The conditional operator is the lowest precedence operator (except yield, - // assignment operators, and commas, none of which are supported), so we - // start there. If it doesn't match, it 'falls through' to progressively - // higher precedence operators, until it eventually matches (or fails to - // match) a 'primary' - a literal or a reference. This way, the abstract syntax - // tree has everything in its proper place, i.e. 2 + 3 * 4 === 14, not 20. - expression = getConditional( tokenizer ); - - return expression; + return function Ractive$subtract( keypath, d ) { + return add( this, keypath, d === undefined ? -1 : -d ); }; + }( Ractive$shared_add ); - getExpressionList = function ( tokenizer ) { - var start, expressions, expr, next; + /* Ractive/prototype/teardown.js */ + var Ractive$teardown = function( Hook, Promise, removeFromArray ) { - start = tokenizer.pos; - - allowWhitespace( tokenizer ); - - expr = getExpression( tokenizer ); - - if ( expr === null ) { - return null; + var teardownHook = new Hook( 'teardown' ); + // Teardown. This goes through the root fragment and all its children, removing observers + // and generally cleaning up after itself + return function Ractive$teardown( callback ) { + var promise; + this.fragment.unbind(); + this.viewmodel.teardown(); + if ( this.fragment.rendered && this.el.__ractive_instances__ ) { + removeFromArray( this.el.__ractive_instances__, this ); } + this.shouldDestroy = true; + promise = this.fragment.rendered ? this.unrender() : Promise.resolve(); + teardownHook.fire( this ); + if ( callback ) { + // TODO deprecate this? + promise.then( callback.bind( this ) ); + } + return promise; + }; + }( Ractive$shared_hooks_Hook, Promise, removeFromArray ); - expressions = [ expr ]; + /* Ractive/prototype/toggle.js */ + var Ractive$toggle = function( log ) { - // allow whitespace between expression and ',' - allowWhitespace( tokenizer ); - - if ( getStringMatch( tokenizer, ',' ) ) { - next = getExpressionList( tokenizer ); - if ( next === null ) { - tokenizer.pos = start; - return null; - } - - expressions = expressions.concat( next ); + return function Ractive$toggle( keypath, callback ) { + var value; + if ( typeof keypath !== 'string' ) { + log.errorOnly( { + debug: this.debug, + messsage: 'badArguments', + arg: { + arguments: keypath + } + } ); } - - return expressions; + value = this.get( keypath ); + return this.set( keypath, !value, callback ); }; + }( log ); - getBracketedExpression = function ( tokenizer ) { - var start, expr; + /* Ractive/prototype/toHTML.js */ + var Ractive$toHTML = function Ractive$toHTML() { + return this.fragment.toString( true ); + }; - start = tokenizer.pos; + /* Ractive/prototype/unrender.js */ + var Ractive$unrender = function( css, Hook, log, Promise, removeFromArray, runloop ) { - if ( !getStringMatch( tokenizer, '(' ) ) { - return null; + var unrenderHook = new Hook( 'unrender' ); + return function Ractive$unrender() { + var this$0 = this; + var promise, shouldDestroy; + if ( !this.fragment.rendered ) { + log.warn( { + debug: this.debug, + message: 'ractive.unrender() was called on a Ractive instance that was not rendered' + } ); + return Promise.resolve(); } - - allowWhitespace( tokenizer ); - - expr = getExpression( tokenizer ); - if ( !expr ) { - tokenizer.pos = start; - return null; + promise = runloop.start( this, true ); + // If this is a component, and the component isn't marked for destruction, + // don't detach nodes from the DOM unnecessarily + shouldDestroy = !this.component || this.component.shouldDestroy || this.shouldDestroy; + if ( this.constructor.css ) { + promise.then( function() { + css.remove( this$0.constructor ); + } ); } - - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, ')' ) ) { - tokenizer.pos = start; - return null; + // Cancel any animations in progress + while ( this._animations[ 0 ] ) { + this._animations[ 0 ].stop(); } - - return { - t: BRACKETED, - x: expr - }; + this.fragment.unrender( shouldDestroy ); + removeFromArray( this.el.__ractive_instances__, this ); + unrenderHook.fire( this ); + runloop.end(); + return promise; }; + }( global_css, Ractive$shared_hooks_Hook, log, Promise, removeFromArray, runloop ); - getPrimary = function ( tokenizer ) { - return getLiteral( tokenizer ) - || getReference( tokenizer ) - || getBracketedExpression( tokenizer ); - }; + /* Ractive/prototype/unshift.js */ + var Ractive$unshift = function( makeArrayMethod ) { - getMember = function ( tokenizer ) { - var start, expression, name, refinement, member; + return makeArrayMethod( 'unshift' ); + }( Ractive$shared_makeArrayMethod ); - expression = getPrimary( tokenizer ); - if ( !expression ) { - return null; - } + /* Ractive/prototype/update.js */ + var Ractive$update = function( Hook, runloop ) { - refinement = getRefinement( tokenizer ); - if ( !refinement ) { - return expression; + var updateHook = new Hook( 'update' ); + return function Ractive$update( keypath, callback ) { + var promise; + if ( typeof keypath === 'function' ) { + callback = keypath; + keypath = ''; + } else { + keypath = keypath || ''; } - - while ( refinement !== null ) { - member = { - t: MEMBER, - x: expression, - r: refinement - }; - - expression = member; - refinement = getRefinement( tokenizer ); + promise = runloop.start( this, true ); + this.viewmodel.mark( keypath ); + runloop.end(); + updateHook.fire( this, keypath ); + if ( callback ) { + promise.then( callback.bind( this ) ); } - - return member; + return promise; }; + }( Ractive$shared_hooks_Hook, runloop ); - getInvocation = function ( tokenizer ) { - var start, expression, expressionList, result; + /* Ractive/prototype/updateModel.js */ + var Ractive$updateModel = function( arrayContentsMatch, isEqual ) { - expression = getMember( tokenizer ); - if ( !expression ) { - return null; + var __export; + __export = function Ractive$updateModel( keypath, cascade ) { + var values; + if ( typeof keypath !== 'string' ) { + keypath = ''; + cascade = true; } + consolidateChangedValues( this, keypath, values = {}, cascade ); + return this.set( values ); + }; - start = tokenizer.pos; - - if ( !getStringMatch( tokenizer, '(' ) ) { - return expression; + function consolidateChangedValues( ractive, keypath, values, cascade ) { + var bindings, childDeps, i, binding, oldValue, newValue, checkboxGroups = []; + bindings = ractive._twowayBindings[ keypath ]; + if ( bindings && ( i = bindings.length ) ) { + while ( i-- ) { + binding = bindings[ i ]; + // special case - radio name bindings + if ( binding.radioName && !binding.element.node.checked ) { + continue; + } + // special case - checkbox name bindings come in groups, so + // we want to get the value once at most + if ( binding.checkboxName ) { + if ( !checkboxGroups[ binding.keypath ] && !binding.changed() ) { + checkboxGroups.push( binding.keypath ); + checkboxGroups[ binding.keypath ] = binding; + } + continue; + } + oldValue = binding.attribute.value; + newValue = binding.getValue(); + if ( arrayContentsMatch( oldValue, newValue ) ) { + continue; + } + if ( !isEqual( oldValue, newValue ) ) { + values[ keypath ] = newValue; + } + } } - - allowWhitespace( tokenizer ); - expressionList = getExpressionList( tokenizer ); - - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, ')' ) ) { - tokenizer.pos = start; - return expression; + // Handle groups of `<input type='checkbox' name='{{foo}}' ...>` + if ( checkboxGroups.length ) { + checkboxGroups.forEach( function( keypath ) { + var binding, oldValue, newValue; + binding = checkboxGroups[ keypath ]; + // one to represent the entire group + oldValue = binding.attribute.value; + newValue = binding.getValue(); + if ( !arrayContentsMatch( oldValue, newValue ) ) { + values[ keypath ] = newValue; + } + } ); } - - result = { - t: INVOCATION, - x: expression - }; - - if ( expressionList ) { - result.o = expressionList; + if ( !cascade ) { + return; } + // cascade + childDeps = ractive.viewmodel.depsMap[ 'default' ][ keypath ]; + if ( childDeps ) { + i = childDeps.length; + while ( i-- ) { + consolidateChangedValues( ractive, childDeps[ i ], values, cascade ); + } + } + } + return __export; + }( arrayContentsMatch, isEqual ); - return result; + /* Ractive/prototype.js */ + var prototype = function( add, animate, detach, find, findAll, findAllComponents, findComponent, fire, get, insert, merge, observe, off, on, pop, push, render, reset, resetTemplate, reverse, set, shift, sort, splice, subtract, teardown, toggle, toHTML, unrender, unshift, update, updateModel ) { + + return { + add: add, + animate: animate, + detach: detach, + find: find, + findAll: findAll, + findAllComponents: findAllComponents, + findComponent: findComponent, + fire: fire, + get: get, + insert: insert, + merge: merge, + observe: observe, + off: off, + on: on, + pop: pop, + push: push, + render: render, + reset: reset, + resetTemplate: resetTemplate, + reverse: reverse, + set: set, + shift: shift, + sort: sort, + splice: splice, + subtract: subtract, + teardown: teardown, + toggle: toggle, + toHTML: toHTML, + unrender: unrender, + unshift: unshift, + update: update, + updateModel: updateModel }; + }( Ractive$add, Ractive$animate, Ractive$detach, Ractive$find, Ractive$findAll, Ractive$findAllComponents, Ractive$findComponent, Ractive$fire, Ractive$get, Ractive$insert, Ractive$merge, Ractive$observe, Ractive$off, Ractive$on, Ractive$pop, Ractive$push, Ractive$render, Ractive$reset, Ractive$resetTemplate, Ractive$reverse, Ractive$set, Ractive$shift, Ractive$sort, Ractive$splice, Ractive$subtract, Ractive$teardown, Ractive$toggle, Ractive$toHTML, Ractive$unrender, Ractive$unshift, Ractive$update, Ractive$updateModel ); - // right-to-left - makePrefixSequenceMatcher = function ( symbol, fallthrough ) { - return function ( tokenizer ) { - var start, expression; + /* utils/getGuid.js */ + var getGuid = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function( c ) { + var r, v; + r = Math.random() * 16 | 0; + v = c == 'x' ? r : r & 3 | 8; + return v.toString( 16 ); + } ); + }; - if ( !getStringMatch( tokenizer, symbol ) ) { - return fallthrough( tokenizer ); - } + /* utils/getNextNumber.js */ + var getNextNumber = function() { - start = tokenizer.pos; + var i = 0; + return function() { + return 'r-' + i++; + }; + }(); - allowWhitespace( tokenizer ); + /* Ractive/prototype/shared/hooks/HookQueue.js */ + var Ractive$shared_hooks_HookQueue = function( Hook ) { - expression = getExpression( tokenizer ); - if ( !expression ) { - fail( tokenizer, 'an expression' ); + function HookQueue( event ) { + this.hook = new Hook( event ); + this.inProcess = {}; + this.queue = {}; + } + HookQueue.prototype = { + constructor: HookQueue, + begin: function( ractive ) { + this.inProcess[ ractive._guid ] = true; + }, + end: function( ractive ) { + var parent = ractive._parent; + // If this is *isn't* a child of a component that's in process, + // it should call methods or fire at this point + if ( !parent || !this.inProcess[ parent._guid ] ) { + fire( this, ractive ); + } else { + getChildQueue( this.queue, parent ).push( ractive ); } - - return { - s: symbol, - o: expression, - t: PREFIX_OPERATOR - }; - }; + delete this.inProcess[ ractive._guid ]; + } }; - // create all prefix sequence matchers - (function () { - var i, len, matcher, prefixOperators, fallthrough; + function getChildQueue( queue, ractive ) { + return queue[ ractive._guid ] || ( queue[ ractive._guid ] = [] ); + } - prefixOperators = '! ~ + - typeof'.split( ' ' ); - - // An invocation operator is higher precedence than logical-not - fallthrough = getInvocation; - for ( i=0, len=prefixOperators.length; i<len; i+=1 ) { - matcher = makePrefixSequenceMatcher( prefixOperators[i], fallthrough ); - fallthrough = matcher; + function fire( hookQueue, ractive ) { + var childQueue = getChildQueue( hookQueue.queue, ractive ); + hookQueue.hook.fire( ractive ); + // queue is "live" because components can end up being + // added while hooks fire on parents that modify data values. + while ( childQueue.length ) { + fire( hookQueue, childQueue.shift() ); } + delete hookQueue.queue[ ractive._guid ]; + } + return HookQueue; + }( Ractive$shared_hooks_Hook ); - // typeof operator is higher precedence than multiplication, so provides the - // fallthrough for the multiplication sequence matcher we're about to create - // (we're skipping void and delete) - getTypeOf = fallthrough; - }()); + /* viewmodel/prototype/get/arrayAdaptor/processWrapper.js */ + var viewmodel$get_arrayAdaptor_processWrapper = function( wrapper, array, methodName, newIndices ) { + var root = wrapper.root, + keypath = wrapper.keypath; + // If this is a sort or reverse, we just do root.set()... + // TODO use merge logic? + if ( methodName === 'sort' || methodName === 'reverse' ) { + root.viewmodel.set( keypath, array ); + return; + } + root.viewmodel.smartUpdate( keypath, array, newIndices ); + }; + /* viewmodel/prototype/get/arrayAdaptor/patch.js */ + var viewmodel$get_arrayAdaptor_patch = function( runloop, defineProperty, getNewIndices, processWrapper ) { - makeInfixSequenceMatcher = function ( symbol, fallthrough ) { - return function ( tokenizer ) { - var start, left, right; - - left = fallthrough( tokenizer ); - if ( !left ) { - return null; + var patchedArrayProto = [], + mutatorMethods = [ + 'pop', + 'push', + 'reverse', + 'shift', + 'sort', + 'splice', + 'unshift' + ], + testObj, patchArrayMethods, unpatchArrayMethods; + mutatorMethods.forEach( function( methodName ) { + var method = function() { + var SLICE$0 = Array.prototype.slice; + var args = SLICE$0.call( arguments, 0 ); + var newIndices, result, wrapper, i; + newIndices = getNewIndices( this, methodName, args ); + // apply the underlying method + result = Array.prototype[ methodName ].apply( this, arguments ); + // trigger changes + runloop.start(); + this._ractive.setting = true; + i = this._ractive.wrappers.length; + while ( i-- ) { + wrapper = this._ractive.wrappers[ i ]; + runloop.addViewmodel( wrapper.root.viewmodel ); + processWrapper( wrapper, this, methodName, newIndices ); } + runloop.end(); + this._ractive.setting = false; + return result; + }; + defineProperty( patchedArrayProto, 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 + patchArrayMethods = function( array ) { + array.__proto__ = patchedArrayProto; + }; + unpatchArrayMethods = function( array ) { + array.__proto__ = Array.prototype; + }; + } else { + // no, we can't + patchArrayMethods = function( array ) { + var i, methodName; + i = mutatorMethods.length; + while ( i-- ) { + methodName = mutatorMethods[ i ]; + defineProperty( array, methodName, { + value: patchedArrayProto[ methodName ], + configurable: true + } ); + } + }; + unpatchArrayMethods = function( array ) { + var i; + i = mutatorMethods.length; + while ( i-- ) { + delete array[ mutatorMethods[ i ] ]; + } + }; + } + patchArrayMethods.unpatch = unpatchArrayMethods; + return patchArrayMethods; + }( runloop, defineProperty, getNewIndices, viewmodel$get_arrayAdaptor_processWrapper ); - start = tokenizer.pos; + /* viewmodel/prototype/get/arrayAdaptor.js */ + var viewmodel$get_arrayAdaptor = function( defineProperty, isArray, patch ) { - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, symbol ) ) { - tokenizer.pos = start; - return left; + var arrayAdaptor, + // helpers + ArrayWrapper, errorMessage; + arrayAdaptor = { + filter: function( object ) { + // wrap the array if a) b) it's an array, and b) either it hasn't been wrapped already, + // or the array didn't trigger the get() itself + return isArray( object ) && ( !object._ractive || !object._ractive.setting ); + }, + wrap: function( ractive, array, keypath ) { + return new ArrayWrapper( ractive, array, keypath ); + } + }; + ArrayWrapper = function( ractive, array, keypath ) { + this.root = ractive; + this.value = array; + this.keypath = keypath; + // if this array hasn't already been ractified, ractify it + if ( !array._ractive ) { + // define a non-enumerable _ractive property to store the wrappers + defineProperty( array, '_ractive', { + value: { + wrappers: [], + instances: [], + setting: false + }, + configurable: true + } ); + patch( array ); + } + // store the ractive instance, so we can handle transitions later + if ( !array._ractive.instances[ ractive._guid ] ) { + array._ractive.instances[ ractive._guid ] = 0; + array._ractive.instances.push( ractive ); + } + array._ractive.instances[ ractive._guid ] += 1; + array._ractive.wrappers.push( this ); + }; + ArrayWrapper.prototype = { + get: function() { + return this.value; + }, + teardown: function() { + var array, storage, wrappers, instances, index; + array = this.value; + storage = array._ractive; + wrappers = storage.wrappers; + instances = storage.instances; + // if teardown() was invoked because we're clearing the cache as a result of + // a change that the array itself triggered, we can save ourselves the teardown + // and immediate setup + if ( storage.setting ) { + return false; } + index = wrappers.indexOf( this ); + if ( index === -1 ) { + throw new Error( errorMessage ); + } + wrappers.splice( index, 1 ); + // if nothing else depends on this array, we can revert it to its + // natural state + if ( !wrappers.length ) { + delete array._ractive; + patch.unpatch( this.value ); + } else { + // remove ractive instance if possible + instances[ this.root._guid ] -= 1; + if ( !instances[ this.root._guid ] ) { + index = instances.indexOf( this.root ); + if ( index === -1 ) { + throw new Error( errorMessage ); + } + instances.splice( index, 1 ); + } + } + } + }; + errorMessage = 'Something went wrong in a rather interesting way'; + return arrayAdaptor; + }( defineProperty, isArray, viewmodel$get_arrayAdaptor_patch ); - allowWhitespace( tokenizer ); + /* viewmodel/prototype/get/magicArrayAdaptor.js */ + var viewmodel$get_magicArrayAdaptor = function( magicAdaptor, arrayAdaptor ) { - right = getExpression( tokenizer ); - if ( !right ) { - tokenizer.pos = start; - return left; + var magicArrayAdaptor, MagicArrayWrapper; + if ( magicAdaptor ) { + magicArrayAdaptor = { + filter: function( object, keypath, ractive ) { + return magicAdaptor.filter( object, keypath, ractive ) && arrayAdaptor.filter( object ); + }, + wrap: function( ractive, array, keypath ) { + return new MagicArrayWrapper( ractive, array, keypath ); } - - return { - t: INFIX_OPERATOR, - s: symbol, - o: [ left, right ] - }; }; - }; + MagicArrayWrapper = function( ractive, array, keypath ) { + this.value = array; + this.magic = true; + this.magicWrapper = magicAdaptor.wrap( ractive, array, keypath ); + this.arrayWrapper = arrayAdaptor.wrap( ractive, array, keypath ); + }; + MagicArrayWrapper.prototype = { + get: function() { + return this.value; + }, + teardown: function() { + this.arrayWrapper.teardown(); + this.magicWrapper.teardown(); + }, + reset: function( value ) { + return this.magicWrapper.reset( value ); + } + }; + } + return magicArrayAdaptor; + }( viewmodel$get_magicAdaptor, viewmodel$get_arrayAdaptor ); - // create all infix sequence matchers - (function () { - var i, len, matcher, infixOperators, fallthrough; + /* viewmodel/prototype/adapt.js */ + var viewmodel$adapt = function( config, arrayAdaptor, log, magicAdaptor, magicArrayAdaptor ) { - // All the infix operators on order of precedence (source: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence) - // Each sequence matcher will initially fall through to its higher precedence - // neighbour, and only attempt to match if one of the higher precedence operators - // (or, ultimately, a literal, reference, or bracketed expression) already matched - infixOperators = '* / % + - << >> >>> < <= > >= in instanceof == != === !== & ^ | && ||'.split( ' ' ); - - // A typeof operator is higher precedence than multiplication - fallthrough = getTypeOf; - for ( i=0, len=infixOperators.length; i<len; i+=1 ) { - matcher = makeInfixSequenceMatcher( infixOperators[i], fallthrough ); - fallthrough = matcher; + var __export; + var prefixers = {}; + __export = function Viewmodel$adapt( keypath, value ) { + var ractive = this.ractive, + len, i, adaptor, wrapped; + // Do we have an adaptor for this value? + len = ractive.adapt.length; + for ( i = 0; i < len; i += 1 ) { + adaptor = ractive.adapt[ i ]; + // Adaptors can be specified as e.g. [ 'Backbone.Model', 'Backbone.Collection' ] - + // we need to get the actual adaptor if that's the case + if ( typeof adaptor === 'string' ) { + var found = config.registries.adaptors.find( ractive, adaptor ); + if ( !found ) { + // will throw. "return" for safety, if we downgrade :) + return log.critical( { + debug: ractive.debug, + message: 'missingPlugin', + args: { + plugin: 'adaptor', + name: adaptor + } + } ); + } + adaptor = ractive.adapt[ i ] = found; + } + if ( adaptor.filter( value, keypath, ractive ) ) { + wrapped = this.wrapped[ keypath ] = adaptor.wrap( ractive, value, keypath, getPrefixer( keypath ) ); + wrapped.value = value; + return value; + } } - - // Logical OR is the fallthrough for the conditional matcher - getLogicalOr = fallthrough; - }()); - - - // The conditional operator is the lowest precedence operator, so we start here - getConditional = function ( tokenizer ) { - var start, expression, ifTrue, ifFalse; - - expression = getLogicalOr( tokenizer ); - if ( !expression ) { - return null; + if ( ractive.magic ) { + if ( magicArrayAdaptor.filter( value, keypath, ractive ) ) { + this.wrapped[ keypath ] = magicArrayAdaptor.wrap( ractive, value, keypath ); + } else if ( magicAdaptor.filter( value, keypath, ractive ) ) { + this.wrapped[ keypath ] = magicAdaptor.wrap( ractive, value, keypath ); + } + } else if ( ractive.modifyArrays && arrayAdaptor.filter( value, keypath, ractive ) ) { + this.wrapped[ keypath ] = arrayAdaptor.wrap( ractive, value, keypath ); } + return value; + }; - start = tokenizer.pos; - - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '?' ) ) { - tokenizer.pos = start; - return expression; + function prefixKeypath( obj, prefix ) { + var prefixed = {}, + key; + if ( !prefix ) { + return obj; } - - allowWhitespace( tokenizer ); - - ifTrue = getExpression( tokenizer ); - if ( !ifTrue ) { - tokenizer.pos = start; - return expression; + prefix += '.'; + for ( key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + prefixed[ prefix + key ] = obj[ key ]; + } } + return prefixed; + } - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, ':' ) ) { - tokenizer.pos = start; - return expression; + function getPrefixer( rootKeypath ) { + var rootDot; + if ( !prefixers[ rootKeypath ] ) { + rootDot = rootKeypath ? rootKeypath + '.' : ''; + prefixers[ rootKeypath ] = function( relativeKeypath, value ) { + var obj; + if ( typeof relativeKeypath === 'string' ) { + obj = {}; + obj[ rootDot + relativeKeypath ] = value; + return obj; + } + if ( typeof relativeKeypath === 'object' ) { + // 'relativeKeypath' is in fact a hash, not a keypath + return rootDot ? prefixKeypath( relativeKeypath, rootKeypath ) : relativeKeypath; + } + }; } + return prefixers[ rootKeypath ]; + } + return __export; + }( config, viewmodel$get_arrayAdaptor, log, viewmodel$get_magicAdaptor, viewmodel$get_magicArrayAdaptor ); - allowWhitespace( tokenizer ); - - ifFalse = getExpression( tokenizer ); - if ( !ifFalse ) { - tokenizer.pos = start; - return expression; + /* viewmodel/helpers/getUpstreamChanges.js */ + var getUpstreamChanges = function getUpstreamChanges( changes ) { + var upstreamChanges = [ '' ], + i, keypath, keys, upstreamKeypath; + i = changes.length; + while ( i-- ) { + keypath = changes[ i ]; + keys = keypath.split( '.' ); + while ( keys.length > 1 ) { + keys.pop(); + upstreamKeypath = keys.join( '.' ); + if ( upstreamChanges.indexOf( upstreamKeypath ) === -1 ) { + upstreamChanges.push( upstreamKeypath ); + } } + } + return upstreamChanges; + }; - return { - t: CONDITIONAL, - o: [ expression, ifTrue, ifFalse ] + /* viewmodel/prototype/applyChanges/getPotentialWildcardMatches.js */ + var viewmodel$applyChanges_getPotentialWildcardMatches = function() { + + var __export; + var starMaps = {}; + // This function takes a keypath such as 'foo.bar.baz', and returns + // all the variants of that keypath that include a wildcard in place + // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. + // These are then checked against the dependants map (ractive.viewmodel.depsMap) + // to see if any pattern observers are downstream of one or more of + // these wildcard keypaths (e.g. 'foo.bar.*.status') + __export = function getPotentialWildcardMatches( keypath ) { + var keys, starMap, mapper, result; + keys = keypath.split( '.' ); + starMap = getStarMap( keys.length ); + mapper = function( star, i ) { + return star ? '*' : keys[ i ]; }; + result = starMap.map( function( mask ) { + return mask.map( mapper ).join( '.' ); + } ); + return result; }; - + // This function returns all the possible true/false combinations for + // a given number - e.g. for two, the possible combinations are + // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. + // It does so by getting all the binary values between 0 and e.g. 11 + function getStarMap( length ) { + var ones = '', + max, binary, starMap, mapper, i; + if ( !starMaps[ length ] ) { + starMap = []; + while ( ones.length < length ) { + ones += 1; + } + max = parseInt( ones, 2 ); + mapper = function( digit ) { + return digit === '1'; + }; + for ( i = 0; i <= max; i += 1 ) { + binary = i.toString( 2 ); + while ( binary.length < length ) { + binary = '0' + binary; + } + starMap[ i ] = Array.prototype.map.call( binary, mapper ); + } + starMaps[ length ] = starMap; + } + return starMaps[ length ]; + } + return __export; + }(); + /* viewmodel/prototype/applyChanges/notifyPatternObservers.js */ + var viewmodel$applyChanges_notifyPatternObservers = function( getPotentialWildcardMatches ) { - getDigits = getRegexMatcher( /^[0-9]+/ ); - getExponent = getRegexMatcher( /^[eE][\-+]?[0-9]+/ ); - getFraction = getRegexMatcher( /^\.[0-9]+/ ); - getInteger = getRegexMatcher( /^(0|[1-9][0-9]*)/ ); + var __export; + var lastKey = /[^\.]+$/; + __export = notifyPatternObservers; + function notifyPatternObservers( viewmodel, keypath, onlyDirect ) { + var potentialWildcardMatches; + updateMatchingPatternObservers( viewmodel, keypath ); + if ( onlyDirect ) { + return; + } + potentialWildcardMatches = getPotentialWildcardMatches( keypath ); + potentialWildcardMatches.forEach( function( upstreamPattern ) { + cascade( viewmodel, upstreamPattern, keypath ); + } ); + } - getReference = function ( tokenizer ) { - var startPos, name, dot, combo, refinement, lastDotIndex; + function cascade( viewmodel, upstreamPattern, keypath ) { + var group, map, actualChildKeypath; + group = viewmodel.depsMap.patternObservers; + map = group[ upstreamPattern ]; + if ( map ) { + map.forEach( function( childKeypath ) { + var key = lastKey.exec( childKeypath )[ 0 ]; + // 'baz' + actualChildKeypath = keypath ? keypath + '.' + key : key; + // 'foo.bar.baz' + updateMatchingPatternObservers( viewmodel, actualChildKeypath ); + cascade( viewmodel, childKeypath, actualChildKeypath ); + } ); + } + } - startPos = tokenizer.pos; + function updateMatchingPatternObservers( viewmodel, keypath ) { + viewmodel.patternObservers.forEach( function( observer ) { + if ( observer.regex.test( keypath ) ) { + observer.update( keypath ); + } + } ); + } + return __export; + }( viewmodel$applyChanges_getPotentialWildcardMatches ); - // could be an implicit iterator ('.'), a prefixed reference ('.name') or a - // standard reference ('name') - dot = getStringMatch( tokenizer, '.' ) || ''; - name = getName( tokenizer ) || ''; + /* viewmodel/prototype/applyChanges.js */ + var viewmodel$applyChanges = function( getUpstreamChanges, notifyPatternObservers ) { - combo = dot + name; - - if ( !combo ) { - return null; + var __export; + __export = function Viewmodel$applyChanges() { + var this$0 = this; + var self = this, + changes, upstreamChanges, hash = {}; + changes = this.changes; + if ( !changes.length ) { + // TODO we end up here on initial render. Perhaps we shouldn't? + return; } - while ( refinement = getDotRefinement( tokenizer ) || getArrayRefinement( tokenizer ) ) { - combo += refinement; + function cascade( keypath ) { + var map, dependants, keys; + if ( self.noCascade.hasOwnProperty( keypath ) ) { + return; + } + if ( dependants = self.deps.computed[ keypath ] ) { + dependants.forEach( invalidate ); + keys = dependants.map( getKey ); + keys.forEach( mark ); + keys.forEach( cascade ); + } + if ( map = self.depsMap.computed[ keypath ] ) { + map.forEach( cascade ); + } } - if ( getStringMatch( tokenizer, '(' ) ) { - - // if this is a method invocation (as opposed to a function) we need - // to strip the method name from the reference combo, else the context - // will be wrong - lastDotIndex = combo.lastIndexOf( '.' ); - if ( lastDotIndex !== -1 ) { - combo = combo.substr( 0, lastDotIndex ); - tokenizer.pos = startPos + combo.length; - } else { - tokenizer.pos -= 1; + function mark( keypath ) { + self.mark( keypath ); + } + changes.forEach( cascade ); + upstreamChanges = getUpstreamChanges( changes ); + upstreamChanges.forEach( function( keypath ) { + var dependants, keys; + if ( dependants = self.deps.computed[ keypath ] ) { + dependants.forEach( invalidate ); + keys = dependants.map( getKey ); + keys.forEach( mark ); + keys.forEach( cascade ); } + } ); + this.changes = []; + // Pattern observers are a weird special case + if ( this.patternObservers.length ) { + upstreamChanges.forEach( function( keypath ) { + return notifyPatternObservers( this$0, keypath, true ); + } ); + changes.forEach( function( keypath ) { + return notifyPatternObservers( this$0, keypath ); + } ); } - - return { - t: REFERENCE, - n: combo - }; + if ( this.deps.observers ) { + upstreamChanges.forEach( function( keypath ) { + return notifyUpstreamDependants( this$0, null, keypath, 'observers' ); + } ); + notifyAllDependants( this, changes, 'observers' ); + } + if ( this.deps[ 'default' ] ) { + var bindings = []; + upstreamChanges.forEach( function( keypath ) { + return notifyUpstreamDependants( this$0, bindings, keypath, 'default' ); + } ); + if ( bindings.length ) { + notifyBindings( this, bindings, changes ); + } + notifyAllDependants( this, changes, 'default' ); + } + // Return a hash of keypaths to updated values + changes.forEach( function( keypath ) { + hash[ keypath ] = this$0.get( keypath ); + } ); + this.implicitChanges = {}; + this.noCascade = {}; + return hash; }; - getRefinement = function ( tokenizer ) { - var start, refinement, name, expr; + function invalidate( computation ) { + computation.invalidate(); + } - start = tokenizer.pos; + function getKey( computation ) { + return computation.key; + } - allowWhitespace( tokenizer ); + function notifyUpstreamDependants( viewmodel, bindings, keypath, groupName ) { + var dependants, value; + if ( dependants = findDependants( viewmodel, keypath, groupName ) ) { + value = viewmodel.get( keypath ); + dependants.forEach( function( d ) { + // don't "set" the parent value, refine it + // i.e. not data = value, but data[foo] = fooValue + if ( bindings && d.refineValue ) { + bindings.push( d ); + } else { + d.setValue( value ); + } + } ); + } + } - // "." name - if ( getStringMatch( tokenizer, '.' ) ) { - allowWhitespace( tokenizer ); - - if ( name = getName( tokenizer ) ) { - return { - t: REFINEMENT, - n: name - }; + function notifyBindings( viewmodel, bindings, changes ) { + bindings.forEach( function( binding ) { + var useSet = false, + i = 0, + length = changes.length, + refinements = []; + while ( i < length ) { + var keypath = changes[ i ]; + if ( keypath === binding.keypath ) { + useSet = true; + break; + } + if ( keypath.slice( 0, binding.keypath.length ) === binding.keypath ) { + refinements.push( keypath ); + } + i++; } + if ( useSet ) { + binding.setValue( viewmodel.get( binding.keypath ) ); + } + if ( refinements.length ) { + binding.refineValue( refinements ); + } + } ); + } - fail( tokenizer, 'a property name' ); + function notifyAllDependants( viewmodel, keypaths, groupName ) { + var queue = []; + addKeypaths( keypaths ); + queue.forEach( dispatch ); + + function addKeypaths( keypaths ) { + keypaths.forEach( addKeypath ); + keypaths.forEach( cascade ); } - // "[" expression "]" - if ( getStringMatch( tokenizer, '[' ) ) { - allowWhitespace( tokenizer ); - - expr = getExpression( tokenizer ); - if ( !expr ) { - fail( tokenizer, 'an expression' ); + function addKeypath( keypath ) { + var deps = findDependants( viewmodel, keypath, groupName ); + if ( deps ) { + queue.push( { + keypath: keypath, + deps: deps + } ); } + } - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, ']' ) ) { - fail( tokenizer, '"]"' ); + function cascade( keypath ) { + var childDeps; + if ( childDeps = viewmodel.depsMap[ groupName ][ keypath ] ) { + addKeypaths( childDeps ); } + } - return { - t: REFINEMENT, - x: expr - }; + function dispatch( set ) { + var value = viewmodel.get( set.keypath ); + set.deps.forEach( function( d ) { + return d.setValue( value ); + } ); } + } - return null; - }; + function findDependants( viewmodel, keypath, groupName ) { + var group = viewmodel.deps[ groupName ]; + return group ? group[ keypath ] : null; + } + return __export; + }( getUpstreamChanges, viewmodel$applyChanges_notifyPatternObservers ); - // Any literal except function and regexp literals, which aren't supported (yet?) - getLiteral = function ( tokenizer ) { - var literal = getNumberLiteral( tokenizer ) || - getBooleanLiteral( tokenizer ) || - getGlobal( tokenizer ) || - getStringLiteral( tokenizer ) || - getObjectLiteral( tokenizer ) || - getArrayLiteral( tokenizer ); + /* viewmodel/prototype/capture.js */ + var viewmodel$capture = function Viewmodel$capture() { + this.captureGroups.push( [] ); + }; - return literal; - }; - - getArrayLiteral = function ( tokenizer ) { - var start, array, expressionList; - - start = tokenizer.pos; - - // allow whitespace before '[' - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '[' ) ) { - tokenizer.pos = start; - return null; + /* viewmodel/prototype/clearCache.js */ + var viewmodel$clearCache = function Viewmodel$clearCache( keypath, dontTeardownWrapper ) { + var cacheMap, wrapper; + if ( !dontTeardownWrapper ) { + // Is there a wrapped property at this keypath? + if ( wrapper = this.wrapped[ keypath ] ) { + // Did we unwrap it? + if ( wrapper.teardown() !== false ) { + // Is this right? + // What's the meaning of returning false from teardown? + // Could there be a GC ramification if this is a "real" ractive.teardown()? + this.wrapped[ keypath ] = null; + } } - - expressionList = getExpressionList( tokenizer ); - - if ( !getStringMatch( tokenizer, ']' ) ) { - tokenizer.pos = start; - return null; + } + this.cache[ keypath ] = undefined; + if ( cacheMap = this.cacheMap[ keypath ] ) { + while ( cacheMap.length ) { + this.clearCache( cacheMap.pop() ); } + } + }; - return { - t: ARRAY_LITERAL, - o: expressionList - }; - }; + /* viewmodel/Computation/getComputationSignature.js */ + var getComputationSignature = function() { - getBooleanLiteral = function ( tokenizer ) { - var remaining = tokenizer.remaining(); - - if ( remaining.substr( 0, 4 ) === 'true' ) { - tokenizer.pos += 4; + var __export; + var pattern = /\$\{([^\}]+)\}/g; + __export = function( signature ) { + if ( typeof signature === 'function' ) { return { - t: BOOLEAN_LITERAL, - v: 'true' + get: signature }; } - - if ( remaining.substr( 0, 5 ) === 'false' ) { - tokenizer.pos += 5; + if ( typeof signature === 'string' ) { return { - t: BOOLEAN_LITERAL, - v: 'false' + get: createFunctionFromString( signature ) }; } - - return null; - }; - - globals = /^(?:Array|Date|RegExp|decodeURIComponent|decodeURI|encodeURIComponent|encodeURI|isFinite|isNaN|parseFloat|parseInt|JSON|Math|NaN|undefined|null)/; - - // Not strictly literals, but we can treat them as such because they - // never need to be dereferenced. - - // Allowed globals: - // ---------------- - // - // Array, Date, RegExp, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, isFinite, isNaN, parseFloat, parseInt, JSON, Math, NaN, undefined, null - getGlobal = function ( tokenizer ) { - var start, name, match, global; - - start = tokenizer.pos; - name = getName( tokenizer ); - - if ( !name ) { - return null; - } - - match = globals.exec( name ); - if ( match ) { - tokenizer.pos = start + match[0].length; - return { - t: GLOBAL, - v: match[0] + if ( typeof signature === 'object' && typeof signature.get === 'string' ) { + signature = { + get: createFunctionFromString( signature.get ), + set: signature.set }; } - - tokenizer.pos = start; - return null; + return signature; }; - getNumberLiteral = function ( tokenizer ) { - var start, result; + function createFunctionFromString( signature ) { + var functionBody = 'var __ractive=this;return(' + signature.replace( pattern, function( match, keypath ) { + return '__ractive.get("' + keypath + '")'; + } ) + ')'; + return new Function( functionBody ); + } + return __export; + }(); - start = tokenizer.pos; + /* viewmodel/Computation/Computation.js */ + var Computation = function( log, isEqual ) { - // special case - we may have a decimal without a literal zero (because - // some programmers are plonkers) - if ( result = getFraction( tokenizer ) ) { - return { - t: NUMBER_LITERAL, - v: result - }; + var Computation = function( ractive, key, signature ) { + var this$0 = this; + this.ractive = ractive; + this.viewmodel = ractive.viewmodel; + this.key = key; + this.getter = signature.get; + this.setter = signature.set; + this.hardDeps = signature.deps || []; + this.softDeps = []; + this.depValues = {}; + if ( this.hardDeps ) { + this.hardDeps.forEach( function( d ) { + return ractive.viewmodel.register( d, this$0, 'computed' ); + } ); } - - result = getInteger( tokenizer ); - if ( result === null ) { - return null; + this._dirty = this._firstRun = true; + }; + Computation.prototype = { + constructor: Computation, + init: function() { + var initial; + this.bypass = true; + initial = this.ractive.viewmodel.get( this.key ); + this.ractive.viewmodel.clearCache( this.key ); + this.bypass = false; + if ( this.setter && initial !== undefined ) { + this.set( initial ); + } + }, + invalidate: function() { + this._dirty = true; + }, + get: function() { + var this$0 = this; + var ractive, newDeps, dependenciesChanged, dependencyValuesChanged = false; + if ( this.getting ) { + // prevent double-computation (e.g. caused by array mutation inside computation) + return; + } + this.getting = true; + if ( this._dirty ) { + ractive = this.ractive; + // determine whether the inputs have changed, in case this depends on + // other computed values + if ( this._firstRun || !this.hardDeps.length && !this.softDeps.length ) { + dependencyValuesChanged = true; + } else { + [ + this.hardDeps, + this.softDeps + ].forEach( function( deps ) { + var keypath, value, i; + if ( dependencyValuesChanged ) { + return; + } + i = deps.length; + while ( i-- ) { + keypath = deps[ i ]; + value = ractive.viewmodel.get( keypath ); + if ( !isEqual( value, this$0.depValues[ keypath ] ) ) { + this$0.depValues[ keypath ] = value; + dependencyValuesChanged = true; + return; + } + } + } ); + } + if ( dependencyValuesChanged ) { + ractive.viewmodel.capture(); + try { + this.value = this.getter.call( ractive ); + } catch ( err ) { + log.warn( { + debug: ractive.debug, + message: 'failedComputation', + args: { + key: this.key, + err: err.message || err + } + } ); + this.value = void 0; + } + newDeps = ractive.viewmodel.release(); + dependenciesChanged = this.updateDependencies( newDeps ); + if ( dependenciesChanged ) { + [ + this.hardDeps, + this.softDeps + ].forEach( function( deps ) { + deps.forEach( function( keypath ) { + this$0.depValues[ keypath ] = ractive.viewmodel.get( keypath ); + } ); + } ); + } + } + this._dirty = false; + } + this.getting = this._firstRun = false; + return this.value; + }, + set: function( value ) { + if ( this.setting ) { + this.value = value; + return; + } + if ( !this.setter ) { + throw new Error( 'Computed properties without setters are read-only. (This may change in a future version of Ractive!)' ); + } + this.setter.call( this.ractive, value ); + }, + updateDependencies: function( newDeps ) { + var i, oldDeps, keypath, dependenciesChanged; + oldDeps = this.softDeps; + // remove dependencies that are no longer used + i = oldDeps.length; + while ( i-- ) { + keypath = oldDeps[ i ]; + if ( newDeps.indexOf( keypath ) === -1 ) { + dependenciesChanged = true; + this.viewmodel.unregister( keypath, this, 'computed' ); + } + } + // create references for any new dependencies + i = newDeps.length; + while ( i-- ) { + keypath = newDeps[ i ]; + if ( oldDeps.indexOf( keypath ) === -1 && ( !this.hardDeps || this.hardDeps.indexOf( keypath ) === -1 ) ) { + dependenciesChanged = true; + this.viewmodel.register( keypath, this, 'computed' ); + } + } + if ( dependenciesChanged ) { + this.softDeps = newDeps.slice(); + } + return dependenciesChanged; } + }; + return Computation; + }( log, isEqual ); - result += getFraction( tokenizer ) || ''; - result += getExponent( tokenizer ) || ''; + /* viewmodel/prototype/compute.js */ + var viewmodel$compute = function( getComputationSignature, Computation ) { - return { - t: NUMBER_LITERAL, - v: result - }; + return function Viewmodel$compute( key, signature ) { + signature = getComputationSignature( signature ); + return this.computations[ key ] = new Computation( this.ractive, key, signature ); }; + }( getComputationSignature, Computation ); - getObjectLiteral = function ( tokenizer ) { - var start, pairs, keyValuePairs, i, pair; + /* viewmodel/prototype/get/FAILED_LOOKUP.js */ + var viewmodel$get_FAILED_LOOKUP = { + FAILED_LOOKUP: true + }; - start = tokenizer.pos; + /* viewmodel/prototype/get/UnresolvedImplicitDependency.js */ + var viewmodel$get_UnresolvedImplicitDependency = function( removeFromArray, runloop ) { - // allow whitespace - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '{' ) ) { - tokenizer.pos = start; - return null; + var empty = {}; + var UnresolvedImplicitDependency = function( viewmodel, keypath ) { + this.viewmodel = viewmodel; + this.root = viewmodel.ractive; + // TODO eliminate this + this.ref = keypath; + this.parentFragment = empty; + viewmodel.unresolvedImplicitDependencies[ keypath ] = true; + viewmodel.unresolvedImplicitDependencies.push( this ); + runloop.addUnresolved( this ); + }; + UnresolvedImplicitDependency.prototype = { + resolve: function() { + this.viewmodel.mark( this.ref ); + this.viewmodel.unresolvedImplicitDependencies[ this.ref ] = false; + removeFromArray( this.viewmodel.unresolvedImplicitDependencies, this ); + }, + teardown: function() { + runloop.removeUnresolved( this ); } + }; + return UnresolvedImplicitDependency; + }( removeFromArray, runloop ); - keyValuePairs = getKeyValuePairs( tokenizer ); + /* viewmodel/prototype/get.js */ + var viewmodel$get = function( isNumeric, FAILED_LOOKUP, UnresolvedImplicitDependency ) { - // allow whitespace between final value and '}' - allowWhitespace( tokenizer ); - - if ( !getStringMatch( tokenizer, '}' ) ) { - tokenizer.pos = start; - return null; + var __export; + var empty = {}; + __export = function Viewmodel$get( keypath ) { + var options = arguments[ 1 ]; + if ( options === void 0 ) + options = empty; + var ractive = this.ractive, + cache = this.cache, + value, computation, wrapped, captureGroup; + if ( keypath[ 0 ] === '@' ) { + value = keypath.slice( 1 ); + return isNumeric( value ) ? +value : value; } - - return { - t: OBJECT_LITERAL, - m: keyValuePairs - }; + if ( cache[ keypath ] === undefined ) { + // Is this a computed property? + if ( ( computation = this.computations[ keypath ] ) && !computation.bypass ) { + value = computation.get(); + this.adapt( keypath, value ); + } else if ( wrapped = this.wrapped[ keypath ] ) { + value = wrapped.value; + } else if ( !keypath ) { + this.adapt( '', ractive.data ); + value = ractive.data; + } else { + value = retrieve( this, keypath ); + } + cache[ keypath ] = value; + } else { + value = cache[ keypath ]; + } + if ( options.evaluateWrapped && ( wrapped = this.wrapped[ keypath ] ) ) { + value = wrapped.get(); + } + // capture the keypath, if we're inside a computation + if ( options.capture && ( captureGroup = this.captureGroups[ this.captureGroups.length - 1 ] ) ) { + if ( !~captureGroup.indexOf( keypath ) ) { + captureGroup.push( keypath ); + // if we couldn't resolve the keypath, we need to make it as a failed + // lookup, so that the computation updates correctly once we CAN + // resolve the keypath + if ( value === FAILED_LOOKUP && this.unresolvedImplicitDependencies[ keypath ] !== true ) { + new UnresolvedImplicitDependency( this, keypath ); + } + } + } + return value === FAILED_LOOKUP ? void 0 : value; }; - getKeyValuePairs = function ( tokenizer ) { - var start, pairs, pair, keyValuePairs; + function retrieve( viewmodel, keypath ) { + var keys, key, parentKeypath, parentValue, cacheMap, value, wrapped; + keys = keypath.split( '.' ); + key = keys.pop(); + parentKeypath = keys.join( '.' ); + parentValue = viewmodel.get( parentKeypath ); + if ( wrapped = viewmodel.wrapped[ parentKeypath ] ) { + parentValue = wrapped.get(); + } + if ( parentValue === null || parentValue === undefined ) { + return; + } + // update cache map + if ( !( cacheMap = viewmodel.cacheMap[ parentKeypath ] ) ) { + viewmodel.cacheMap[ parentKeypath ] = [ keypath ]; + } else { + if ( cacheMap.indexOf( keypath ) === -1 ) { + cacheMap.push( keypath ); + } + } + // If this property doesn't exist, we return a sentinel value + // so that we know to query parent scope (if such there be) + if ( typeof parentValue === 'object' && !( key in parentValue ) ) { + return viewmodel.cache[ keypath ] = FAILED_LOOKUP; + } + value = parentValue[ key ]; + // Do we have an adaptor for this value? + viewmodel.adapt( keypath, value, false ); + // Update cache + viewmodel.cache[ keypath ] = value; + return value; + } + return __export; + }( isNumeric, viewmodel$get_FAILED_LOOKUP, viewmodel$get_UnresolvedImplicitDependency ); - start = tokenizer.pos; + /* viewmodel/prototype/init.js */ + var viewmodel$init = function() { - pair = getKeyValuePair( tokenizer ); - if ( pair === null ) { - return null; + var __export; + __export = function Viewmodel$init() { + var key, computation, computations = []; + for ( key in this.ractive.computed ) { + computation = this.compute( key, this.ractive.computed[ key ] ); + computations.push( computation ); } + computations.forEach( init ); + }; - pairs = [ pair ]; + function init( computation ) { + computation.init(); + } + return __export; + }(); - if ( getStringMatch( tokenizer, ',' ) ) { - keyValuePairs = getKeyValuePairs( tokenizer ); + /* viewmodel/prototype/mark.js */ + var viewmodel$mark = function Viewmodel$mark( keypath, options ) { + var computation; + // implicit changes (i.e. `foo.length` on `ractive.push('foo',42)`) + // should not be picked up by pattern observers + if ( options ) { + if ( options.implicit ) { + this.implicitChanges[ keypath ] = true; + } + if ( options.noCascade ) { + this.noCascade[ keypath ] = true; + } + } + if ( computation = this.computations[ keypath ] ) { + computation.invalidate(); + } + if ( this.changes.indexOf( keypath ) === -1 ) { + this.changes.push( keypath ); + } + this.clearCache( keypath ); + }; - if ( !keyValuePairs ) { - tokenizer.pos = start; - return null; + /* viewmodel/prototype/merge/mapOldToNewIndex.js */ + var viewmodel$merge_mapOldToNewIndex = function( oldArray, newArray ) { + var usedIndices, firstUnusedIndex, newIndices, changed; + usedIndices = {}; + firstUnusedIndex = 0; + newIndices = oldArray.map( function( item, i ) { + var index, start, len; + start = firstUnusedIndex; + len = newArray.length; + do { + index = newArray.indexOf( item, start ); + if ( index === -1 ) { + changed = true; + return -1; } - - return pairs.concat( keyValuePairs ); + start = index + 1; + } while ( usedIndices[ index ] && start < len ); + // keep track of the first unused index, so we don't search + // the whole of newArray for each item in oldArray unnecessarily + if ( index === firstUnusedIndex ) { + firstUnusedIndex += 1; } + if ( index !== i ) { + changed = true; + } + usedIndices[ index ] = true; + return index; + } ); + return newIndices; + }; - return pairs; - }; + /* viewmodel/prototype/merge.js */ + var viewmodel$merge = function( warn, mapOldToNewIndex ) { - getKeyValuePair = function ( tokenizer ) { - var start, pair, key, value; - - start = tokenizer.pos; - - // allow whitespace between '{' and key - allowWhitespace( tokenizer ); - - key = getKey( tokenizer ); - if ( key === null ) { - tokenizer.pos = start; - return null; + var __export; + var comparators = {}; + __export = function Viewmodel$merge( keypath, currentArray, array, options ) { + var oldArray, newArray, comparator, newIndices; + this.mark( keypath ); + if ( options && options.compare ) { + comparator = getComparatorFunction( options.compare ); + try { + oldArray = currentArray.map( comparator ); + newArray = array.map( comparator ); + } catch ( err ) { + // fallback to an identity check - worst case scenario we have + // to do more DOM manipulation than we thought... + // ...unless we're in debug mode of course + if ( this.debug ) { + throw err; + } else { + warn( 'Merge operation: comparison failed. Falling back to identity checking' ); + } + oldArray = currentArray; + newArray = array; + } + } else { + oldArray = currentArray; + newArray = array; } + // find new indices for members of oldArray + newIndices = mapOldToNewIndex( oldArray, newArray ); + this.smartUpdate( keypath, array, newIndices, currentArray.length !== array.length ); + }; - // allow whitespace between key and ':' - allowWhitespace( tokenizer ); + function stringify( item ) { + return JSON.stringify( item ); + } - // next character must be ':' - if ( !getStringMatch( tokenizer, ':' ) ) { - tokenizer.pos = start; - return null; + function getComparatorFunction( comparator ) { + // If `compare` is `true`, we use JSON.stringify to compare + // objects that are the same shape, but non-identical - i.e. + // { foo: 'bar' } !== { foo: 'bar' } + if ( comparator === true ) { + return stringify; } + if ( typeof comparator === 'string' ) { + if ( !comparators[ comparator ] ) { + comparators[ comparator ] = function( item ) { + return item[ comparator ]; + }; + } + return comparators[ comparator ]; + } + if ( typeof comparator === 'function' ) { + return comparator; + } + throw new Error( 'The `compare` option must be a function, or a string representing an identifying field (or `true` to use JSON.stringify)' ); + } + return __export; + }( warn, viewmodel$merge_mapOldToNewIndex ); - // allow whitespace between ':' and value - allowWhitespace( tokenizer ); + /* viewmodel/prototype/register.js */ + var viewmodel$register = function() { - // next expression must be a, well... expression - value = getExpression( tokenizer ); - if ( value === null ) { - tokenizer.pos = start; - return null; + var __export; + __export = function Viewmodel$register( keypath, dependant ) { + var group = arguments[ 2 ]; + if ( group === void 0 ) + group = 'default'; + var depsByKeypath, deps; + if ( dependant.isStatic ) { + return; } - - return { - t: KEY_VALUE_PAIR, - k: key, - v: value - }; + depsByKeypath = this.deps[ group ] || ( this.deps[ group ] = {} ); + deps = depsByKeypath[ keypath ] || ( depsByKeypath[ keypath ] = [] ); + deps.push( dependant ); + if ( !keypath ) { + return; + } + updateDependantsMap( this, keypath, group ); }; - // http://mathiasbynens.be/notes/javascript-properties - // can be any name, string literal, or number literal - getKey = function ( tokenizer ) { - return getName( tokenizer ) || getStringLiteral( tokenizer ) || getNumberLiteral( tokenizer ); - }; + function updateDependantsMap( viewmodel, keypath, group ) { + var keys, parentKeypath, map, parent; + // update dependants map + keys = keypath.split( '.' ); + while ( keys.length ) { + keys.pop(); + parentKeypath = keys.join( '.' ); + map = viewmodel.depsMap[ group ] || ( viewmodel.depsMap[ group ] = {} ); + parent = map[ parentKeypath ] || ( map[ parentKeypath ] = [] ); + if ( parent[ keypath ] === undefined ) { + parent[ keypath ] = 0; + parent.push( keypath ); + } + parent[ keypath ] += 1; + keypath = parentKeypath; + } + } + return __export; + }(); - getStringLiteral = function ( tokenizer ) { - var start, string; + /* viewmodel/prototype/release.js */ + var viewmodel$release = function Viewmodel$release() { + return this.captureGroups.pop(); + }; - start = tokenizer.pos; + /* viewmodel/prototype/set.js */ + var viewmodel$set = function( isEqual, createBranch ) { - if ( getStringMatch( tokenizer, '"' ) ) { - string = getDoubleQuotedString( tokenizer ); - - if ( !getStringMatch( tokenizer, '"' ) ) { - tokenizer.pos = start; - return null; + var __export; + __export = function Viewmodel$set( keypath, value, silent ) { + var computation, wrapper, dontTeardownWrapper; + computation = this.computations[ keypath ]; + if ( computation ) { + if ( computation.setting ) { + // let the other computation set() handle things... + return; } - - return { - t: STRING_LITERAL, - v: string - }; + computation.set( value ); + value = computation.get(); } - - if ( getStringMatch( tokenizer, "'" ) ) { - string = getSingleQuotedString( tokenizer ); - - if ( !getStringMatch( tokenizer, "'" ) ) { - tokenizer.pos = start; - return null; + if ( isEqual( this.cache[ keypath ], value ) ) { + return; + } + wrapper = this.wrapped[ keypath ]; + // If we have a wrapper with a `reset()` method, we try and use it. If the + // `reset()` method returns false, the wrapper should be torn down, and + // (most likely) a new one should be created later + if ( wrapper && wrapper.reset ) { + dontTeardownWrapper = wrapper.reset( value ) !== false; + if ( dontTeardownWrapper ) { + value = wrapper.get(); } - - return { - t: STRING_LITERAL, - v: string - }; } - - return null; + if ( !computation && !dontTeardownWrapper ) { + resolveSet( this, keypath, value ); + } + if ( !silent ) { + this.mark( keypath ); + } else { + // We're setting a parent of the original target keypath (i.e. + // creating a fresh branch) - we need to clear the cache, but + // not mark it as a change + this.clearCache( keypath ); + } }; - - }()); - -}()); -// Ractive.parse -// =============== -// -// Takes in a string, and returns an object representing the parsed template. -// A parsed template is an array of 1 or more 'descriptors', which in some -// cases have children. -// -// The format is optimised for size, not readability, however for reference the -// keys for each descriptor are as follows: -// -// * r - Reference, e.g. 'mustache' in {{mustache}} -// * t - Type code (e.g. 1 is text, 2 is interpolator...) -// * f - Fragment. Contains a descriptor's children -// * e - Element name -// * a - map of element Attributes, or proxy event/transition Arguments -// * d - Dynamic proxy event/transition arguments -// * n - indicates an iNverted section -// * i - Index reference, e.g. 'num' in {{#section:num}}content{{/section}} -// * v - eVent proxies (i.e. when user e.g. clicks on a node, fire proxy event) -// * c - Conditionals (e.g. ['yes', 'no'] in {{condition ? yes : no}}) -// * x - eXpressions -// * t1 - intro Transition -// * t2 - outro Transition - -(function () { - - var onlyWhitespace, inlinePartialStart, inlinePartialEnd, parseCompoundTemplate; - - onlyWhitespace = /^\s*$/; - - inlinePartialStart = /<!--\s*\{\{\s*>\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*}\}\s*-->/; - inlinePartialEnd = /<!--\s*\{\{\s*\/\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*}\}\s*-->/; - - parse = function ( template, options ) { - var tokens, fragmentStub, json, token; - - options = options || {}; - - // does this template include inline partials? - if ( inlinePartialStart.test( template ) ) { - return parseCompoundTemplate( template, options ); - } - - - if ( options.sanitize === true ) { - options.sanitize = { - // blacklist from https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/lang/html/html4-elements-whitelist.json - elements: 'applet base basefont body frame frameset head html isindex link meta noframes noscript object param script style title'.split( ' ' ), - eventAttributes: true + function resolveSet( viewmodel, keypath, value ) { + var keys, lastKey, parentKeypath, wrapper, parentValue, wrapperSet, valueSet; + wrapperSet = function() { + if ( wrapper.set ) { + wrapper.set( lastKey, value ); + } else { + parentValue = wrapper.get(); + valueSet(); + } }; + valueSet = function() { + if ( !parentValue ) { + parentValue = createBranch( lastKey ); + viewmodel.set( parentKeypath, parentValue, true ); + } + parentValue[ lastKey ] = value; + }; + keys = keypath.split( '.' ); + lastKey = keys.pop(); + parentKeypath = keys.join( '.' ); + wrapper = viewmodel.wrapped[ parentKeypath ]; + if ( wrapper ) { + wrapperSet(); + } else { + parentValue = viewmodel.get( parentKeypath ); + // may have been wrapped via the above .get() + // call on viewmodel if this is first access via .set()! + if ( wrapper = viewmodel.wrapped[ parentKeypath ] ) { + wrapperSet(); + } else { + valueSet(); + } + } } + return __export; + }( isEqual, createBranch ); - tokens = tokenize( template, options ); + /* viewmodel/prototype/smartUpdate.js */ + var viewmodel$smartUpdate = function() { - if ( !options.preserveWhitespace ) { - // remove first token if it only contains whitespace - token = tokens[0]; - if ( token && ( token.type === TEXT ) && onlyWhitespace.test( token.value ) ) { - tokens.shift(); + var __export; + var implicitOption = { + implicit: true + }, + noCascadeOption = { + noCascade: true + }; + __export = function Viewmodel$smartUpdate( keypath, array, newIndices ) { + var this$0 = this; + var dependants, oldLength; + oldLength = newIndices.length; + // Indices that are being removed should be marked as dirty + newIndices.forEach( function( newIndex, oldIndex ) { + if ( newIndex === -1 ) { + this$0.mark( keypath + '.' + oldIndex, noCascadeOption ); + } + } ); + // Update the model + // TODO allow existing array to be updated in place, rather than replaced? + this.set( keypath, array, true ); + if ( dependants = this.deps[ 'default' ][ keypath ] ) { + dependants.filter( canShuffle ).forEach( function( d ) { + return d.shuffle( newIndices, array ); + } ); } - - // ditto last token - token = tokens[ tokens.length - 1 ]; - if ( token && ( token.type === TEXT ) && onlyWhitespace.test( token.value ) ) { - tokens.pop(); + if ( oldLength !== array.length ) { + this.mark( keypath + '.length', implicitOption ); + for ( var i = oldLength; i < array.length; i += 1 ) { + this.mark( keypath + '.' + i ); + } + // don't allow removed indexes beyond end of new array to trigger recomputations + for ( var i$0 = array.length; i$0 < oldLength; i$0 += 1 ) { + this.mark( keypath + '.' + i$0, noCascadeOption ); + } } - } - - fragmentStub = getFragmentStubFromTokens( tokens, options, options.preserveWhitespace ); - - json = fragmentStub.toJson(); + }; - if ( typeof json === 'string' ) { - // If we return it as a string, Ractive will attempt to reparse it! - // Instead we wrap it in an array. Ractive knows what to do then - return [ json ]; + function canShuffle( dependant ) { + return typeof dependant.shuffle === 'function'; } + return __export; + }(); - return json; + /* viewmodel/prototype/teardown.js */ + var viewmodel$teardown = function Viewmodel$teardown() { + var this$0 = this; + var unresolvedImplicitDependency; + // Clear entire cache - this has the desired side-effect + // of unwrapping adapted values (e.g. arrays) + Object.keys( this.cache ).forEach( function( keypath ) { + return this$0.clearCache( keypath ); + } ); + // Teardown any failed lookups - we don't need them to resolve any more + while ( unresolvedImplicitDependency = this.unresolvedImplicitDependencies.pop() ) { + unresolvedImplicitDependency.teardown(); + } }; - - parseCompoundTemplate = function ( template, options ) { - var mainTemplate, remaining, partials, name, startMatch, endMatch; + /* viewmodel/prototype/unregister.js */ + var viewmodel$unregister = function() { - partials = {}; + var __export; + __export = function Viewmodel$unregister( keypath, dependant ) { + var group = arguments[ 2 ]; + if ( group === void 0 ) + group = 'default'; + var deps, index; + if ( dependant.isStatic ) { + return; + } + deps = this.deps[ group ][ keypath ]; + index = deps.indexOf( dependant ); + if ( index === -1 ) { + throw new Error( 'Attempted to remove a dependant that was no longer registered! This should not happen. If you are seeing this bug in development please raise an issue at https://github.com/RactiveJS/Ractive/issues - thanks' ); + } + deps.splice( index, 1 ); + if ( !keypath ) { + return; + } + updateDependantsMap( this, keypath, group ); + }; - mainTemplate = ''; - remaining = template; + function updateDependantsMap( viewmodel, keypath, group ) { + var keys, parentKeypath, map, parent; + // update dependants map + keys = keypath.split( '.' ); + while ( keys.length ) { + keys.pop(); + parentKeypath = keys.join( '.' ); + map = viewmodel.depsMap[ group ]; + parent = map[ parentKeypath ]; + parent[ keypath ] -= 1; + if ( !parent[ keypath ] ) { + // remove from parent deps map + parent.splice( parent.indexOf( keypath ), 1 ); + parent[ keypath ] = undefined; + } + keypath = parentKeypath; + } + } + return __export; + }(); - while ( startMatch = inlinePartialStart.exec( remaining ) ) { - name = startMatch[1]; + /* viewmodel/adaptConfig.js */ + var adaptConfig = function() { - mainTemplate += remaining.substr( 0, startMatch.index ); - remaining = remaining.substring( startMatch.index + startMatch[0].length ); + // should this be combined with prototype/adapt.js? + var configure = { + lookup: function( target, adaptors ) { + var i, adapt = target.adapt; + if ( !adapt || !adapt.length ) { + return adapt; + } + if ( adaptors && Object.keys( adaptors ).length && ( i = adapt.length ) ) { + while ( i-- ) { + var adaptor = adapt[ i ]; + if ( typeof adaptor === 'string' ) { + adapt[ i ] = adaptors[ adaptor ] || adaptor; + } + } + } + return adapt; + }, + combine: function( parent, adapt ) { + // normalize 'Foo' to [ 'Foo' ] + parent = arrayIfString( parent ); + adapt = arrayIfString( adapt ); + // no parent? return adapt + if ( !parent || !parent.length ) { + return adapt; + } + // no adapt? return 'copy' of parent + if ( !adapt || !adapt.length ) { + return parent.slice(); + } + // add parent adaptors to options + parent.forEach( function( a ) { + // don't put in duplicates + if ( adapt.indexOf( a ) === -1 ) { + adapt.push( a ); + } + } ); + return adapt; + } + }; - endMatch = inlinePartialEnd.exec( remaining ); - - if ( !endMatch || endMatch[1] !== name ) { - throw new Error( 'Inline partials must have a closing delimiter, and cannot be nested' ); + function arrayIfString( adapt ) { + if ( typeof adapt === 'string' ) { + adapt = [ adapt ]; } + return adapt; + } + return configure; + }(); - partials[ name ] = parse( remaining.substr( 0, endMatch.index ), options ); + /* viewmodel/Viewmodel.js */ + var Viewmodel = function( create, adapt, applyChanges, capture, clearCache, compute, get, init, mark, merge, register, release, set, smartUpdate, teardown, unregister, adaptConfig ) { - remaining = remaining.substring( endMatch.index + endMatch[0].length ); + var noMagic; + try { + Object.defineProperty( {}, 'test', { + value: 0 + } ); + } catch ( err ) { + noMagic = true; } - - return { - template: parse( mainTemplate, options ), - partials: partials + var Viewmodel = function( ractive ) { + this.ractive = ractive; + // TODO eventually, we shouldn't need this reference + Viewmodel.extend( ractive.constructor, ractive ); + this.cache = {}; + // we need to be able to use hasOwnProperty, so can't inherit from null + this.cacheMap = create( null ); + this.deps = { + computed: {}, + 'default': {} + }; + this.depsMap = { + computed: {}, + 'default': {} + }; + this.patternObservers = []; + this.wrapped = create( null ); + this.computations = create( null ); + this.captureGroups = []; + this.unresolvedImplicitDependencies = []; + this.changes = []; + this.implicitChanges = {}; + this.noCascade = {}; }; - }; + Viewmodel.extend = function( Parent, instance ) { + if ( instance.magic && noMagic ) { + throw new Error( 'Getters and setters (magic mode) are not supported in this browser' ); + } + instance.adapt = adaptConfig.combine( Parent.prototype.adapt, instance.adapt ) || []; + instance.adapt = adaptConfig.lookup( instance, instance.adaptors ); + }; + Viewmodel.prototype = { + adapt: adapt, + applyChanges: applyChanges, + capture: capture, + clearCache: clearCache, + compute: compute, + get: get, + init: init, + mark: mark, + merge: merge, + register: register, + release: release, + set: set, + smartUpdate: smartUpdate, + teardown: teardown, + unregister: unregister + }; + return Viewmodel; + }( create, viewmodel$adapt, viewmodel$applyChanges, viewmodel$capture, viewmodel$clearCache, viewmodel$compute, viewmodel$get, viewmodel$init, viewmodel$mark, viewmodel$merge, viewmodel$register, viewmodel$release, viewmodel$set, viewmodel$smartUpdate, viewmodel$teardown, viewmodel$unregister, adaptConfig ); -}()); -tokenize = function ( template, options ) { - var tokenizer, tokens, token, last20, next20; + /* Ractive/initialise.js */ + var Ractive_initialise = function( config, create, Fragment, getElement, getNextNumber, Hook, HookQueue, Viewmodel ) { - options = options || {}; + var __export; + var constructHook = new Hook( 'construct' ), + configHook = new Hook( 'config' ), + initHook = new HookQueue( 'init' ); + __export = function initialiseRactiveInstance( ractive ) { + var options = arguments[ 1 ]; + if ( options === void 0 ) + options = {}; + var el; + initialiseProperties( ractive, options ); + // make this option do what would be expected if someone + // did include it on a new Ractive() or new Component() call. + // Silly to do so (put a hook on the very options being used), + // but handle it correctly, consistent with the intent. + constructHook.fire( config.getConstructTarget( ractive, options ), options ); + // init config from Parent and options + config.init( ractive.constructor, ractive, options ); + configHook.fire( ractive ); + // Teardown any existing instances *before* trying to set up the new one - + // avoids certain weird bugs + if ( el = getElement( ractive.el ) ) { + if ( !ractive.append ) { + if ( el.__ractive_instances__ ) { + try { + el.__ractive_instances__.splice( 0, el.__ractive_instances__.length ).forEach( function( r ) { + return r.teardown(); + } ); + } catch ( err ) {} + } + el.innerHTML = ''; + } + } + initHook.begin( ractive ); + // TEMPORARY. This is so we can implement Viewmodel gradually + ractive.viewmodel = new Viewmodel( ractive ); + // hacky circular problem until we get this sorted out + // if viewmodel immediately processes computed properties, + // they may call ractive.get, which calls ractive.viewmodel, + // which hasn't been set till line above finishes. + ractive.viewmodel.init(); + // Render our *root fragment* + if ( ractive.template ) { + ractive.fragment = new Fragment( { + template: ractive.template, + root: ractive, + owner: ractive + } ); + } + initHook.end( ractive ); + // render automatically ( if `el` is specified ) + if ( el ) { + ractive.render( el, ractive.append ); + } + }; - tokenizer = { - str: stripHtmlComments( template ), - pos: 0, - delimiters: options.delimiters || [ '{{', '}}' ], - tripleDelimiters: options.tripleDelimiters || [ '{{{', '}}}' ], - remaining: function () { - return tokenizer.str.substring( tokenizer.pos ); + function initialiseProperties( ractive, options ) { + // Generate a unique identifier, for places where you'd use a weak map if it + // existed + ractive._guid = getNextNumber(); + // events + ractive._subs = create( null ); + // storage for item configuration from instantiation to reset, + // like dynamic functions or original values + ractive._config = {}; + // two-way bindings + ractive._twowayBindings = create( null ); + // animations (so we can stop any in progress at teardown) + ractive._animations = []; + // nodes registry + ractive.nodes = {}; + // live queries + ractive._liveQueries = []; + ractive._liveComponentQueries = []; + // If this is a component, store a reference to the parent + if ( options._parent && options._component ) { + ractive._parent = options._parent; + ractive.component = options._component; + // And store a reference to the instance on the component + options._component.instance = ractive; + } } - }; + return __export; + }( config, create, Fragment, getElement, getNextNumber, Ractive$shared_hooks_Hook, Ractive$shared_hooks_HookQueue, Viewmodel ); - tokens = []; + /* extend/unwrapExtended.js */ + var unwrapExtended = function( wrap, config, circular ) { - while ( tokenizer.pos < tokenizer.str.length ) { - token = getToken( tokenizer ); - - if ( token === null && tokenizer.remaining() ) { - last20 = tokenizer.str.substr( 0, tokenizer.pos ).substr( -20 ); - if ( last20.length === 20 ) { - last20 = '...' + last20; + var __export; + var Ractive; + circular.push( function() { + Ractive = circular.Ractive; + } ); + __export = function unwrapExtended( Child ) { + if ( !( Child.prototype instanceof Ractive ) ) { + return Child; } - - next20 = tokenizer.remaining().substr( 0, 20 ); - if ( next20.length === 20 ) { - next20 = next20 + '...'; + var options = {}; + while ( Child ) { + config.registries.forEach( function( r ) { + addRegistry( r.useDefaults ? Child.prototype : Child, options, r.name ); + } ); + Object.keys( Child.prototype ).forEach( function( key ) { + if ( key === 'computed' ) { + return; + } + var value = Child.prototype[ key ]; + if ( !( key in options ) ) { + options[ key ] = value._method ? value._method : value; + } else if ( typeof options[ key ] === 'function' && typeof value === 'function' && options[ key ]._method ) { + var result, needsSuper = value._method; + if ( needsSuper ) { + value = value._method; + } + // rewrap bound directly to parent fn + result = wrap( options[ key ]._method, value ); + if ( needsSuper ) { + result._method = result; + } + options[ key ] = result; + } + } ); + if ( Child._parent !== Ractive ) { + Child = Child._parent; + } else { + Child = false; + } } + return options; + }; - throw new Error( 'Could not parse template: ' + ( last20 ? last20 + '<- ' : '' ) + 'failed at character ' + tokenizer.pos + ' ->' + next20 ); + function addRegistry( target, options, name ) { + var registry, keys = Object.keys( target[ name ] ); + if ( !keys.length ) { + return; + } + if ( !( registry = options[ name ] ) ) { + registry = options[ name ] = {}; + } + keys.filter( function( key ) { + return !( key in registry ); + } ).forEach( function( key ) { + return registry[ key ] = target[ name ][ key ]; + } ); } + return __export; + }( wrapMethod, config, circular ); - tokens[ tokens.length ] = token; - } + /* extend/_extend.js */ + var Ractive_extend = function( create, defineProperties, getGuid, config, initialise, Viewmodel, unwrap ) { - stripStandalones( tokens ); - stripCommentTokens( tokens ); - - return tokens; -}; -Ractive.prototype = proto; + return function extend() { + var options = arguments[ 0 ]; + if ( options === void 0 ) + options = {}; + var Parent = this, + Child, proto, staticProperties; + // if we're extending with another Ractive instance, inherit its + // prototype methods and default options as well + options = unwrap( options ); + // create Child constructor + Child = function( options ) { + initialise( this, options ); + }; + proto = create( Parent.prototype ); + proto.constructor = Child; + staticProperties = { + // each component needs a guid, for managing CSS etc + _guid: { + value: getGuid() + }, + // alias prototype as defaults + defaults: { + value: proto + }, + // extendable + extend: { + value: extend, + writable: true, + configurable: true + }, + // Parent - for IE8, can't use Object.getPrototypeOf + _parent: { + value: Parent + } + }; + defineProperties( Child, staticProperties ); + // extend configuration + config.extend( Parent, proto, options ); + Viewmodel.extend( Parent, proto ); + Child.prototype = proto; + return Child; + }; + }( create, defineProperties, getGuid, config, Ractive_initialise, Viewmodel, unwrapExtended ); -Ractive.adaptors = adaptors; -Ractive.eventDefinitions = eventDefinitions; -Ractive.partials = {}; + /* Ractive.js */ + var Ractive = function( defaults, easing, interpolators, svg, magic, defineProperties, proto, Promise, extendObj, extend, parse, initialise, circular ) { -Ractive.easing = easing; -Ractive.extend = extend; -Ractive.interpolate = interpolate; -Ractive.interpolators = interpolators; -Ractive.parse = parse; + var Ractive, properties; + // Main Ractive required object + Ractive = function( options ) { + initialise( this, options ); + }; + // Ractive properties + properties = { + // static methods: + extend: { + value: extend + }, + parse: { + value: parse + }, + // Namespaced constructors + Promise: { + value: Promise + }, + // support + svg: { + value: svg + }, + magic: { + value: magic + }, + // version + VERSION: { + value: '0.6.1' + }, + // Plugins + adaptors: { + writable: true, + value: {} + }, + components: { + writable: true, + value: {} + }, + decorators: { + writable: true, + value: {} + }, + easing: { + writable: true, + value: easing + }, + events: { + writable: true, + value: {} + }, + interpolators: { + writable: true, + value: interpolators + }, + partials: { + writable: true, + value: {} + }, + transitions: { + writable: true, + value: {} + } + }; + // Ractive properties + defineProperties( Ractive, properties ); + Ractive.prototype = extendObj( proto, defaults ); + Ractive.prototype.constructor = Ractive; + // alias prototype as defaults + Ractive.defaults = Ractive.prototype; + // Certain modules have circular dependencies. If we were bundling a + // module loader, e.g. almond.js, this wouldn't be a problem, but we're + // not - we're using amdclean as part of the build process. Because of + // this, we need to wait until all modules have loaded before those + // circular dependencies can be required. + circular.Ractive = Ractive; + while ( circular.length ) { + circular.pop()(); + } + // Ractive.js makes liberal use of things like Array.prototype.indexOf. In + // older browsers, these are made available via a shim - here, we do a quick + // pre-flight check to make sure that either a) we're not in a shit browser, + // or b) we're using a Ractive-legacy.js build + var FUNCTION = 'function'; + if ( typeof Date.now !== FUNCTION || typeof String.prototype.trim !== FUNCTION || typeof Object.keys !== FUNCTION || typeof Array.prototype.indexOf !== FUNCTION || typeof Array.prototype.forEach !== FUNCTION || typeof Array.prototype.map !== FUNCTION || typeof Array.prototype.filter !== FUNCTION || typeof window !== 'undefined' && typeof window.addEventListener !== FUNCTION ) { + throw new Error( 'It looks like you\'re attempting to use Ractive.js in an older browser. You\'ll need to use one of the \'legacy builds\' in order to continue - see http://docs.ractivejs.org/latest/legacy-builds for more information.' ); + } + return Ractive; + }( options, easing, interpolators, svg, magic, defineProperties, prototype, Promise, extend, Ractive_extend, parse, Ractive_initialise, circular ); -// TODO add some more transitions -Ractive.transitions = transitions; -Ractive.VERSION = VERSION; + // export as Common JS module... + if ( typeof module !== "undefined" && module.exports ) { + module.exports = Ractive; + } + // ... or as AMD module + else if ( typeof define === "function" && define.amd ) { + define( function() { + return Ractive; + } ); + } -// export as Common JS module... -if ( typeof module !== "undefined" && module.exports ) { - module.exports = Ractive; -} + // ... or as browser global + global.Ractive = Ractive; -// ... or as AMD module -else if ( typeof define === "function" && define.amd ) { - define( function () { + Ractive.noConflict = function() { + global.Ractive = noConflict; return Ractive; - }); -} + }; -// ... or as browser global -else { - global.Ractive = Ractive; -} - -}( this )); \ No newline at end of file +}( typeof window !== 'undefined' ? window : this ) );