From d381294651eabc39063796bd9249447e05d3919a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 20 Sep 2018 15:38:27 -0400 Subject: [PATCH] some binding stuff --- src/compile/render-dom/Block.ts | 25 ++++++++----- src/compile/render-dom/wrappers/EachBlock.ts | 15 ++++---- .../render-dom/wrappers/Element/Attribute.ts | 1 - .../wrappers/Element/Binding/Binding.ts | 18 +++++----- .../Element/Binding/InputCheckboxBinding.ts | 35 +++++++++++++++++++ .../Element/Binding/InputTextBinding.ts | 12 +++++-- .../wrappers/Element/Binding/SelectBinding.ts | 10 ------ .../render-dom/wrappers/Element/index.ts | 10 +++++- src/compile/render-dom/wrappers/IfBlock.ts | 4 --- .../wrappers/InlineComponent/index.ts | 2 +- .../render-dom/wrappers/MustacheTag.ts | 2 ++ .../render-dom/wrappers/RawMustacheTag.ts | 2 ++ src/compile/render-dom/wrappers/shared/Tag.ts | 7 ++-- 13 files changed, 97 insertions(+), 46 deletions(-) create mode 100644 src/compile/render-dom/wrappers/Element/Binding/InputCheckboxBinding.ts diff --git a/src/compile/render-dom/Block.ts b/src/compile/render-dom/Block.ts index 8183d69340..1766b41a66 100644 --- a/src/compile/render-dom/Block.ts +++ b/src/compile/render-dom/Block.ts @@ -9,7 +9,7 @@ export interface BlockOptions { renderer?: Renderer; comment?: string; key?: string; - bindings?: Map; + bindings?: Map string>; dependencies?: Set; } @@ -26,7 +26,7 @@ export default class Block { dependencies: Set; - bindings: Map; + bindings: Map string>; builders: { init: CodeBuilder; @@ -109,30 +109,37 @@ export default class Block { const seen = new Set(); const dupes = new Set(); - this.wrappers.forEach(wrapper => { - if (!wrapper.var) return; - if (wrapper.parent && wrapper.parent.canUseInnerHTML) return; + let i = this.wrappers.length; + + while (i--) { + const wrapper = this.wrappers[i]; + + if (!wrapper.var) continue; + if (wrapper.parent && wrapper.parent.canUseInnerHTML) continue; if (seen.has(wrapper.var)) { dupes.add(wrapper.var); } seen.add(wrapper.var); - }); + } const counts = new Map(); + i = this.wrappers.length; + + while (i--) { + const wrapper = this.wrappers[i]; - this.wrappers.forEach(wrapper => { if (!wrapper.var) return; if (dupes.has(wrapper.var)) { const i = counts.get(wrapper.var) || 0; - wrapper.var = this.getUniqueName(wrapper.var + i); counts.set(wrapper.var, i + 1); + wrapper.var = this.getUniqueName(wrapper.var + i); } else { wrapper.var = this.getUniqueName(wrapper.var); } - }); + } } addDependencies(dependencies: Set) { diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts index e04f3cbb39..8a26a71b8e 100644 --- a/src/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -49,9 +49,12 @@ export default class EachBlockWrapper extends Wrapper { // TODO this seems messy this.block.hasAnimation = this.node.hasAnimation; - // node.contexts.forEach(prop => { - // this.block.bindings.set(prop.key.name, `ctx.${this.vars.each_block_value}[ctx.${indexName}]${prop.tail}`); - // }); + this.indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`); + + node.contexts.forEach(prop => { + // TODO this doesn't feel great + this.block.bindings.set(prop.key.name, () => `ctx.${this.vars.each_block_value}[ctx.${this.indexName}]${prop.tail}`); + }); if (this.node.index) { this.block.getUniqueName(this.node.index); // this prevents name collisions (#1254) @@ -59,7 +62,7 @@ export default class EachBlockWrapper extends Wrapper { renderer.blocks.push(this.block); - this.fragment = new FragmentWrapper(renderer, block, node.children, this, stripWhitespace, nextSibling); + this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, stripWhitespace, nextSibling); block.addDependencies(this.block.dependencies); this.block.hasUpdateMethod = this.block.dependencies.size > 0; // TODO should this logic be in Block? @@ -116,12 +119,10 @@ export default class EachBlockWrapper extends Wrapper { this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`); - const indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`); - // TODO only add these if necessary this.contextProps.push( `child_ctx.${this.vars.each_block_value} = list;`, - `child_ctx.${indexName} = i;` + `child_ctx.${this.indexName} = i;` ); const { snippet } = this.node.expression; diff --git a/src/compile/render-dom/wrappers/Element/Attribute.ts b/src/compile/render-dom/wrappers/Element/Attribute.ts index 85f7ad71f4..414f4fe924 100644 --- a/src/compile/render-dom/wrappers/Element/Attribute.ts +++ b/src/compile/render-dom/wrappers/Element/Attribute.ts @@ -3,7 +3,6 @@ import Block from '../../Block'; import fixAttributeCasing from '../../../../utils/fixAttributeCasing'; import ElementWrapper from './index'; import { stringify } from '../../../../utils/stringify'; -import Wrapper from '../shared/wrapper'; import deindent from '../../../../utils/deindent'; export default class AttributeWrapper { diff --git a/src/compile/render-dom/wrappers/Element/Binding/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding/Binding.ts index 1cc9d37ee9..7ae3561a8b 100644 --- a/src/compile/render-dom/wrappers/Element/Binding/Binding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding/Binding.ts @@ -214,13 +214,15 @@ export default class BindingWrapper { updateConditions.length ? `if (${updateConditions.join(' && ')}) ${this.updateDom}` : this.updateDom ); - block.builders.hydrate.addLine( - `@addListener(${this.element.var}, "${name}", ${handler_name});` - ); - - block.builders.destroy.addLine( - `@removeListener(${this.element.var}, "${name}", ${handler_name});` - ); + this.events.forEach(name => { + block.builders.hydrate.addLine( + `@addListener(${this.element.var}, "${name}", ${handler_name});` + ); + + block.builders.destroy.addLine( + `@removeListener(${this.element.var}, "${name}", ${handler_name});` + ); + }); } } @@ -247,7 +249,7 @@ function getEventHandler( usesContext: true, usesState: true, usesStore: storeDependencies.length > 0, - mutation: `${head}${tail} = ${value};`, + mutation: `${head()}${tail} = ${value};`, props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`), storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`) }; diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxBinding.ts new file mode 100644 index 0000000000..77dc395859 --- /dev/null +++ b/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxBinding.ts @@ -0,0 +1,35 @@ +import Binding from '../../../../nodes/Binding'; +import Element from '../../../../nodes/Element'; +import ElementWrapper from '..'; +import BindingWrapper from './Binding'; + +export default class InputCheckboxBinding extends BindingWrapper { + events = ['change']; + + static filter( + node: Element, + binding_lookup: Record, + type: string + ) { + return ( + node.name === 'input' && + binding_lookup.checked && + type === 'checkbox' + ); + } + + constructor( + element: ElementWrapper, + binding_lookup: Record + ) { + super(element, binding_lookup.checked); + } + + fromDom() { + return `${this.element.var}.checked`; + } + + toDom() { + return `${this.element.var}.checked = ${this.binding.value.snippet};`; + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputTextBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputTextBinding.ts index 63e3fb0f06..fd1dc096b9 100644 --- a/src/compile/render-dom/wrappers/Element/Binding/InputTextBinding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding/InputTextBinding.ts @@ -1,10 +1,11 @@ import Binding from '../../../../nodes/Binding'; import Element from '../../../../nodes/Element'; import ElementWrapper from '..'; -import Block from '../../../Block'; import BindingWrapper from './Binding'; export default class InputTextBinding extends BindingWrapper { + events = ['input']; + static filter( node: Element, binding_lookup: Record, @@ -23,10 +24,15 @@ export default class InputTextBinding extends BindingWrapper { element: ElementWrapper, binding_lookup: Record ) { - super(element); + super(element, binding_lookup.value); + this.needsLock = true; } - render(block: Block) { + fromDom() { + return `${this.element.var}.value`; + } + toDom() { + return `${this.element.var}.value = ${this.binding.value.snippet};`; } } \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/Binding/SelectBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/SelectBinding.ts index 6a9cabf34e..5da42af3ff 100644 --- a/src/compile/render-dom/wrappers/Element/Binding/SelectBinding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding/SelectBinding.ts @@ -1,10 +1,6 @@ import Binding from '../../../../nodes/Binding'; import Element from '../../../../nodes/Element'; import ElementWrapper from '..'; -import Block from '../../../Block'; -import Renderer from '../../../Renderer'; -import flattenReference from '../../../../../utils/flattenReference'; -import { Node } from '../../../../../interfaces'; import BindingWrapper from './Binding'; export default class SelectBinding extends BindingWrapper { @@ -47,10 +43,4 @@ export default class SelectBinding extends BindingWrapper { `@selectOptions(${this.element.var}, ${this.binding.value.snippet});` : `@selectOption(${this.element.var}, ${this.binding.value.snippet});`; } - - render(block: Block) { - - - super.render(block); - } } \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 6cf61b2140..cf7434272c 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -3,7 +3,6 @@ import Element from '../../../nodes/Element'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import Node from '../../../nodes/shared/Node'; -import { CompileOptions } from '../../../../interfaces'; import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../../utils/quoteIfNecessary'; import isVoidElementName from '../../../../utils/isVoidElementName'; import FragmentWrapper from '../Fragment'; @@ -15,6 +14,7 @@ import namespaces from '../../../../utils/namespaces'; import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import { dimensions } from '../../../../utils/patterns'; +import InputCheckboxBinding from './Binding/InputCheckboxBinding'; import InputTextBinding from './Binding/InputTextBinding'; import InputRadioGroupBinding from './Binding/InputRadioGroupBinding'; import SelectBinding from './Binding/SelectBinding'; @@ -22,6 +22,7 @@ import SelectBinding from './Binding/SelectBinding'; const bindings = [ InputTextBinding, InputRadioGroupBinding, + InputCheckboxBinding, SelectBinding ]; @@ -124,9 +125,11 @@ export default class ElementWrapper extends Wrapper { return new AttributeWrapper(attribute, this); }); + let has_bindings; const binding_lookup = {}; this.node.bindings.forEach(binding => { binding_lookup[binding.name] = binding; + has_bindings = true; }); const type = this.node.getStaticAttributeValue('type'); @@ -140,6 +143,11 @@ export default class ElementWrapper extends Wrapper { }) .map(Binding => new Binding(this, binding_lookup)); + // TODO remove this, it's just useful during refactoring + if (has_bindings && !this.bindings.length) { + throw new Error(`no binding was created`); + } + this.fragment = new FragmentWrapper(renderer, block, node.children, this, stripWhitespace, nextSibling); } diff --git a/src/compile/render-dom/wrappers/IfBlock.ts b/src/compile/render-dom/wrappers/IfBlock.ts index 70fd1a793d..53d2d77a3c 100644 --- a/src/compile/render-dom/wrappers/IfBlock.ts +++ b/src/compile/render-dom/wrappers/IfBlock.ts @@ -14,10 +14,6 @@ function isElseIf(node: ElseBlock) { ); } -function isElseBranch(branch) { - return branch.block && !branch.condition; -} - class IfBlockBranch extends Wrapper { block: Block; fragment: FragmentWrapper; diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index e208a5de7b..11e1adaf03 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -204,7 +204,7 @@ export default class InlineComponentWrapper extends Wrapper { const lhs = binding.value.node.type === 'MemberExpression' ? binding.value.snippet - : `${head}${tail} = childState${quotePropIfNecessary(binding.name)}`; + : `${head()}${tail} = childState${quotePropIfNecessary(binding.name)}`; setFromChild = deindent` ${lhs} = childState${quotePropIfNecessary(binding.name)}; diff --git a/src/compile/render-dom/wrappers/MustacheTag.ts b/src/compile/render-dom/wrappers/MustacheTag.ts index 7b966d0be3..433974db87 100644 --- a/src/compile/render-dom/wrappers/MustacheTag.ts +++ b/src/compile/render-dom/wrappers/MustacheTag.ts @@ -4,6 +4,8 @@ import Node from '../../nodes/shared/Node'; import Tag from './shared/Tag'; export default class MustacheTagWrapper extends Tag { + var = 'text'; + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { super(renderer, block, parent, node); this.cannotUseInnerHTML(); diff --git a/src/compile/render-dom/wrappers/RawMustacheTag.ts b/src/compile/render-dom/wrappers/RawMustacheTag.ts index df0b1bf978..f4ce1d8d7d 100644 --- a/src/compile/render-dom/wrappers/RawMustacheTag.ts +++ b/src/compile/render-dom/wrappers/RawMustacheTag.ts @@ -6,6 +6,8 @@ import Wrapper from './shared/wrapper'; import deindent from '../../../utils/deindent'; export default class RawMustacheTagWrapper extends Tag { + var = 'raw'; + constructor( renderer: Renderer, block: Block, diff --git a/src/compile/render-dom/wrappers/shared/Tag.ts b/src/compile/render-dom/wrappers/shared/Tag.ts index dc4eb0bb84..b0b5ec01c3 100644 --- a/src/compile/render-dom/wrappers/shared/Tag.ts +++ b/src/compile/render-dom/wrappers/shared/Tag.ts @@ -2,13 +2,16 @@ import Wrapper from './Wrapper'; import Renderer from '../../Renderer'; import Block from '../../Block'; import Node from '../../../nodes/shared/Node'; +import MustacheTag from '../../../nodes/MustacheTag'; +import RawMustacheTag from '../../../nodes/RawMustacheTag'; export default class Tag extends Wrapper { - constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) { + node: MustacheTag | RawMustacheTag; + + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { super(renderer, block, parent, node); this.cannotUseInnerHTML(); - this.var = this.type === 'MustacheTag' ? 'text' : 'raw'; block.addDependencies(node.expression.dependencies); }