binding stuff

pull/3539/head
Richard Harris 6 years ago
parent b9b72a9f45
commit d01669ebec

@ -4,6 +4,7 @@ import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import {dimensions} from "../../utils/patterns"; import {dimensions} from "../../utils/patterns";
import { Node as ESTreeNode } from 'estree';
import { x } from 'code-red'; import { x } from 'code-red';
// TODO this should live in a specific binding // TODO this should live in a specific binding
@ -18,6 +19,7 @@ export default class Binding extends Node {
type: 'Binding'; type: 'Binding';
name: string; name: string;
expression: Expression; expression: Expression;
raw_expression: ESTreeNode; // TODO exists only for bind:this — is there a more elegant solution?
is_contextual: boolean; is_contextual: boolean;
obj: string; obj: string;
prop: string; prop: string;
@ -35,6 +37,7 @@ export default class Binding extends Node {
this.name = info.name; this.name = info.name;
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = JSON.parse(JSON.stringify(info.expression));
let obj; let obj;
let prop; let prop;

@ -416,7 +416,7 @@ export default function dom(
${inject_state && x`$$self.$inject_state = ${inject_state};`} ${inject_state && x`$$self.$inject_state = ${inject_state};`}
${injected.length && `let ${injected.join(', ')};`} ${injected.map(name => b`let ${name};`)}
${reactive_declarations.length > 0 && b` ${reactive_declarations.length > 0 && b`
$$self.$$.update = ($$dirty = ${reactive_dependencies}) => { $$self.$$.update = ($$dirty = ${reactive_dependencies}) => {

@ -7,7 +7,7 @@ import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import { changed } from '../shared/changed'; import { changed } from '../shared/changed';
import { Node, Identifier } from 'estree'; import { Node, Identifier, MemberExpression } from 'estree';
export default class BindingWrapper { export default class BindingWrapper {
node: Binding; node: Binding;
@ -51,12 +51,8 @@ export default class BindingWrapper {
this.object = get_object(this.node.expression.node).name; this.object = get_object(this.node.expression.node).name;
// TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script>
const contextless_snippet = JSON.parse(JSON.stringify(this.node.expression.node));
// view to model // view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, contextless_snippet); this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression);
this.snippet = this.node.expression.manipulate(block); this.snippet = this.node.expression.manipulate(block);
@ -243,59 +239,95 @@ function get_event_handler(
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
name: string, name: string,
snippet: Node lhs: Node
): { ): {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: (Node | Node[]);
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
snippet?: Node; lhs?: Node;
} { } {
const value = get_value_from_dom(renderer, binding.parent, binding); const value = get_value_from_dom(renderer, binding.parent, binding);
let store = binding.object[0] === '$' ? binding.object.slice(1) : null; const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
let tail = null; const context = block.bindings.get(name);
if (binding.node.expression.node.type === 'MemberExpression') {
// const { start, end } = get_tail(binding.node.expression.node);
// tail = renderer.component.source.slice(start, end);
tail = binding.node.expression.node.object;
}
if (binding.node.is_contextual) { if (context) {
const binding = block.bindings.get(name); const { object, property } = context;
const { object, property, snippet } = binding;
if (binding.store) { if (lhs.type === 'Identifier') {
store = binding.store; lhs = x`${object}[${property}]`;
tail = x`${binding.tail}.${tail}`;
}
return { contextual_dependencies.add(object.name);
uses_context: true, contextual_dependencies.add(property.name);
mutation: store } else {
? mutate_store(store, value, tail) // (lhs as MemberExpression).object = x`${object}[${property}]`;
: b`${snippet} = ${value};`,
contextual_dependencies: new Set([object.name, property.name])
};
} }
const mutation = store
? mutate_store(store, value, tail)
: b`${snippet} = ${value};`;
if (binding.node.expression.node.type === 'MemberExpression') {
return {
uses_context: binding.node.expression.uses_context,
mutation,
contextual_dependencies: binding.node.expression.contextual_dependencies,
snippet
};
} }
return { return {
uses_context: false, uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
mutation, mutation: b`${lhs} = ${value}`,
contextual_dependencies: new Set() contextual_dependencies
}; };
// if (lhs.type === 'MemberExpression') {
// return {
// uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
// mutation: b`${lhs} = ${value}`,
// contextual_dependencies
// };
// } else {
// }
// const binding_info = block.bindings.get(name);
// console.log(binding_info);
// let store = binding.object[0] === '$' ? binding.object.slice(1) : null;
// let tail = null;
// if (binding.node.expression.node.type === 'MemberExpression') {
// // const { start, end } = get_tail(binding.node.expression.node);
// // tail = renderer.component.source.slice(start, end);
// tail = binding.node.expression.node.object;
// }
// if (binding.node.is_contextual) {
// const binding = block.bindings.get(name);
// const { object, property, snippet } = binding;
// if (binding.store) {
// store = binding.store;
// tail = x`${binding.tail}.${tail}`;
// }
// return {
// uses_context: true,
// mutation: store
// ? mutate_store(store, value, tail)
// : b`${snippet} = ${value};`,
// contextual_dependencies: new Set([object.name, property.name])
// };
// }
// const mutation = store
// ? mutate_store(store, value, tail)
// : b`${snippet} = ${value};`;
// if (binding.node.expression.node.type === 'MemberExpression') {
// return {
// uses_context: binding.node.expression.uses_context,
// mutation,
// contextual_dependencies: binding.node.expression.contextual_dependencies,
// snippet
// };
// }
// return {
// uses_context: false,
// mutation,
// contextual_dependencies: new Set()
// };
} }
function get_value_from_dom( function get_value_from_dom(

@ -191,7 +191,7 @@ export default class IfBlockWrapper extends Wrapper {
const vars = { name, anchor, if_exists_condition, has_else, has_transitions }; const vars = { name, anchor, if_exists_condition, has_else, has_transitions };
const detaching = is_head(parent_node) ? null : 'detaching'; const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) { if (this.node.else) {
this.branches.forEach(branch => { this.branches.forEach(branch => {

@ -18,19 +18,19 @@ export default function bind_this(component: Component, block: Block, binding: B
let object; let object;
let body; let body;
if (binding.is_contextual && binding.expression.node.type === 'Identifier') { if (binding.is_contextual && binding.raw_expression.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to // bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x; // to `array[index] = x;
const { name } = binding.expression.node; const { name } = binding.raw_expression;
const { snippet } = block.bindings.get(name); const { snippet } = block.bindings.get(name);
lhs = snippet; lhs = snippet;
body = b`${lhs} = $$value`; // TODO we need to invalidate... something body = b`${lhs} = $$value`; // TODO we need to invalidate... something
} else { } else {
object = flatten_reference(binding.expression.node).name; object = flatten_reference(binding.raw_expression).name;
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim(); lhs = binding.raw_expression;
body = binding.expression.node.type === 'Identifier' body = binding.raw_expression.type === 'Identifier'
? b` ? b`
${component.invalidate(object, x`${lhs} = $$value`)}; ${component.invalidate(object, x`${lhs} = $$value`)};
` `

@ -1,3 +1,20 @@
export default { export default {
html: '<p>1 + 2 = 3</p>' html: `<div>foo</div><div>bar</div><div>baz</div>`,
test({ assert, component, target }) {
let elems = target.querySelectorAll('div');
assert.equal(component.divs.length, 3, 'three divs are registered (unkeyed array)');
component.divs.forEach((e, i) => {
assert.equal(e, elems[i], `div ${i} is correct (unkeyed array)`);
});
component.items = ['foo', 'baz'];
assert.equal(component.divs.length, 3, 'the divs array is still 3 long');
assert.equal(component.divs[2], null, 'the last div is unregistered');
elems = target.querySelectorAll('div');
component.divs.forEach((e, i) => {
assert.equal(e, elems[i], `div ${i} is still correct`);
});
}
}; };

@ -1,6 +1,8 @@
<script> <script>
let a = 1; export let items = ['foo', 'bar', 'baz'];
let b = 2; export let divs = [];
</script> </script>
<p>{a} + {b} = {a + b}</p> {#each items as item, j}
<div bind:this={divs[j]}>{item}</div>
{/each}
Loading…
Cancel
Save