From 105919e7bae5a65071e7d4180a4c27e3d6a9b60e Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Thu, 7 Nov 2019 00:39:03 +0800 Subject: [PATCH 1/3] feat dynamic event handler --- src/compiler/compile/nodes/EventHandler.ts | 33 +----- src/compiler/compile/render_dom/Block.ts | 14 +-- .../compile/render_dom/wrappers/Body.ts | 21 ++-- .../wrappers/Element/EventHandler.ts | 69 ++++++++++++ .../render_dom/wrappers/Element/index.ts | 6 +- .../wrappers/InlineComponent/index.ts | 4 +- .../compile/render_dom/wrappers/Window.ts | 5 +- .../wrappers/shared/add_event_handlers.ts | 33 +----- .../samples/event-handler-dynamic/expected.js | 106 ++++++++++++++++++ .../event-handler-dynamic/input.svelte | 23 ++++ .../Button.svelte | 1 + .../_config.js | 18 +++ .../main.svelte | 11 ++ .../Button.svelte | 1 + .../_config.js | 18 +++ .../main.svelte | 11 ++ .../samples/event-handler-dynamic/_config.js | 50 +++++++++ .../samples/event-handler-dynamic/main.svelte | 23 ++++ 18 files changed, 368 insertions(+), 79 deletions(-) create mode 100644 src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts create mode 100644 test/js/samples/event-handler-dynamic/expected.js create mode 100644 test/js/samples/event-handler-dynamic/input.svelte create mode 100644 test/runtime/samples/component-event-handler-dynamic/Button.svelte create mode 100644 test/runtime/samples/component-event-handler-dynamic/_config.js create mode 100644 test/runtime/samples/component-event-handler-dynamic/main.svelte create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/Button.svelte create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/_config.js create mode 100644 test/runtime/samples/component-event-handler-modifier-once-dynamic/main.svelte create mode 100644 test/runtime/samples/event-handler-dynamic/_config.js create mode 100644 test/runtime/samples/event-handler-dynamic/main.svelte diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts index 7faf5d15dd..19da3d9dd7 100644 --- a/src/compiler/compile/nodes/EventHandler.ts +++ b/src/compiler/compile/nodes/EventHandler.ts @@ -1,8 +1,6 @@ import Node from './shared/Node'; import Expression from './shared/Expression'; import Component from '../Component'; -import { b, x } from 'code-red'; -import Block from '../render_dom/Block'; import { sanitize } from '../../utils/names'; import { Identifier } from 'estree'; @@ -14,6 +12,7 @@ export default class EventHandler extends Node { handler_name: Identifier; uses_context = false; can_make_passive = false; + reassigned?: boolean; constructor(component: Component, parent, template_scope, info) { super(component, parent, template_scope, info); @@ -22,7 +21,7 @@ export default class EventHandler extends Node { this.modifiers = new Set(info.modifiers); if (info.expression) { - this.expression = new Expression(component, this, template_scope, info.expression, true); + this.expression = new Expression(component, this, template_scope, info.expression); this.uses_context = this.expression.uses_context; if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) { @@ -42,34 +41,12 @@ export default class EventHandler extends Node { if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) { this.can_make_passive = true; } + + this.reassigned = component.var_lookup.get(info.expression.name).reassigned; } } } else { - const id = component.get_unique_name(`${sanitize(this.name)}_handler`); - - component.add_var({ - name: id.name, - internal: true, - referenced: true - }); - - component.partly_hoisted.push(b` - function ${id}(event) { - @bubble($$self, event); - } - `); - - this.handler_name = id; + this.handler_name = component.get_unique_name(`${sanitize(this.name)}_handler`); } } - - // TODO move this? it is specific to render-dom - render(block: Block) { - if (this.expression) { - return this.expression.manipulate(block); - } - - // this.component.add_reference(this.handler_name); - return x`#ctx.${this.handler_name}`; - } } diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 8f5740ecb4..49e40c3a4f 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -60,7 +60,7 @@ export default class Block { destroy: Array; }; - event_listeners: Node[] = []; + event_listeners: Array = []; maintain_context: boolean; has_animation: boolean; @@ -203,13 +203,11 @@ export default class Block { } add_variable(id: Identifier, init?: Node) { - this.variables.forEach(v => { - if (v.id.name === id.name) { - throw new Error( - `Variable '${id.name}' already initialised with a different value` - ); - } - }); + if (this.variables.has(id.name)) { + throw new Error( + `Variable '${id.name}' already initialised with a different value` + ); + } this.variables.set(id.name, { id, init }); } diff --git a/src/compiler/compile/render_dom/wrappers/Body.ts b/src/compiler/compile/render_dom/wrappers/Body.ts index dc1f561b1f..e16ebc25bd 100644 --- a/src/compiler/compile/render_dom/wrappers/Body.ts +++ b/src/compiler/compile/render_dom/wrappers/Body.ts @@ -3,21 +3,24 @@ import Wrapper from './shared/Wrapper'; import { b } from 'code-red'; import Body from '../../nodes/Body'; import { Identifier } from 'estree'; +import EventHandler from './Element/EventHandler'; export default class BodyWrapper extends Wrapper { node: Body; render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.node.handlers.forEach(handler => { - const snippet = handler.render(block); + this.node.handlers + .map(handler => new EventHandler(handler, this)) + .forEach(handler => { + const snippet = handler.get_snippet(block); - block.chunks.init.push(b` - @_document.body.addEventListener("${handler.name}", ${snippet}); - `); + block.chunks.init.push(b` + @_document.body.addEventListener("${handler.node.name}", ${snippet}); + `); - block.chunks.destroy.push(b` - @_document.body.removeEventListener("${handler.name}", ${snippet}); - `); - }); + block.chunks.destroy.push(b` + @_document.body.removeEventListener("${handler.node.name}", ${snippet}); + `); + }); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts new file mode 100644 index 0000000000..37089e7493 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -0,0 +1,69 @@ +import EventHandler from '../../../nodes/EventHandler'; +import Wrapper from '../shared/Wrapper'; +import Block from '../../Block'; +import { b, x, p } from 'code-red'; + +const TRUE = x`true`; +const FALSE = x`false`; + +export default class EventHandlerWrapper { + node: EventHandler; + parent: Wrapper; + + constructor(node: EventHandler, parent: Wrapper) { + this.node = node; + this.parent = parent; + + if (!node.expression) { + this.parent.renderer.component.add_var({ + name: node.handler_name.name, + internal: true, + referenced: true, + }); + + this.parent.renderer.component.partly_hoisted.push(b` + function ${node.handler_name.name}(event) { + @bubble($$self, event); + } + `); + } + } + + get_snippet(block) { + const snippet = this.node.expression ? this.node.expression.manipulate(block) : x`#ctx.${this.node.handler_name}`; + + if (this.node.reassigned) { + block.maintain_context = true; + return x`function () { ${snippet}.apply(this, arguments); }`; + } + return snippet; + } + + render(block: Block, target: string) { + let snippet = this.get_snippet(block); + + if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; + if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`; + if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; + + const args = []; + + const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); + if (opts.length) { + args.push((opts.length === 1 && opts[0] === 'capture') + ? TRUE + : x`{ ${opts.map(opt => p`${opt}: true`)} }`); + } else if (block.renderer.options.dev) { + args.push(FALSE); + } + + if (block.renderer.options.dev) { + args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE); + args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE); + } + + block.event_listeners.push( + x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})` + ); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 033ef85ef4..9b7bbfef74 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -24,6 +24,7 @@ import bind_this from '../shared/bind_this'; import { changed } from '../shared/changed'; import { is_head } from '../shared/is_head'; import { Identifier } from 'estree'; +import EventHandler from './EventHandler'; const events = [ { @@ -113,6 +114,7 @@ export default class ElementWrapper extends Wrapper { fragment: FragmentWrapper; attributes: AttributeWrapper[]; bindings: Binding[]; + event_handlers: EventHandler[]; class_dependencies: string[]; slot_block: Block; @@ -194,6 +196,8 @@ export default class ElementWrapper extends Wrapper { // e.g.