block by Rich-Harris 831b8298670369f5024e344589c0f43e

831b8298670369f5024e

Full Screen

data-binding between components

Response to question posed on Gitter: https://gitter.im/sveltejs/svelte?at=5863e94cc895451b752dd651

npm install
npm run build
open index.html

index.html

<body>
	<main></main>
	<script src='bundle.js'></script>
</body>

App.html

{{#each people as person}}
	<Person isLeader='{{leader === person}}' person='{{person}}' on:select='set({ leader: event.person })'/>
{{/each}}

<script>
	import Person from './Person.html';

	export default {
		data: function () {
			return {
				leader: null,
				people: [
					{ name: 'Alice' },
					{ name: 'Bob' },
					{ name: 'Carol' }
				]
			};
		},

		components: {
			Person
		}
	}
</script>

Person.html

<div>
	<p><strong>{{person.name}}</strong> {{#if isLeader}}(leader){{/if}}</p>
	<button on:click='fire("select", { person: person })' disabled='{{isLeader}}'>make leader</button>
</div>

bundle.js

(function () {
'use strict';

function appendNode ( node, target ) {
	target.appendChild( node );
}

function insertNode ( node, target, anchor ) {
	target.insertBefore( node, anchor );
}

function detachNode ( node ) {
	node.parentNode.removeChild( node );
}

function createElement ( name ) {
	return document.createElement( name );
}

function createText ( data ) {
	return document.createTextNode( data );
}

function createComment ( data ) {
	return document.createComment( data );
}

function get ( key ) {
	return key ? this._state[ key ] : this._state;
}

function fire ( eventName, data ) {
	var handlers = eventName in this._handlers && this._handlers[ eventName ].slice();
	if ( !handlers ) return;

	for ( var i = 0; i < handlers.length; i += 1 ) {
		handlers[i].call( this, data );
	}
}

function observe ( key, callback, options ) {
	var group = ( options && options.defer ) ? this._observers.pre : this._observers.post;

	( group[ key ] || ( group[ key ] = [] ) ).push( callback );

	if ( !options || options.init !== false ) {
		callback.__calling = true;
		callback.call( this, this._state[ key ] );
		callback.__calling = false;
	}

	return {
		cancel: function () {
			var index = group[ key ].indexOf( callback );
			if ( ~index ) group[ key ].splice( index, 1 );
		}
	};
}

function on ( eventName, handler ) {
	var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] );
	handlers.push( handler );

	return {
		cancel: function () {
			var index = handlers.indexOf( handler );
			if ( ~index ) handlers.splice( index, 1 );
		}
	};
}

function noop () {}

function dispatchObservers ( component, group, newState, oldState ) {
	for ( var key in group ) {
		if ( !( key in newState ) ) continue;

		var newValue = newState[ key ];
		var oldValue = oldState[ key ];

		if ( newValue === oldValue && typeof newValue !== 'object' ) continue;

		var callbacks = group[ key ];
		if ( !callbacks ) continue;

		for ( var i = 0; i < callbacks.length; i += 1 ) {
			var callback = callbacks[i];
			if ( callback.__calling ) continue;

			callback.__calling = true;
			callback.call( component, newValue, oldValue );
			callback.__calling = false;
		}
	}
}

function renderMainFragment$1 ( root, component ) {
	var div = createElement( 'div' );
	
	var p = createElement( 'p' );
	
	appendNode( p, div );
	
	var strong = createElement( 'strong' );
	
	appendNode( strong, p );
	var text = createText( root.person.name );
	appendNode( text, strong );
	appendNode( createText( " " ), p );
	var ifBlock_anchor = createComment( "#if isLeader" );
	appendNode( ifBlock_anchor, p );
	
	function getBlock ( root ) {
		if ( root.isLeader ) return renderIfBlock_0;
		return null;
	}
	
	var currentBlock = getBlock( root );
	var ifBlock = currentBlock && currentBlock( root, component );
	
	if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor );
	appendNode( createText( "\n\t" ), div );
	
	var button = createElement( 'button' );
	
	function clickHandler ( event ) {
		var root = this.__svelte.root;
		
		component.fire("select", { person: root.person });
	}
	
	button.addEventListener( 'click', clickHandler, false );
	
	button.disabled = root.isLeader;
	
	button.__svelte = {
		root: root
	};
	
	appendNode( button, div );
	appendNode( createText( "make leader" ), button );

	return {
		mount: function ( target, anchor ) {
			insertNode( div, target, anchor );
		},
		
		update: function ( changed, root ) {
			text.data = root.person.name;
			
			var _currentBlock = currentBlock;
			currentBlock = getBlock( root );
			if ( _currentBlock === currentBlock && ifBlock) {
				ifBlock.update( changed, root );
			} else {
				if ( ifBlock ) ifBlock.teardown( true );
				ifBlock = currentBlock && currentBlock( root, component );
				if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor );
			}
			
			button.disabled = root.isLeader;
			
			button.__svelte.root = root;
		},
		
		teardown: function ( detach ) {
			if ( ifBlock ) ifBlock.teardown( false );
			button.removeEventListener( 'click', clickHandler, false );
			
			if ( detach ) {
				detachNode( div );
			}
		},
	};
}

function renderIfBlock_0 ( root, component ) {
	var text = createText( "(leader)" );

	return {
		mount: function ( target, anchor ) {
			insertNode( text, target, anchor );
		},
		
		update: noop,
		
		teardown: function ( detach ) {
			if ( detach ) {
				detachNode( text );
			}
		},
	};
}

function Person ( options ) {
	options = options || {};
	
	this._state = options.data || {};

	this._observers = {
		pre: Object.create( null ),
		post: Object.create( null )
	};

	this._handlers = Object.create( null );

	this._root = options._root;
	this._yield = options._yield;

	this._fragment = renderMainFragment$1( this._state, this );
	if ( options.target ) this._fragment.mount( options.target, null );
}

Person.prototype.get = get;
Person.prototype.fire = fire;
Person.prototype.observe = observe;
Person.prototype.on = on;

Person.prototype.set = function set ( newState ) {
	var oldState = this._state;
	this._state = Object.assign( {}, oldState, newState );
	
	dispatchObservers( this, this._observers.pre, newState, oldState );
	if ( this._fragment ) this._fragment.update( newState, this._state );
	dispatchObservers( this, this._observers.post, newState, oldState );
};

Person.prototype.teardown = function teardown ( detach ) {
	this.fire( 'teardown' );

	this._fragment.teardown( detach !== false );
	this._fragment = null;

	this._state = {};
};

var template = (function () {
	return {
		data: function () {
			return {
				leader: null,
				people: [
					{ name: 'Alice' },
					{ name: 'Bob' },
					{ name: 'Carol' }
				]
			};
		},

		components: {
			Person
		}
	}
}());

function renderMainFragment ( root, component ) {
	var eachBlock_anchor = createComment( "#each people" );
	var eachBlock_value = root.people;
	var eachBlock_iterations = [];
	
	for ( var i = 0; i < eachBlock_value.length; i += 1 ) {
		eachBlock_iterations[i] = renderEachBlock( root, eachBlock_value, eachBlock_value[i], i, component );
	}

	return {
		mount: function ( target, anchor ) {
			insertNode( eachBlock_anchor, target, anchor );
			
			for ( var i = 0; i < eachBlock_iterations.length; i += 1 ) {
				eachBlock_iterations[i].mount( eachBlock_anchor.parentNode, eachBlock_anchor );
			}
		},
		
		update: function ( changed, root ) {
			var eachBlock_value = root.people;
			
			for ( var i = 0; i < eachBlock_value.length; i += 1 ) {
				if ( !eachBlock_iterations[i] ) {
					eachBlock_iterations[i] = renderEachBlock( root, eachBlock_value, eachBlock_value[i], i, component );
					eachBlock_iterations[i].mount( eachBlock_anchor.parentNode, eachBlock_anchor );
				} else {
					eachBlock_iterations[i].update( changed, root, eachBlock_value, eachBlock_value[i], i );
				}
			}
			
			for ( var i = eachBlock_value.length; i < eachBlock_iterations.length; i += 1 ) {
				eachBlock_iterations[i].teardown( true );
			}
			
			eachBlock_iterations.length = eachBlock_value.length;
		},
		
		teardown: function ( detach ) {
			for ( var i = 0; i < eachBlock_iterations.length; i += 1 ) {
				eachBlock_iterations[i].teardown( detach );
			}
			
			if ( detach ) {
				detachNode( eachBlock_anchor );
			}
		},
	};
}

function renderEachBlock ( root, eachBlock_value, person, person__index, component ) {
	var person1_initialData = {
		isLeader: root.leader === person,
		person: person
	};
	var person1 = new template.components.Person({
		target: null,
		_root: component._root || component,
		data: person1_initialData
	});
	
	person1.on( 'select', function ( event ) {
		component.set({ leader: event.person });
	});

	return {
		mount: function ( target, anchor ) {
			person1._fragment.mount( target, anchor );
		},
		
		update: function ( changed, root, eachBlock_value, person, person__index ) {
			var person = eachBlock_value[person__index];
			
			var person1_changes = {};
			
			if ( 'leader' in changed||'people' in changed ) person1_changes.isLeader = root.leader === person;
			if ( 'people' in changed ) person1_changes.person = person;
			
			if ( Object.keys( person1_changes ).length ) person1.set( person1_changes );
		},
		
		teardown: function ( detach ) {
			person1.teardown( detach );
		},
	};
}

function App ( options ) {
	options = options || {};
	
	this._state = Object.assign( template.data(), options.data );

	this._observers = {
		pre: Object.create( null ),
		post: Object.create( null )
	};

	this._handlers = Object.create( null );

	this._root = options._root;
	this._yield = options._yield;

	this._renderHooks = [];
	
	this._fragment = renderMainFragment( this._state, this );
	if ( options.target ) this._fragment.mount( options.target, null );
	
	while ( this._renderHooks.length ) {
		var hook = this._renderHooks.pop();
		hook.fn.call( hook.context );
	}
}

App.prototype.get = get;
App.prototype.fire = fire;
App.prototype.observe = observe;
App.prototype.on = on;

App.prototype.set = function set ( newState ) {
	var oldState = this._state;
	this._state = Object.assign( {}, oldState, newState );
	
	dispatchObservers( this, this._observers.pre, newState, oldState );
	if ( this._fragment ) this._fragment.update( newState, this._state );
	dispatchObservers( this, this._observers.post, newState, oldState );
	
	while ( this._renderHooks.length ) {
		var hook = this._renderHooks.pop();
		hook.fn.call( hook.context );
	}
};

App.prototype.teardown = function teardown ( detach ) {
	this.fire( 'teardown' );

	this._fragment.teardown( detach !== false );
	this._fragment = null;

	this._state = {};
};

new App({
	target: document.querySelector( 'main' )
});

}());

main.js

import App from './App.html';

new App({
	target: document.querySelector( 'main' )
});

package.json

{
  "name": "svelte-component-list",
  "devDependencies": {
    "rollup": "^0.40.0",
    "rollup-plugin-svelte": "^1.6.0"
  },
  "scripts": {
    "build": "rollup -c"
  }
}

rollup.config.js

import svelte from 'rollup-plugin-svelte';

export default {
	entry: 'main.js',
	dest: 'bundle.js',
	format: 'iife',
	plugins: [
		svelte()
	]
};