Skip to content

Commit

Permalink
Merge pull request #732 from sveltejs/gh-638
Browse files Browse the repository at this point in the history
Event propagation shorthand
  • Loading branch information
Rich-Harris authored Jul 29, 2017
2 parents 6a74db0 + 1b92f5f commit 71047c2
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 43 deletions.
33 changes: 19 additions & 14 deletions src/generators/dom/visitors/Component/EventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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 ) {
Expand Down
47 changes: 26 additions & 21 deletions src/generators/dom/visitors/Element/EventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/parse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export class Parser {
if (required) {
this.error(`Expected ${str}`);
}

return false;
}

match(str: string) {
Expand Down
17 changes: 11 additions & 6 deletions src/parse/read/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 1 addition & 2 deletions src/parse/state/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
2 changes: 2 additions & 0 deletions src/validate/html/validateEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default function validateEventHandlerCallee(
attribute: Node,
refCallees: Node[]
) {
if (!attribute.expression) return;

const { callee, start, type } = attribute.expression;

if (type !== 'CallExpression') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<button on:click='fire("foo", { answer: 42 })'>click me</button>
18 changes: 18 additions & 0 deletions test/runtime/samples/event-handler-shorthand-component/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default {
html: `
<button>click me</button>
`,

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);
}
};
11 changes: 11 additions & 0 deletions test/runtime/samples/event-handler-shorthand-component/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Widget on:foo/>

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

export default {
components: {
Widget
}
};
</script>
13 changes: 13 additions & 0 deletions test/runtime/samples/event-handler-shorthand/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
html: `
<button>click me</button>
`,

test (assert, component, target, window) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');

button.dispatchEvent(event);
assert.ok(component.clicked);
}
};
11 changes: 11 additions & 0 deletions test/runtime/samples/event-handler-shorthand/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<button on:click>click me</button>

<script>
export default {
oncreate () {
this.on('click', () => {
this.clicked = true;
});
}
};
</script>

0 comments on commit 71047c2

Please sign in to comment.