events half-way implemented

pull/31/head
Rich-Harris 8 years ago
parent b59b33bf06
commit b173384cfc

@ -1,11 +1,13 @@
import { getLocator } from 'locate-character'; import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import deindent from './utils/deindent.js'; import deindent from './utils/deindent.js';
import walkHtml from './utils/walkHtml.js'; import walkHtml from './utils/walkHtml.js';
import flattenReference from './utils/flattenReference.js'; import flattenReference from './utils/flattenReference.js';
import counter from './utils/counter.js';
function createRenderer ( fragment ) { function createRenderer ( fragment ) {
return deindent` return deindent`
function ${fragment.name} ( target${fragment.useAnchor ? ', anchor' : ''} ) { function ${fragment.name} ( component, target${fragment.useAnchor ? ', anchor' : ''} ) {
${fragment.initStatements.join( '\n\n' )} ${fragment.initStatements.join( '\n\n' )}
return { return {
@ -16,13 +18,14 @@ function createRenderer ( fragment ) {
teardown: function () { teardown: function () {
${fragment.teardownStatements.join( '\n\n' )} ${fragment.teardownStatements.join( '\n\n' )}
} }
} };
} }
`; `;
} }
export default function generate ( parsed, template ) { export default function generate ( parsed, template ) {
const locator = getLocator( template ); const code = new MagicString( template );
const renderers = []; const renderers = [];
const counters = { const counters = {
@ -45,11 +48,7 @@ export default function generate ( parsed, template ) {
contexts: {}, contexts: {},
contextChain: [ 'context' ], contextChain: [ 'context' ],
counters: { counter: counter(),
element: 0,
text: 0,
anchor: 0
},
parent: null parent: null
}; };
@ -63,6 +62,8 @@ export default function generate ( parsed, template ) {
return `context.${flattened.keypath}`; return `context.${flattened.keypath}`;
} }
console.log( `node, contexts`, node, contexts )
return 'TODO'
throw new Error( 'TODO expressions' ); throw new Error( 'TODO expressions' );
} }
@ -70,15 +71,50 @@ export default function generate ( parsed, template ) {
walkHtml( child, { walkHtml( child, {
Element: { Element: {
enter ( node ) { enter ( node ) {
const name = `element_${current.counters.element++}`; const name = current.counter( node.name );
current.initStatements.push( deindent` const initStatements = [
var ${name} = document.createElement( '${node.name}' ); `var ${name} = document.createElement( '${node.name}' );`
];
const teardownStatements = [
`${name}.parentNode.removeChild( ${name} );`
];
node.attributes.forEach( attribute => {
if ( attribute.type === 'EventHandler' ) {
// TODO use magic-string here, so that stack traces
// go through the template
// TODO verify that it's a valid callee (i.e. built-in or declared method)
const handler = current.counter( `${attribute.name}Handler` );
const callee = `component.${attribute.expression.callee.name}`;
const args = attribute.expression.arguments
.map( arg => flattenExpression( arg, current.contexts ) )
.join( ', ' );
initStatements.push( deindent`
function ${handler} ( event ) {
${callee}(${args});
}
${name}.addEventListener( '${attribute.name}', ${handler}, false );
` ); ` );
current.teardownStatements.push( deindent` teardownStatements.push( deindent`
${name}.parentNode.removeChild( ${name} ); ${name}.removeEventListener( '${attribute.name}', ${handler}, false );
` ); ` );
}
else {
throw new Error( `Not implemented: ${attribute.type}` );
}
});
current.initStatements.push( initStatements.join( '\n' ) );
current.teardownStatements.push( teardownStatements.join( '\n' ) );
current = Object.assign( {}, current, { current = Object.assign( {}, current, {
target: name, target: name,
@ -106,14 +142,14 @@ export default function generate ( parsed, template ) {
Text: { Text: {
enter ( node ) { enter ( node ) {
current.initStatements.push( deindent` current.initStatements.push( deindent`
${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data ) }) ); ${current.target}.appendChild( document.createTextNode( ${JSON.stringify( node.data )} ) );
` ); ` );
} }
}, },
MustacheTag: { MustacheTag: {
enter ( node ) { enter ( node ) {
const name = `text_${current.counters.text++}`; const name = current.counter( 'text' );
const expression = flattenExpression( node.expression, current.contexts ); const expression = flattenExpression( node.expression, current.contexts );
current.initStatements.push( deindent` current.initStatements.push( deindent`
@ -147,7 +183,7 @@ export default function generate ( parsed, template ) {
current.updateStatements.push( deindent` current.updateStatements.push( deindent`
if ( ${expression} && !${name} ) { if ( ${expression} && !${name} ) {
${name} = ${renderer}( ${current.target}, ${name}_anchor ); ${name} = ${renderer}( component, ${current.target}, ${name}_anchor );
} }
else if ( !${expression} && ${name} ) { else if ( !${expression} && ${name} ) {
@ -175,11 +211,7 @@ export default function generate ( parsed, template ) {
updateStatements: [], updateStatements: [],
teardownStatements: [], teardownStatements: [],
counters: { counter: counter(),
element: 0,
text: 0,
anchor: 0
},
parent: current parent: current
}; };
@ -209,7 +241,7 @@ export default function generate ( parsed, template ) {
current.updateStatements.push( deindent` current.updateStatements.push( deindent`
for ( var i = 0; i < ${expression}.length; i += 1 ) { for ( var i = 0; i < ${expression}.length; i += 1 ) {
if ( !${name}_iterations[i] ) { if ( !${name}_iterations[i] ) {
${name}_iterations[i] = ${renderer}( ${name}_fragment ); ${name}_iterations[i] = ${renderer}( component, ${name}_fragment );
} }
const iteration = ${name}_iterations[i]; const iteration = ${name}_iterations[i];
@ -245,11 +277,7 @@ export default function generate ( parsed, template ) {
updateStatements: [], updateStatements: [],
teardownStatements: [], teardownStatements: [],
counters: { counter: counter(),
element: 0,
text: 0,
anchor: 0
},
parent: current parent: current
}; };
@ -266,7 +294,7 @@ export default function generate ( parsed, template ) {
renderers.push( createRenderer( current ) ); renderers.push( createRenderer( current ) );
let js; let js = '';
let hasDefaultData = false; let hasDefaultData = false;
// TODO wrap all this in magic-string // TODO wrap all this in magic-string
@ -280,7 +308,7 @@ export default function generate ( parsed, template ) {
} }
} }
const code = deindent` const result = deindent`
${js} ${js}
${renderers.reverse().join( '\n\n' )} ${renderers.reverse().join( '\n\n' )}
@ -294,7 +322,6 @@ export default function generate ( parsed, template ) {
deferred: Object.create( null ) deferred: Object.create( null )
}; };
// universal methods
function dispatchObservers ( group, state, oldState ) { function dispatchObservers ( group, state, oldState ) {
for ( const key in group ) { for ( const key in group ) {
const newValue = state[ key ]; const newValue = state[ key ];
@ -315,6 +342,11 @@ export default function generate ( parsed, template ) {
return state[ key ]; return state[ key ];
}; };
component.set = function set ( newState ) {
Object.assign( state, newState );
mainFragment.update( state );
};
component.observe = function ( key, callback, options = {} ) { component.observe = function ( key, callback, options = {} ) {
const group = options.defer ? observers.deferred : observers.immediate; const group = options.defer ? observers.deferred : observers.immediate;
@ -329,12 +361,6 @@ export default function generate ( parsed, template ) {
}; };
}; };
// component-specific methods
component.set = function set ( newState ) {
Object.assign( state, newState );
mainFragment.update( state );
};
component.teardown = function teardown () { component.teardown = function teardown () {
mainFragment.teardown(); mainFragment.teardown();
mainFragment = null; mainFragment = null;
@ -342,12 +368,12 @@ export default function generate ( parsed, template ) {
state = {}; state = {};
}; };
let mainFragment = renderMainFragment( options.target ); let mainFragment = renderMainFragment( component, options.target );
component.set( ${hasDefaultData ? `Object.assign( template.data(), options.data )` : `options.data`} ); component.set( ${hasDefaultData ? `Object.assign( template.data(), options.data )` : `options.data`} );
return component; return component;
} }
`; `;
return { code }; return { code: result }; // TODO use magic-string
} }

@ -0,0 +1,12 @@
export default function counter () {
const counts = {};
return function ( label ) {
if ( label in counts ) {
return `label_${counts[ label ]++}`;
}
counts[ label ] = 0;
return label;
};
}

@ -32,6 +32,7 @@
}, },
"dependencies": { "dependencies": {
"acorn": "^4.0.3", "acorn": "^4.0.3",
"estree-walker": "^0.3.0",
"locate-character": "^2.0.0", "locate-character": "^2.0.0",
"magic-string": "^0.16.0" "magic-string": "^0.16.0"
} }

@ -0,0 +1,10 @@
import * as assert from 'assert';
export default {
solo: true,
description: 'attaches event handlers',
html: '<button>toggle</button><!--#if visible-->',
test ( component, target ) {
assert.ok( false, 'TODO fire synthetic event to test' );
}
};

@ -0,0 +1,5 @@
<button on:click='set({ visible: !visible })'>toggle</button>
{{#if visible}}
<p>hello!</p>
{{/if}}
Loading…
Cancel
Save