diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts index 8a26a71b8e..b35eee7536 100644 --- a/src/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -6,11 +6,53 @@ import createDebuggingComment from '../../../utils/createDebuggingComment'; import EachBlock from '../../nodes/EachBlock'; import FragmentWrapper from './Fragment'; import deindent from '../../../utils/deindent'; +import ElseBlock from '../../nodes/ElseBlock'; + +class ElseBlockWrapper extends Wrapper { + node: ElseBlock; + block: Block; + fragment: FragmentWrapper; + isDynamic: boolean; + + var = null; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: ElseBlock, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + + this.block = block.child({ + comment: createDebuggingComment(node, this.renderer.component), + name: this.renderer.component.getUniqueName(`create_else_block`) + }); + + this.fragment = new FragmentWrapper( + renderer, + this.block, + this.node.children, + parent, + stripWhitespace, + nextSibling + ); + + this.isDynamic = this.block.dependencies.size > 0; + if (this.isDynamic) { + // TODO this can't be right + this.block.hasUpdateMethod = true; + } + } +} export default class EachBlockWrapper extends Wrapper { block: Block; node: EachBlock; fragment: FragmentWrapper; + else?: ElseBlockWrapper; var: string; vars: { anchor: string; @@ -63,24 +105,27 @@ export default class EachBlockWrapper extends Wrapper { renderer.blocks.push(this.block); 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? - if (this.else) { - this.else.block = block.child({ - comment: createDebuggingComment(this.else, this.renderer.component), - name: renderer.component.getUniqueName(`${this.block.name}_else`), - }); - - renderer.blocks.push(this.else.block); - this.else.initChildren( - this.else.block, + if (this.node.else) { + this.else = new ElseBlockWrapper( + renderer, + block, + this, + this.node.else, stripWhitespace, nextSibling ); - this.else.block.hasUpdateMethod = this.else.block.dependencies.size > 0; + + renderer.blocks.push(this.else.block); + + if (this.else.isDynamic) { + this.block.addDependencies(this.else.block.dependencies); + } } + block.addDependencies(this.block.dependencies); + this.block.hasUpdateMethod = this.block.dependencies.size > 0; // TODO should this logic be in Block? + if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) { block.addOutro(); } @@ -172,7 +217,7 @@ export default class EachBlockWrapper extends Wrapper { } `); - const initialMountNode = parentNode || `${anchor}.parentNode`; + const initialMountNode = parentNode || `${this.vars.anchor}.parentNode`; if (this.else.block.hasUpdateMethod) { block.builders.update.addBlock(deindent` @@ -197,7 +242,7 @@ export default class EachBlockWrapper extends Wrapper { } else if (!${each_block_else}) { ${each_block_else} = ${this.else.block.name}(#component, ctx); ${each_block_else}.c(); - ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor}); + ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor}); } `); } @@ -207,14 +252,10 @@ export default class EachBlockWrapper extends Wrapper { `); } - this.fragment.nodes.forEach((child: Wrapper) => { - child.render(this.block, null, 'nodes'); - }); + this.fragment.render(this.block, null, 'nodes'); if (this.else) { - this.else.children.forEach((child: Node) => { - child.build(this.else.block, null, 'nodes'); - }); + this.else.fragment.render(this.else.block, null, 'nodes'); } } diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxGroupBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxGroupBinding.ts new file mode 100644 index 0000000000..6aa4038bd7 --- /dev/null +++ b/src/compile/render-dom/wrappers/Element/Binding/InputCheckboxGroupBinding.ts @@ -0,0 +1,29 @@ +import Binding from '../../../../nodes/Binding'; +import Element from '../../../../nodes/Element'; +import ElementWrapper from '..'; +import InputRadioGroupBinding from './InputRadioGroupBinding'; + +export default class InputCheckboxGroupBinding extends InputRadioGroupBinding { + element: ElementWrapper; + node: Binding; + + static filter( + node: Element, + binding_lookup: Record, + type: string + ) { + if (node.name === 'input' && binding_lookup.group) { + return type === 'checkbox'; + } + } + + fromDom() { + return `@getBindingGroupValue(#component._bindingGroups[${this.bindingGroup}])`; + } + + toDom() { + const condition = `~${this.binding.value.snippet}.indexOf(${this.element.var}.__value)`; + + return `${this.element.var}.checked = ${condition};` + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/Binding/InputRadioGroupBinding.ts b/src/compile/render-dom/wrappers/Element/Binding/InputRadioGroupBinding.ts index 8fce43f674..f8dc431106 100644 --- a/src/compile/render-dom/wrappers/Element/Binding/InputRadioGroupBinding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding/InputRadioGroupBinding.ts @@ -10,6 +10,9 @@ import BindingWrapper from './Binding'; export default class InputRadioGroupBinding extends BindingWrapper { element: ElementWrapper; node: Binding; + bindingGroup: number; + + events = ['change']; static filter( node: Element, @@ -26,59 +29,41 @@ export default class InputRadioGroupBinding extends BindingWrapper { binding_lookup: Record ) { super(element, binding_lookup.group); - this.events = ['change']; - } - fromDom() { - const bindingGroup = getBindingGroup(this.element.renderer, this.binding.value.node); - if (this.element.node.getStaticAttributeValue('type') === 'checkbox') { - return `@getBindingGroupValue(#component._bindingGroups[${bindingGroup}])`; + // TODO handle cases involving computed member expressions + const { parts } = flattenReference(this.binding.value.node); + const keypath = parts.join('.'); + + // TODO handle contextual bindings — `keypath` should include unique ID of + // each block that provides context + let index = element.renderer.bindingGroups.indexOf(keypath); + if (index === -1) { + index = element.renderer.bindingGroups.length; + element.renderer.bindingGroups.push(keypath); } + this.bindingGroup = index; + } + + fromDom() { return `${this.element.var}.__value`; } toDom() { - const type = this.element.node.getStaticAttributeValue('type'); - - const condition = type === 'checkbox' - ? `~${this.binding.value.snippet}.indexOf(${this.element.var}.__value)` - : `${this.element.var}.__value === ${this.binding.value.snippet}`; + const condition = `${this.element.var}.__value === ${this.binding.value.snippet}`; return `${this.element.var}.checked = ${condition};` } render(block: Block) { - const bindingGroup = getBindingGroup( - this.element.renderer, - this.binding.value.node - ); - block.builders.hydrate.addLine( - `#component._bindingGroups[${bindingGroup}].push(${this.element.var});` + `#component._bindingGroups[${this.bindingGroup}].push(${this.element.var});` ); block.builders.destroy.addLine( - `#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${this.element.var}), 1);` + `#component._bindingGroups[${this.bindingGroup}].splice(#component._bindingGroups[${this.bindingGroup}].indexOf(${this.element.var}), 1);` ); super.render(block); - - // this.renderHandler(block, 'TODO'); } -} - -function getBindingGroup(renderer: Renderer, value: Node) { - const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions - const keypath = parts.join('.'); - - // TODO handle contextual bindings — `keypath` should include unique ID of - // each block that provides context - let index = renderer.bindingGroups.indexOf(keypath); - if (index === -1) { - index = renderer.bindingGroups.length; - renderer.bindingGroups.push(keypath); - } - - return index; } \ 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 b26fc41b97..7e74c1ebb7 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -15,6 +15,7 @@ import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import { dimensions } from '../../../../utils/patterns'; import InputCheckboxBinding from './Binding/InputCheckboxBinding'; +import InputCheckboxGroupBinding from './Binding/InputCheckboxGroupBinding'; import InputNumberBinding from './Binding/InputNumberBinding'; import InputRangeBinding from './Binding/InputRangeBinding'; import InputTextBinding from './Binding/InputTextBinding'; @@ -25,6 +26,7 @@ const bindings = [ InputTextBinding, InputRadioGroupBinding, InputCheckboxBinding, + InputCheckboxGroupBinding, InputNumberBinding, InputRangeBinding, SelectBinding @@ -118,7 +120,7 @@ export default class ElementWrapper extends Wrapper { nextSibling: Wrapper ) { super(renderer, block, parent, node); - this.var = node.name; + this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_') this.classDependencies = []; diff --git a/src/compile/render-dom/wrappers/Fragment.ts b/src/compile/render-dom/wrappers/Fragment.ts index 0adbad4159..ab29841bbf 100644 --- a/src/compile/render-dom/wrappers/Fragment.ts +++ b/src/compile/render-dom/wrappers/Fragment.ts @@ -117,7 +117,10 @@ export default class FragmentWrapper { first.data = trimStart(first.data); if (!first.data) { this.nodes.shift(); - link(null, this.nodes[0]); + + if (this.nodes.length) { + link(null, this.nodes[0]); + } } } }