move event handlers and refs inside Component.ts

pull/772/head
Rich Harris 8 years ago
parent ee5a4b3d0b
commit 507d6a4062

@ -2,9 +2,7 @@ import deindent from '../../../../utils/deindent';
import CodeBuilder from '../../../../utils/CodeBuilder'; import CodeBuilder from '../../../../utils/CodeBuilder';
import visit from '../../visit'; import visit from '../../visit';
import visitAttribute from './Attribute'; import visitAttribute from './Attribute';
import visitEventHandler from './EventHandler';
import visitBinding from './Binding'; import visitBinding from './Binding';
import visitRef from './Ref';
import { DomGenerator } from '../../index'; import { DomGenerator } from '../../index';
import Block from '../../Block'; import Block from '../../Block';
import getTailSnippet from '../../../../utils/getTailSnippet'; import getTailSnippet from '../../../../utils/getTailSnippet';
@ -26,16 +24,12 @@ function stringifyProps(props: string[]) {
const order = { const order = {
Attribute: 1, Attribute: 1,
EventHandler: 2, Binding: 3
Binding: 3,
Ref: 4,
}; };
const visitors = { const visitors = {
Attribute: visitAttribute, Attribute: visitAttribute,
EventHandler: visitEventHandler, Binding: visitBinding
Binding: visitBinding,
Ref: visitRef,
}; };
export default function visitComponent( export default function visitComponent(
@ -53,17 +47,10 @@ export default function visitComponent(
const childState = node._state; const childState = node._state;
const local = { const local = {
name,
namespace: state.namespace,
isComponent: true,
allUsedContexts: [], allUsedContexts: [],
staticAttributes: [], staticAttributes: [],
dynamicAttributes: [], dynamicAttributes: [],
bindings: [], bindings: []
create: new CodeBuilder(),
update: new CodeBuilder(),
}; };
const isTopLevel = !state.parentNode; const isTopLevel = !state.parentNode;
@ -73,7 +60,7 @@ export default function visitComponent(
node.attributes node.attributes
.sort((a, b) => order[a.type] - order[b.type]) .sort((a, b) => order[a.type] - order[b.type])
.forEach(attribute => { .forEach(attribute => {
visitors[attribute.type]( visitors[attribute.type] && visitors[attribute.type](
generator, generator,
block, block,
childState, childState,
@ -83,38 +70,6 @@ export default function visitComponent(
); );
}); });
if (local.allUsedContexts.length) {
const initialProps = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
})
.join(',\n');
const updates = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `${name}._context.state = state;`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
})
.join('\n');
local.create.addBlock(deindent`
${name}._context = {
${initialProps}
};
`);
local.update.addBlock(updates);
}
const componentInitProperties = [`_root: #component._root`]; const componentInitProperties = [`_root: #component._root`];
// Component has children, put them in a separate {{yield}} block // Component has children, put them in a separate {{yield}} block
@ -127,34 +82,33 @@ export default function visitComponent(
visit(generator, childBlock, childState, child, elementStack); visit(generator, childBlock, childState, child, elementStack);
}); });
const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); const yield_fragment = block.getUniqueName(`${name}_yield_fragment`);
block.builders.init.addLine( block.builders.init.addLine(
`var ${yieldFragment} = ${childBlock.name}( ${params}, #component );` `var ${yield_fragment} = ${childBlock.name}( ${params}, #component );`
); );
block.builders.create.addLine(`${yieldFragment}.create();`); block.builders.create.addLine(`${yield_fragment}.create();`);
block.builders.claim.addLine( block.builders.claim.addLine(
`${yieldFragment}.claim( ${state.parentNodes} );` `${yield_fragment}.claim( ${state.parentNodes} );`
); );
if (childBlock.hasUpdateMethod) { if (childBlock.hasUpdateMethod) {
block.builders.update.addLine( block.builders.update.addLine(
`${yieldFragment}.update( changed, ${params} );` `${yield_fragment}.update( changed, ${params} );`
); );
} }
block.builders.destroy.addLine(`${yieldFragment}.destroy();`); block.builders.destroy.addLine(`${yield_fragment}.destroy();`);
componentInitProperties.push(`_yield: ${yieldFragment}`); componentInitProperties.push(`_yield: ${yield_fragment}`);
} }
const statements: string[] = []; const statements: string[] = [];
let name_updating: string; let name_updating: string;
let name_initial_data: string; let name_initial_data: string;
let _beforecreate: string = null; let beforecreate: string = null;
let bindings = [];
if ( if (
local.staticAttributes.length || local.staticAttributes.length ||
@ -192,7 +146,10 @@ export default function visitComponent(
block.addVariable(name_updating, '{}'); block.addVariable(name_updating, '{}');
statements.push(`var ${name_initial_data} = ${initialPropString};`); statements.push(`var ${name_initial_data} = ${initialPropString};`);
bindings = local.bindings.map(binding => { const setParentFromChildOnChange = new CodeBuilder();
const setParentFromChildOnInit = new CodeBuilder();
local.bindings.forEach(binding => {
let setParentFromChild; let setParentFromChild;
const { name: key } = getObject(binding.value); const { name: key } = getObject(binding.value);
@ -224,58 +181,51 @@ export default function visitComponent(
setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`; setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`;
} }
return { statements.push(deindent`
init: deindent` if ( ${binding.prop} in ${binding.obj} ) {
if ( ${binding.prop} in ${binding.obj} ) { ${name_initial_data}.${binding.name} = ${binding.snippet};
${name_initial_data}.${binding.name} = ${binding.snippet}; ${name_updating}.${binding.name} = true;
${name_updating}.${binding.name} = true; }`
}`, );
bind: deindent`
if (!${name_updating}.${binding.name} && changed.${binding.name}) { setParentFromChildOnChange.addConditional(
${setParentFromChild} `!${name_updating}.${binding.name} && changed.${binding.name}`,
} setParentFromChild
`, );
setParentFromChild: deindent`
if (!${name_updating}.${binding.name}) { setParentFromChildOnInit.addConditional(
${setParentFromChild} `!${name_updating}.${binding.name}`,
} setParentFromChild
`, );
// TODO could binding.dependencies.length ever be 0? // TODO could binding.dependencies.length ever be 0?
update: binding.dependencies.length && deindent` if (binding.dependencies.length) {
updates.push(deindent`
if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) { if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) {
${name}_changes.${binding.name} = ${binding.snippet}; ${name}_changes.${binding.name} = ${binding.snippet};
${name_updating}.${binding.name} = true; ${name_updating}.${binding.name} = true;
} }
` `);
} }
}); });
bindings.forEach(binding => {
statements.push(binding.init);
});
componentInitProperties.push(`data: ${name_initial_data}`); componentInitProperties.push(`data: ${name_initial_data}`);
componentInitProperties.push(deindent` componentInitProperties.push(deindent`
_bind: function(changed, childState) { _bind: function(changed, childState) {
var state = #component.get(), newState = {}; var state = #component.get(), newState = {};
${bindings.map(binding => binding.bind).join('\n')} ${setParentFromChildOnChange}
${name_updating} = changed; ${name_updating} = changed;
#component._set(newState); #component._set(newState);
${name_updating} = {}; ${name_updating} = {};
} }
`); `);
bindings.forEach(binding => { beforecreate = deindent`
if (binding.update) updates.push(binding.update);
});
_beforecreate = deindent`
#component._root._beforecreate.push(function () { #component._root._beforecreate.push(function () {
var state = #component.get(), childState = ${name}.get(), newState = {}; var state = #component.get(), childState = ${name}.get(), newState = {};
if (!childState) return; if (!childState) return;
${bindings.map(binding => binding.setParentFromChild).join('\n')} ${setParentFromChildOnInit}
${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} }; ${name_updating} = { ${local.bindings.map(binding => `${binding.name}: true`).join(', ')} };
#component._set(newState); #component._set(newState);
${name_updating} = {}; ${name_updating} = {};
@ -285,11 +235,11 @@ export default function visitComponent(
componentInitProperties.push(`data: ${initialPropString}`); componentInitProperties.push(`data: ${initialPropString}`);
} }
local.update.addBlock(deindent` block.builders.update.addBlock(deindent`
var ${name}_changes = {}; var ${name}_changes = {};
${updates.join('\n')} ${updates.join('\n')}
${name}._set( ${name}_changes ); ${name}._set( ${name}_changes );
${bindings.length && `${name_updating} = {};`} ${local.bindings.length && `${name_updating} = {};`}
`); `);
} }
@ -298,21 +248,19 @@ export default function visitComponent(
: generator.importedComponents.get(node.name) || : generator.importedComponents.get(node.name) ||
`@template.components.${node.name}`; `@template.components.${node.name}`;
local.create.addBlockAtStart(deindent` block.builders.init.addBlock(deindent`
${statements.join('\n')} ${statements.join('\n')}
var ${name} = new ${expression}({ var ${name} = new ${expression}({
${componentInitProperties.join(',\n')} ${componentInitProperties.join(',\n')}
}); });
${_beforecreate} ${beforecreate}
`); `);
if (isTopLevel) if (isTopLevel)
block.builders.unmount.addLine(`${name}._fragment.unmount();`); block.builders.unmount.addLine(`${name}._fragment.unmount();`);
block.builders.destroy.addLine(`${name}.destroy( false );`); block.builders.destroy.addLine(`${name}.destroy( false );`);
block.builders.init.addBlock(local.create);
const targetNode = state.parentNode || '#target'; const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor'; const anchorNode = state.parentNode ? 'null' : 'anchor';
@ -324,7 +272,94 @@ export default function visitComponent(
`${name}._fragment.mount( ${targetNode}, ${anchorNode} );` `${name}._fragment.mount( ${targetNode}, ${anchorNode} );`
); );
if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); // event handlers
node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => {
const usedContexts: string[] = [];
if (handler.expression) {
generator.addSourcemapLocations(handler.expression);
generator.code.prependRight(
handler.expression.start,
`${block.alias('component')}.`
);
handler.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return 'var state = this._context.state;';
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
(handler.expression ?
`[✂${handler.expression.start}-${handler.expression.end}✂];` :
`${block.alias('component')}.fire('${handler.name}', event);`);
block.builders.init.addBlock(deindent`
${name}.on( '${handler.name}', function ( event ) {
${handlerBody}
});
`);
});
// refs
node.attributes.filter((a: Node) => a.type === 'Ref').forEach((ref: Node) => {
generator.usesRefs = true;
block.builders.init.addLine(`#component.refs.${ref.name} = ${name};`);
block.builders.destroy.addLine(deindent`
if ( #component.refs.${ref.name} === ${name} ) #component.refs.${ref.name} = null;
`);
});
// maintain component context
if (local.allUsedContexts.length) {
const initialProps = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
})
.join(',\n');
const updates = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `${name}._context.state = state;`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
})
.join('\n');
block.builders.init.addBlock(deindent`
${name}._context = {
${initialProps}
};
`);
block.builders.update.addBlock(updates);
}
} }
function isComputed(node: Node) { function isComputed(node: Node) {

@ -1,57 +0,0 @@
import deindent from '../../../../utils/deindent';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitEventHandler(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
const usedContexts: string[] = [];
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return 'var state = this._context.state;';
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
(attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire('${attribute.name}', event);`);
local.create.addBlock(deindent`
${local.name}.on( '${attribute.name}', function ( event ) {
${handlerBody}
});
`);
}

@ -1,22 +0,0 @@
import deindent from '../../../../utils/deindent';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitRef(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
generator.usesRefs = true;
local.create.addLine(`#component.refs.${attribute.name} = ${local.name};`);
block.builders.destroy.addLine(deindent`
if ( #component.refs.${attribute.name} === ${local.name} ) #component.refs.${attribute.name} = null;
`);
}
Loading…
Cancel
Save