dynamic component event handlers

pull/971/head
Rich Harris 7 years ago
parent 57af1539d9
commit e1777b6037

@ -61,12 +61,16 @@ export default function visitComponent(
let beforecreate: string = null; let beforecreate: string = null;
const attributes = node.attributes const attributes = node.attributes
.filter((a: Node) => a.type === 'Attribute') .filter(a => a.type === 'Attribute')
.map((a: Node) => mungeAttribute(a, block)); .map(a => mungeAttribute(a, block));
const bindings = node.attributes const bindings = node.attributes
.filter((a: Node) => a.type === 'Binding') .filter(a => a.type === 'Binding')
.map((a: Node) => mungeBinding(a, block)); .map(a => mungeBinding(a, block));
const eventHandlers = node.attributes
.filter((a: Node) => a.type === 'EventHandler')
.map(a => mungeEventHandler(generator, node, a, block, name_context, allContexts));
const ref = node.attributes.find((a: Node) => a.type === 'Ref'); const ref = node.attributes.find((a: Node) => a.type === 'Ref');
if (ref) generator.usesRefs = true; if (ref) generator.usesRefs = true;
@ -244,9 +248,7 @@ export default function visitComponent(
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
var ${switch_vars.value} = ${snippet}; var ${switch_vars.value} = ${snippet};
`);
block.builders.init.addBlock(deindent`
function ${switch_vars.props}(${params}) { function ${switch_vars.props}(${params}) {
return { return {
${componentInitProperties.join(',\n')} ${componentInitProperties.join(',\n')}
@ -259,6 +261,14 @@ export default function visitComponent(
${beforecreate} ${beforecreate}
} }
${eventHandlers.map(handler => deindent`
function ${handler.var}(event) {
${handler.body}
}
if (${name}) ${name}.on("${handler.name}", ${handler.var});
`)}
`); `);
block.builders.create.addLine( block.builders.create.addLine(
@ -281,6 +291,11 @@ export default function visitComponent(
${name} = new ${switch_vars.value}(${switch_vars.props}(${params})); ${name} = new ${switch_vars.value}(${switch_vars.props}(${params}));
${name}._fragment.c(); ${name}._fragment.c();
${name}._mount(${anchor}.parentNode, ${anchor}); ${name}._mount(${anchor}.parentNode, ${anchor});
${eventHandlers.map(handler => deindent`
${name}.on("${handler.name}", ${handler.var});
`)}
${ref && `#component.refs.${ref.name} = ${name};`} ${ref && `#component.refs.${ref.name} = ${name};`}
} }
@ -305,6 +320,12 @@ export default function visitComponent(
${beforecreate} ${beforecreate}
${eventHandlers.map(handler => deindent`
${name}.on("${handler.name}", function(event) {
${handler.body}
});
`)}
${ref && `#component.refs.${ref.name} = ${name};`} ${ref && `#component.refs.${ref.name} = ${name};`}
`); `);
@ -326,50 +347,6 @@ export default function visitComponent(
`); `);
} }
// 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);
allContexts.add(context);
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return `var state = ${name_context}.state;`;
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_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}
});
`);
});
// maintain component context // maintain component context
if (allContexts.size) { if (allContexts.size) {
const contexts = Array.from(allContexts); const contexts = Array.from(allContexts);
@ -512,6 +489,55 @@ function mungeBinding(binding: Node, block: Block): Binding {
}; };
} }
function mungeEventHandler(generator: DomGenerator, node: Node, handler: Node, block: Block, name_context: string, allContexts: Set<string>) {
let body;
if (handler.expression) {
generator.addSourcemapLocations(handler.expression);
generator.code.prependRight(
handler.expression.start,
`${block.alias('component')}.`
);
const usedContexts: string[] = [];
handler.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
allContexts.add(context);
});
});
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return `var state = ${name_context}.state;`;
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_context}.${indexName}, ${name} = ${listName}[${indexName}]`;
});
body = deindent`
${declarations}
[${handler.expression.start}-${handler.expression.end}];
`;
} else {
body = deindent`
${block.alias('component')}.fire('${handler.name}', event);
`;
}
return {
name: handler.name,
var: block.getUniqueName(`${node.var}_${handler.name}`),
body
};
}
function isComputed(node: Node) { function isComputed(node: Node) {
while (node.type === 'MemberExpression') { while (node.type === 'MemberExpression') {
if (node.computed) return true; if (node.computed) return true;

@ -0,0 +1 @@
<button on:click='fire("select", { id: "bar" })'>select bar</button>

@ -0,0 +1 @@
<button on:click='fire("select", { id: "foo" })'>select foo</button>

@ -0,0 +1,27 @@
export default {
data: {
x: true
},
html: `
<button>select foo</button>
`,
test(assert, component, target, window) {
const click = new window.MouseEvent('click');
target.querySelector('button').dispatchEvent(click);
assert.equal(component.get('selected'), 'foo');
component.set({
x: false
});
assert.htmlEqual(target.innerHTML, `
<button>select bar</button>
`);
target.querySelector('button').dispatchEvent(click);
assert.equal(component.get('selected'), 'bar');
}
};

@ -0,0 +1,12 @@
<:Switch { x ? Foo : Bar } on:select='set({ selected: event.id })'/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>
Loading…
Cancel
Save