From 8cc12b73e123988b49f429be22b0586a1fae7dca Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 24 Nov 2018 13:47:31 -0500 Subject: [PATCH] allow actions on --- src/compile/nodes/Window.ts | 24 +++--- .../render-dom/wrappers/Element/index.ts | 79 +------------------ src/compile/render-dom/wrappers/Window.ts | 18 ++--- .../render-dom/wrappers/shared/addActions.ts | 48 +++++++++++ .../wrappers/shared/addEventHandlers.ts | 43 ++++++++++ .../samples/window-event-custom/main.html | 2 +- 6 files changed, 117 insertions(+), 97 deletions(-) create mode 100644 src/compile/render-dom/wrappers/shared/addActions.ts create mode 100644 src/compile/render-dom/wrappers/shared/addEventHandlers.ts diff --git a/src/compile/nodes/Window.ts b/src/compile/nodes/Window.ts index 4545ee2fca..214baf388e 100644 --- a/src/compile/nodes/Window.ts +++ b/src/compile/nodes/Window.ts @@ -4,6 +4,7 @@ import EventHandler from './EventHandler'; import flattenReference from '../../utils/flattenReference'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; +import Action from './Action'; const validBindings = [ 'innerWidth', @@ -17,15 +18,13 @@ const validBindings = [ export default class Window extends Node { type: 'Window'; - handlers: EventHandler[]; - bindings: Binding[]; + handlers: EventHandler[] = []; + bindings: Binding[] = []; + actions: Action[] = []; constructor(component, parent, scope, info) { super(component, parent, scope, info); - this.handlers = []; - this.bindings = []; - info.attributes.forEach(node => { if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node)); @@ -35,6 +34,7 @@ export default class Window extends Node { if (node.expression.type !== 'Identifier') { const { parts } = flattenReference(node.expression); + // TODO is this constraint necessary? component.error(node.expression, { code: `invalid-binding`, message: `Bindings on must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'` @@ -42,11 +42,11 @@ export default class Window extends Node { } if (!~validBindings.indexOf(node.name)) { - const match = node.name === 'width' - ? 'innerWidth' - : node.name === 'height' - ? 'innerHeight' - : fuzzymatch(node.name, validBindings); + const match = ( + node.name === 'width' ? 'innerWidth' : + node.name === 'height' ? 'innerHeight' : + fuzzymatch(node.name, validBindings) + ); const message = `'${node.name}' is not a valid binding on `; @@ -66,6 +66,10 @@ export default class Window extends Node { this.bindings.push(new Binding(component, this, scope, node)); } + else if (node.type === 'Action') { + this.actions.push(new Action(component, this, scope, node)); + } + else { // TODO there shouldn't be anything else here... } diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 76fa9babd1..045d47cb1b 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -17,6 +17,8 @@ import { dimensions } from '../../../../utils/patterns'; import Binding from './Binding'; import InlineComponentWrapper from '../InlineComponent'; import addToSet from '../../../../utils/addToSet'; +import addEventHandlers from '../shared/addEventHandlers'; +import addActions from '../shared/addActions'; const events = [ { @@ -629,43 +631,7 @@ export default class ElementWrapper extends Wrapper { } addEventHandlers(block: Block) { - const { renderer } = this; - const { component } = renderer; - - this.node.handlers.forEach(handler => { - const modifiers = []; - if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();'); - if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();'); - - const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod)); - if (opts.length) { - const optString = (opts.length === 1 && opts[0] === 'capture') - ? 'true' - : `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`; - - block.builders.hydrate.addLine( - `@addListener(${this.var}, "${handler.name}", ${handler.snippet}, ${optString});` - ); - - block.builders.destroy.addLine( - `@removeListener(${this.var}, "${handler.name}", ${handler.snippet}, ${optString});` - ); - } else { - block.builders.hydrate.addLine( - `@addListener(${this.var}, "${handler.name}", ${handler.snippet});` - ); - - block.builders.destroy.addLine( - `@removeListener(${this.var}, "${handler.name}", ${handler.snippet});` - ); - } - - if (handler.expression) { - handler.expression.declarations.forEach(declaration => { - block.builders.init.addBlock(declaration); - }); - } - }); + addEventHandlers(block, this.var, this.node.handlers); } addRef(block: Block) { @@ -795,44 +761,7 @@ export default class ElementWrapper extends Wrapper { } addActions(block: Block) { - this.node.actions.forEach(action => { - const { expression } = action; - let snippet, dependencies; - if (expression) { - snippet = expression.snippet; - dependencies = expression.dependencies; - - expression.declarations.forEach(declaration => { - block.builders.init.addBlock(declaration); - }); - } - - const name = block.getUniqueName( - `${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` - ); - - block.addVariable(name); - const fn = `ctx.${action.name}`; - - block.builders.mount.addLine( - `${name} = ${fn}.call(null, ${this.var}${snippet ? `, ${snippet}` : ''}) || {};` - ); - - if (dependencies && dependencies.size > 0) { - let conditional = `typeof ${name}.update === 'function' && `; - const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || '); - conditional += dependencies.size > 1 ? `(${deps})` : deps; - - block.builders.update.addConditional( - conditional, - `${name}.update.call(null, ${snippet});` - ); - } - - block.builders.destroy.addLine( - `if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();` - ); - }); + addActions(block, this.var, this.node.actions); } addClasses(block: Block) { diff --git a/src/compile/render-dom/wrappers/Window.ts b/src/compile/render-dom/wrappers/Window.ts index dfaaaa56ec..ffa8e87b53 100644 --- a/src/compile/render-dom/wrappers/Window.ts +++ b/src/compile/render-dom/wrappers/Window.ts @@ -3,6 +3,9 @@ import Block from '../Block'; import Node from '../../nodes/shared/Node'; import Wrapper from './shared/Wrapper'; import deindent from '../../../utils/deindent'; +import addEventHandlers from './shared/addEventHandlers'; +import Window from '../../nodes/Window'; +import addActions from './shared/addActions'; const associatedEvents = { innerWidth: 'resize', @@ -28,6 +31,8 @@ const readonly = new Set([ ]); export default class WindowWrapper extends Wrapper { + node: Window; + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { super(renderer, block, parent, node); } @@ -39,17 +44,8 @@ export default class WindowWrapper extends Wrapper { const events = {}; const bindings: Record = {}; - this.node.handlers.forEach(handler => { - const { snippet } = handler.expression; - - block.builders.init.addLine( - `window.addEventListener("${handler.name}", ${snippet});` - ); - - block.builders.destroy.addLine( - `window.removeEventListener("${handler.name}", ${snippet});` - ); - }); + addActions(block, 'window', this.node.actions); + addEventHandlers(block, 'window', this.node.handlers); this.node.bindings.forEach(binding => { // in dev mode, throw if read-only values are written to diff --git a/src/compile/render-dom/wrappers/shared/addActions.ts b/src/compile/render-dom/wrappers/shared/addActions.ts new file mode 100644 index 0000000000..7c4e41e5a9 --- /dev/null +++ b/src/compile/render-dom/wrappers/shared/addActions.ts @@ -0,0 +1,48 @@ +import Renderer from '../../Renderer'; +import Block from '../../Block'; +import Action from '../../../nodes/Action'; + +export default function addActions( + block: Block, + target: string, + actions: Action[] +) { + actions.forEach(action => { + const { expression } = action; + let snippet, dependencies; + if (expression) { + snippet = expression.snippet; + dependencies = expression.dependencies; + + expression.declarations.forEach(declaration => { + block.builders.init.addBlock(declaration); + }); + } + + const name = block.getUniqueName( + `${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` + ); + + block.addVariable(name); + const fn = `ctx.${action.name}`; + + block.builders.mount.addLine( + `${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` + ); + + if (dependencies && dependencies.size > 0) { + let conditional = `typeof ${name}.update === 'function' && `; + const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || '); + conditional += dependencies.size > 1 ? `(${deps})` : deps; + + block.builders.update.addConditional( + conditional, + `${name}.update.call(null, ${snippet});` + ); + } + + block.builders.destroy.addLine( + `if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();` + ); + }); +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/shared/addEventHandlers.ts b/src/compile/render-dom/wrappers/shared/addEventHandlers.ts new file mode 100644 index 0000000000..3125ce65da --- /dev/null +++ b/src/compile/render-dom/wrappers/shared/addEventHandlers.ts @@ -0,0 +1,43 @@ +import Block from '../../Block'; +import EventHandler from '../../../nodes/EventHandler'; + +export default function addEventHandlers( + block: Block, + target: string, + handlers: EventHandler[] +) { + handlers.forEach(handler => { + const modifiers = []; + if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();'); + if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();'); + + const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod)); + if (opts.length) { + const optString = (opts.length === 1 && opts[0] === 'capture') + ? 'true' + : `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`; + + block.builders.hydrate.addLine( + `@addListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});` + ); + + block.builders.destroy.addLine( + `@removeListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});` + ); + } else { + block.builders.hydrate.addLine( + `@addListener(${target}, "${handler.name}", ${handler.snippet});` + ); + + block.builders.destroy.addLine( + `@removeListener(${target}, "${handler.name}", ${handler.snippet});` + ); + } + + if (handler.expression) { + handler.expression.declarations.forEach(declaration => { + block.builders.init.addBlock(declaration); + }); + } + }); +} \ No newline at end of file diff --git a/test/runtime/samples/window-event-custom/main.html b/test/runtime/samples/window-event-custom/main.html index a94c79105f..ce453d9334 100644 --- a/test/runtime/samples/window-event-custom/main.html +++ b/test/runtime/samples/window-event-custom/main.html @@ -14,6 +14,6 @@ } - +

escaped: {escaped}

\ No newline at end of file