From 51af8c29e0759825f5a9c8c90d03f1092ceec6de Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 29 Jul 2017 18:10:01 -0400 Subject: [PATCH 1/2] event propagation shorthand for elements (#638) --- .../dom/visitors/Element/EventHandler.ts | 47 ++++++++++--------- src/parse/index.ts | 2 + src/parse/read/directives.ts | 17 ++++--- src/parse/state/tag.ts | 3 +- src/validate/html/validateEventHandler.ts | 2 + .../event-handler-shorthand/_config.js | 13 +++++ .../samples/event-handler-shorthand/main.html | 11 +++++ 7 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 test/runtime/samples/event-handler-shorthand/_config.js create mode 100644 test/runtime/samples/event-handler-shorthand/main.html diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index eb96c4e636..56fdb42a87 100644 --- a/src/generators/dom/visitors/Element/EventHandler.ts +++ b/src/generators/dom/visitors/Element/EventHandler.ts @@ -16,30 +16,33 @@ export default function visitEventHandler( const isCustomEvent = generator.events.has(name); const shouldHoist = !isCustomEvent && state.inEachBlock; - generator.addSourcemapLocations(attribute.expression); - - const flattened = flattenReference(attribute.expression.callee); - if (flattened.name !== 'event' && flattened.name !== 'this') { - // allow event.stopPropagation(), this.select() etc - // TODO verify that it's a valid callee (i.e. built-in or declared method) - generator.code.prependRight( - attribute.expression.start, - `${block.alias('component')}.` - ); - if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! - } - const context = shouldHoist ? null : state.parentNode; const usedContexts: string[] = []; - attribute.expression.arguments.forEach((arg: Node) => { - const { contexts } = block.contextualise(arg, context, true); - contexts.forEach(context => { - if (!~usedContexts.indexOf(context)) usedContexts.push(context); - if (!~state.allUsedContexts.indexOf(context)) - state.allUsedContexts.push(context); + if (attribute.expression) { + generator.addSourcemapLocations(attribute.expression); + + const flattened = flattenReference(attribute.expression.callee); + if (flattened.name !== 'event' && flattened.name !== 'this') { + // allow event.stopPropagation(), this.select() etc + // TODO verify that it's a valid callee (i.e. built-in or declared method) + generator.code.prependRight( + attribute.expression.start, + `${block.alias('component')}.` + ); + if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! + } + + attribute.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, context, true); + + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + if (!~state.allUsedContexts.indexOf(context)) + state.allUsedContexts.push(context); + }); }); - }); + } const _this = context || 'this'; const declarations = usedContexts.map(name => { @@ -66,7 +69,9 @@ export default function visitEventHandler( ${state.usesComponent && `var ${block.alias('component')} = ${_this}._svelte.component;`} ${declarations} - [✂${attribute.expression.start}-${attribute.expression.end}✂]; + ${attribute.expression ? + `[✂${attribute.expression.start}-${attribute.expression.end}✂];` : + `${block.alias('component')}.fire('${attribute.name}', event);`} `; if (isCustomEvent) { diff --git a/src/parse/index.ts b/src/parse/index.ts index 514e074fd4..b9748515e5 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -111,6 +111,8 @@ export class Parser { if (required) { this.error(`Expected ${str}`); } + + return false; } match(str: string) { diff --git a/src/parse/read/directives.ts b/src/parse/read/directives.ts index 6064a61fcb..0b61ad6350 100644 --- a/src/parse/read/directives.ts +++ b/src/parse/read/directives.ts @@ -43,16 +43,21 @@ function readExpression(parser: Parser, start: number, quoteMark) { export function readEventHandlerDirective( parser: Parser, start: number, - name: string + name: string, + hasValue: boolean ) { - const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; + let expression; - const expressionStart = parser.index; + if (hasValue) { + const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; - const expression = readExpression(parser, expressionStart, quoteMark); + const expressionStart = parser.index; - if (expression.type !== 'CallExpression') { - parser.error(`Expected call expression`, expressionStart); + expression = readExpression(parser, expressionStart, quoteMark); + + if (expression.type !== 'CallExpression') { + parser.error(`Expected call expression`, expressionStart); + } } return { diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 6fee187dd9..cfa0561365 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -256,8 +256,7 @@ function readAttribute(parser: Parser, uniqueNames) { parser.allowWhitespace(); if (/^on:/.test(name)) { - parser.eat('=', true); - return readEventHandlerDirective(parser, start, name.slice(3)); + return readEventHandlerDirective(parser, start, name.slice(3), parser.eat('=')); } if (/^bind:/.test(name)) { diff --git a/src/validate/html/validateEventHandler.ts b/src/validate/html/validateEventHandler.ts index 85fdad08e6..ec4476d3fb 100644 --- a/src/validate/html/validateEventHandler.ts +++ b/src/validate/html/validateEventHandler.ts @@ -9,6 +9,8 @@ export default function validateEventHandlerCallee( validator: Validator, attribute: Node ) { + if (!attribute.expression) return; + const { callee, start, type } = attribute.expression; if (type !== 'CallExpression') { diff --git a/test/runtime/samples/event-handler-shorthand/_config.js b/test/runtime/samples/event-handler-shorthand/_config.js new file mode 100644 index 0000000000..ceded0bf08 --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand/_config.js @@ -0,0 +1,13 @@ +export default { + html: ` + + `, + + test (assert, component, target, window) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click'); + + button.dispatchEvent(event); + assert.ok(component.clicked); + } +}; diff --git a/test/runtime/samples/event-handler-shorthand/main.html b/test/runtime/samples/event-handler-shorthand/main.html new file mode 100644 index 0000000000..5b283cbb59 --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file From 1b92f5fa20098ce487e882599276a24a01e4a9a9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 29 Jul 2017 18:19:04 -0400 Subject: [PATCH 2/2] event propagation shorthand for components (#638) --- .../dom/visitors/Component/EventHandler.ts | 33 +++++++++++-------- .../Widget.html | 1 + .../_config.js | 18 ++++++++++ .../main.html | 11 +++++++ 4 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 test/runtime/samples/event-handler-shorthand-component/Widget.html create mode 100644 test/runtime/samples/event-handler-shorthand-component/_config.js create mode 100644 test/runtime/samples/event-handler-shorthand-component/main.html diff --git a/src/generators/dom/visitors/Component/EventHandler.ts b/src/generators/dom/visitors/Component/EventHandler.ts index 1010456253..0abef43821 100644 --- a/src/generators/dom/visitors/Component/EventHandler.ts +++ b/src/generators/dom/visitors/Component/EventHandler.ts @@ -13,22 +13,25 @@ export default function visitEventHandler( local ) { // TODO verify that it's a valid callee (i.e. built-in or declared method) - generator.addSourcemapLocations(attribute.expression); - generator.code.prependRight( - attribute.expression.start, - `${block.alias('component')}.` - ); - const usedContexts: string[] = []; - 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); + 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 => { @@ -42,7 +45,9 @@ export default function visitEventHandler( const handlerBody = (declarations.length ? declarations.join('\n') + '\n\n' : '') + - `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; + (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 ) { diff --git a/test/runtime/samples/event-handler-shorthand-component/Widget.html b/test/runtime/samples/event-handler-shorthand-component/Widget.html new file mode 100644 index 0000000000..47e1f95a2f --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand-component/Widget.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/event-handler-shorthand-component/_config.js b/test/runtime/samples/event-handler-shorthand-component/_config.js new file mode 100644 index 0000000000..e0e21f1400 --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand-component/_config.js @@ -0,0 +1,18 @@ +export default { + html: ` + + `, + + test (assert, component, target, window) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click'); + + let answer; + component.on('foo', event => { + answer = event.answer; + }); + + button.dispatchEvent(event); + assert.equal(answer, 42); + } +}; diff --git a/test/runtime/samples/event-handler-shorthand-component/main.html b/test/runtime/samples/event-handler-shorthand-component/main.html new file mode 100644 index 0000000000..5eb9bbb3eb --- /dev/null +++ b/test/runtime/samples/event-handler-shorthand-component/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file