From a7b2e97b16281de4498f4fd24d49a908bc865117 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 15 Aug 2024 17:03:06 +0200 Subject: [PATCH] Removes bindValue, bindChecked. Makes initial render of selects work --- literal-html/test.html | 47 ++++++++++++++++++++- modules/renderer/renderer-boolean.js | 4 +- modules/renderer/renderer-checked.js | 1 - modules/renderer/renderer-value.js | 63 +++++++++------------------- modules/stash.js | 31 ++++++++++++++ scope/bind-checked.js | 33 --------------- scope/bind-value.js | 33 --------------- 7 files changed, 98 insertions(+), 114 deletions(-) create mode 100644 modules/stash.js delete mode 100644 scope/bind-checked.js delete mode 100644 scope/bind-value.js diff --git a/literal-html/test.html b/literal-html/test.html index 8ca8e97..7da8e75 100644 --- a/literal-html/test.html +++ b/literal-html/test.html @@ -181,7 +181,50 @@

#template-7

Tests

-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/renderer/renderer-boolean.js b/modules/renderer/renderer-boolean.js index 0862b60..0419811 100644 --- a/modules/renderer/renderer-boolean.js +++ b/modules/renderer/renderer-boolean.js @@ -14,7 +14,7 @@ attribute. function setBooleanProperty(node, name, value) { if (node[name] === !!value) return; node[name] = !!value; - if (window.DEUBG) ++stats.property; + if (window.DEBUG) ++stats.property; } function setBooleanAttribute(node, name, value) { @@ -22,7 +22,7 @@ function setBooleanAttribute(node, name, value) { if ((node.getAttribute(name) !== null)) { if (value) return; node.removeAttribute(name); - if (window.DEUBG) ++stats.attribute; + if (window.DEBUG) ++stats.attribute; return; } diff --git a/modules/renderer/renderer-checked.js b/modules/renderer/renderer-checked.js index 70bba09..71cf75f 100644 --- a/modules/renderer/renderer-checked.js +++ b/modules/renderer/renderer-checked.js @@ -4,7 +4,6 @@ import isDefined from 'fn/is-defined.js'; import Signal from 'fn/signal.js'; import trigger from 'dom/trigger.js'; import config from '../config.js'; -import bindChecked from '../../scope/bind-checked.js'; import AttributeRenderer from './renderer-attribute.js'; import { toAttributeBoolean } from './renderer-boolean.js'; import { getValue } from './renderer-value.js'; diff --git a/modules/renderer/renderer-value.js b/modules/renderer/renderer-value.js index 51e862b..d9cb169 100644 --- a/modules/renderer/renderer-value.js +++ b/modules/renderer/renderer-value.js @@ -5,14 +5,13 @@ import overload from 'fn/overload.js'; import Signal from 'fn/signal.js'; import trigger from 'dom/trigger.js'; import config from '../config.js'; -import bindValue from '../../scope/bind-value.js'; +import { stash } from '../stash.js'; import AttributeRenderer, { toAttributeString } from './renderer-attribute.js'; import { stats } from './renderer.js'; import toText from './to-text.js'; -const A = Array.prototype; -const $value = Symbol('value'); +const A = Array.prototype; const enhancedTypes = { 'select-one': true, @@ -28,7 +27,7 @@ const enhancedTypes = { Literal provides a mechanism for setting and getting values of any type on select, checkbox and radio inputs. Where `input.value` always returns a string -(on uncustomised DOM elements, at least), getValue(element) returns the value +(on uncustomised DOM elements, at least), `stash(element)` returns the value set by a Literal template *before* it was coerced to a string. If no such value exists it falls back to returning the string. @@ -40,9 +39,9 @@ events('input', document.body) **/ function getElementValue(element) { - return $value in element ? element[$value] : + return stash.has(element) ? stash.get(element) : 'value' in element ? element.value : - element.getAttribute('value') || undefined ; + element.getAttribute('value') ; } export const getValue = overload(get('type'), { @@ -84,23 +83,19 @@ const types = { function setElementValue(element, value) { // Don't render into focused nodes, it makes the cursor jump to the // end of the field, and we should cede control to the user - if (document.activeElement === element) { - return 0; - } + if (document.activeElement === element) return; const isEnhanced = enhancedTypes[element.type]; - // If value is already set on $value expando do nothing - if (isEnhanced && $value in element && element[$value] === value) return; + // If value is already stashed do nothing + if (isEnhanced && stash.has(element) && stash.get(element) === value) return; // Refuse to set value that does not conform to input type const expectedType = types[element.type]; if (expectedType && typeof value !== expectedType) return; - // Where input is an enhanced type set object value as a $value expando - if (isEnhanced) { - element[$value] = value; - } + // Where input is an enhanced type stash object + if (isEnhanced) stash.set(element, value); // Convert to string with Literal's text rendering rules const string = toText(value); @@ -153,16 +148,6 @@ export const setValue = overload(get('type'), { }); -/** -removeValue(element) -Deletes value expando on element. -**/ - -export function removeValue(element) { - delete element[$value]; -} - - /** ValueRenderer(fn, element, unused, consts) Constructs an object responsible for rendering from a value attribute to a @@ -170,13 +155,12 @@ value property. Parameter `name` is redundant, but here for symmetry with other renderers. **/ -const toValue = overload(get('type'), { +const coercer = { //'date': composeDate, //'select-multiple': composeArray, - 'number': Number, - 'range': Number, - 'default': id -}); + 'number': Number, + 'range': Number +}; export default class ValueRenderer extends AttributeRenderer { static consts = ['DATA', 'data', 'element', 'shadow', 'host', 'id']; @@ -190,20 +174,13 @@ export default class ValueRenderer extends AttributeRenderer { Signal.evaluate(this, this.evaluate); } - render(strings) { - // If arguments contains a single expression use its value - const value = toValue(this.element.type, this.singleExpression ? - arguments[1] : + render(strings, value) { + return setValue(this.element, + this.singleExpression ? + coercer[this.element.type] ? + coercer[this.element.type](value) : + value : toAttributeString(arguments) ); - - return setValue(this.element, value); - } - - stop() { - // Guard against memory leaks by cleaning up $value expando when - // the renderer is done. - removeValue(this.element); - return super.stop(); } } diff --git a/modules/stash.js b/modules/stash.js new file mode 100644 index 0000000..cbe08c1 --- /dev/null +++ b/modules/stash.js @@ -0,0 +1,31 @@ + +/** +stash(element) +stash(element, data) + +Quick-and-dirty get or set a value on an element using a WeakMap. Useful for +stashing data for event delegation. Note that the `value` attribute renderer +uses `stash()` internally to store values on elements, which is useful when +you want an `` to have a non-string value. Where an `'input'` or +`'change'` event is delegated these values can be retrieved inside the handler +by getting `stash(input)`. +**/ + +import overload from 'fn/overload.js'; + +export const stash = new WeakMap(); + +export default overload(function() { return arguments.length; }, { + 1: (object) => { + return stash.get(object); + }, + + 2: (object, value) => { + stash.set(object, value); + return value; + }, + + default: window.DEBUG ? + () => { throw new Error('Literal: stash(element) to get stashed value, stash(element, value) to set stashed value'); } : + undefined +}); diff --git a/scope/bind-checked.js b/scope/bind-checked.js deleted file mode 100644 index 565f725..0000000 --- a/scope/bind-checked.js +++ /dev/null @@ -1,33 +0,0 @@ - -import id from 'fn/id.js'; -import isDefined from 'fn/is-defined.js'; -import set from 'fn/set.js'; -//import events from 'dom/events.js'; -//import { observe } from '../data.js'; - -export default function bindChecked(element, data, path, to, from, setChecked) { - console.warn('Literal: you are using the function bind(). This is experimental and the API may change.'); - - const attribute = element.getAttribute('value'); - const defined = isDefined(attribute); - - const inputs = events('input', element) - // Transform - .map(defined ? - (e) => from(e.target.checked ? attribute : undefined) : - (e) => from(e.target.checked) - ) - // Set value on data - .each(set(path, data)); - - return observe(path, data) - // Transform - .map(to) - // Set checked on input, bypassing usual renderer.compose() - .each(defined ? - (value) => setChecked(element, v + '' === attribute) : - (value) => setChecked(element, !!value) - ) - // Unbind events when observe stream is stopped - .done(inputs); -} diff --git a/scope/bind-value.js b/scope/bind-value.js deleted file mode 100644 index aafe24d..0000000 --- a/scope/bind-value.js +++ /dev/null @@ -1,33 +0,0 @@ - -import id from 'fn/id.js'; -import set from 'fn/set-path.js'; -//import events from 'dom/events.js'; -import { getValue, setValue } from '../modules/renderer/renderer-value.js'; -//import { observe } from '../data.js'; - -let warned; - -function getTargetValue(e) { - return getValue(e.target); -} - -export default function bindValue(element, data, path, to, from, setValue) { - if (window.DEBUG && !warned) { - warned = true; - console.warn('Literal: you are using the experimental template function bind(). Not recommended for use on many inputs, prefer delegation for that.'); - } - - const inputs = events('input', element) - // Get Literal's idea of the input value - .map(getTargetValue) - // Transform - .map(from) - // Set value on data - .each(set(path, data)); - - return observe(path, data) - // Transform - .map(to) - // Unbind events when observe stream is stopped - .done(inputs); -}