From 27871e316c903b1cb9a74636d93e05d327c4ddb0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 Nov 2017 13:04:32 -0500 Subject: [PATCH] tidy up --- .../dom/visitors/Element/addBindings.ts | 210 ++++-------------- src/shared/dom.js | 18 ++ src/utils/getStaticAttributeValue.ts | 1 + 3 files changed, 59 insertions(+), 170 deletions(-) diff --git a/src/generators/dom/visitors/Element/addBindings.ts b/src/generators/dom/visitors/Element/addBindings.ts index b7d3eca980..4cffc81300 100644 --- a/src/generators/dom/visitors/Element/addBindings.ts +++ b/src/generators/dom/visitors/Element/addBindings.ts @@ -37,7 +37,6 @@ export default function addBindings( node: Node ) { const bindings: Node[] = node.attributes.filter((a: Node) => a.type === 'Binding'); - if (bindings.length === 0) return; types[node.name](generator, block, state, node, bindings); @@ -143,19 +142,13 @@ function addInputBinding( ); } - node.initialUpdate = updateElement; + block.builders.update.addBlock( + needsLock ? + `if (!${lock}) ${updateElement}` : + updateElement + ); - if (updateConditions.length) { - block.builders.update.addBlock(deindent` - if (${updateConditions.join(' && ')}) { - ${updateElement} - } - `); - } else { - block.builders.update.addBlock(deindent` - ${updateElement} - `); - } + node.initialUpdate = updateElement; } function addSelectBinding( @@ -177,181 +170,59 @@ function addSelectBinding( state.allUsedContexts.push(context); }); - const eventNames = getBindingEventName(node, attribute); + const lock = `#${node.var}_updating`; + block.addVariable(lock, 'false'); + const handler = block.getUniqueName( - `${node.var}_${eventNames.join('_')}_handler` + `${node.var}_change_handler` ); - const isMultipleSelect = - node.name === 'select' && - node.attributes.find( - (attr: Node) => attr.name.toLowerCase() === 'multiple' - ); // TODO use getStaticAttributeValue - const type = getStaticAttributeValue(node, 'type'); - const bindingGroup = attribute.name === 'group' - ? getBindingGroup(generator, attribute.value) - : null; - const isMediaElement = node.name === 'audio' || node.name === 'video'; - const isReadOnly = isMediaElement && readOnlyMediaAttributes.has(attribute.name) + const isMultipleSelect = getStaticAttributeValue(node, 'multiple') === true; - const value = getBindingValue( - generator, - block, - state, - node, - attribute, - isMultipleSelect, - isMediaElement, - bindingGroup, - type - ); + // view to model + const value = isMultipleSelect ? + `[].map.call(${node.var}.querySelectorAll(':checked'), function(option) { return option.__value; })` : + `selectedOption && selectedOption.__value`; let setter = getSetter(generator, block, name, snippet, node, attribute, dependencies, value); - let updateElement = `${node.var}.${attribute.name} = ${snippet};`; - const needsLock = !isReadOnly && node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others? - const lock = `#${node.var}_updating`; - let updateConditions = needsLock ? [`!${lock}`] : []; - - if (needsLock) block.addVariable(lock, 'false'); - - // special case - if (type === 'radio') { - setter = deindent` - if (!${node.var}.checked) return; - ${setter} - `; - } - - const condition = type === 'checkbox' - ? `~${snippet}.indexOf(${node.var}.__value)` - : `${node.var}.__value === ${snippet}`; - - block.builders.hydrate.addLine( - `#component._bindingGroups[${bindingGroup}].push(${node.var});` - ); - - block.builders.destroy.addBlock( - `#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${node.var}), 1);` - ); - - updateElement = `${node.var}.checked = ${condition};`; - } else if (isMediaElement) { - generator.hasComplexBindings = true; - block.builders.hydrate.addBlock(`#component._root._beforecreate.push(${handler});`); - - if (attribute.name === 'currentTime') { - const frame = block.getUniqueName(`${node.var}_animationframe`); - block.addVariable(frame); - setter = deindent` - cancelAnimationFrame(${frame}); - if (!${node.var}.paused) ${frame} = requestAnimationFrame(${handler}); - ${setter} - `; - - updateConditions.push(`!isNaN(${snippet})`); - } else if (attribute.name === 'paused') { - // this is necessary to prevent the audio restarting by itself - const last = block.getUniqueName(`${node.var}_paused_value`); - block.addVariable(last, 'true'); - - updateConditions = [`${last} !== (${last} = ${snippet})`]; - updateElement = `${node.var}[${last} ? "pause" : "play"]();`; - } + if (!isMultipleSelect) { + setter = deindent` + var selectedOption = ${node.var}.querySelector(':checked') || ${node.var}.options[0]; + ${setter}`; } + generator.hasComplexBindings = true; + block.builders.hydrate.addBlock( + `if (!('${name}' in state)) #component._root._beforecreate.push(${handler});` + ); + block.builders.init.addBlock(deindent` function ${handler}() { - ${needsLock && `${lock} = true;`} + ${lock} = true; ${setter} - ${needsLock && `${lock} = false;`} + ${lock} = false; } `); - if (node.name === 'input' && type === 'range') { - // need to bind to `input` and `change`, for the benefit of IE - block.builders.hydrate.addBlock(deindent` - @addListener(${node.var}, "input", ${handler}); - @addListener(${node.var}, "change", ${handler}); - `); - - block.builders.destroy.addBlock(deindent` - @removeListener(${node.var}, "input", ${handler}); - @removeListener(${node.var}, "change", ${handler}); - `); - } else { - eventNames.forEach(eventName => { - block.builders.hydrate.addLine( - `@addListener(${node.var}, "${eventName}", ${handler});` - ); + block.builders.hydrate.addLine( + `@addListener(${node.var}, "change", ${handler});` + ); - block.builders.destroy.addLine( - `@removeListener(${node.var}, "${eventName}", ${handler});` - ); - }); - } + block.builders.destroy.addLine( + `@removeListener(${node.var}, "change", ${handler});` + ); - if (!isMediaElement) { - node.initialUpdate = updateElement; - } + // model to view + const updateElement = isMultipleSelect ? + `@selectOptions(${node.var}, ${snippet});` : + `@selectOption(${node.var}, ${snippet});`; - if (!isReadOnly) { // audio/video duration is read-only, it never updates - if (updateConditions.length) { - block.builders.update.addBlock(deindent` - if (${updateConditions.join(' && ')}) { - ${updateElement} - } - `); - } else { - block.builders.update.addBlock(deindent` - ${updateElement} - `); - } - } + block.builders.update.addLine( + `if (!${lock}) ${updateElement}` + ); - if (attribute.name === 'paused') { - block.builders.create.addLine( - `@addListener(${node.var}, "play", ${handler});` - ); - block.builders.destroy.addLine( - `@removeListener(${node.var}, "play", ${handler});` - ); - } + node.initialUpdate = updateElement; } function addMediaBinding( @@ -405,7 +276,6 @@ function addMediaBinding( let setter = getSetter(generator, block, name, snippet, node, attribute, dependencies, value); let updateElement = `${node.var}.${attribute.name} = ${snippet};`; - const needsLock = !isReadOnly && node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others? const lock = `#${node.var}_updating`; let updateConditions = needsLock ? [`!${lock}`] : []; diff --git a/src/shared/dom.js b/src/shared/dom.js index 29cf58e12c..56655c14a2 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -148,4 +148,22 @@ export function setInputType(input, type) { export function setStyle(node, key, value) { node.style.setProperty(key, value); +} + +export function selectOption(select, value) { + for (var i = 0; i < select.options.length; i += 1) { + var option = select.options[i]; + + if (option.__value === value) { + option.selected = true; + return; + } + } +} + +export function selectOptions(select, value) { + for (var i = 0; i < select.options.length; i += 1) { + var option = select.options[i]; + option.selected = ~value.indexOf(option.__value); + } } \ No newline at end of file diff --git a/src/utils/getStaticAttributeValue.ts b/src/utils/getStaticAttributeValue.ts index 67bea12259..c356d84c35 100644 --- a/src/utils/getStaticAttributeValue.ts +++ b/src/utils/getStaticAttributeValue.ts @@ -7,6 +7,7 @@ export default function getStaticAttributeValue(node: Node, name: string) { if (!attribute) return null; + if (attribute.value === true) return true; if (attribute.value.length === 0) return ''; if (attribute.value.length === 1 && attribute.value[0].type === 'Text') {