Skip to content

Commit

Permalink
fix binding for each block local variable
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed May 20, 2020
1 parent c19542b commit cdbceca
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 168 deletions.
37 changes: 19 additions & 18 deletions src/compiler/compile/render_dom/wrappers/Element/Binding.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object';
import Block from '../../Block';
import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference';
Expand All @@ -10,20 +12,20 @@ import { Node, Identifier } from 'estree';

export default class BindingWrapper {
node: Binding;
parent: ElementWrapper;
parent: ElementWrapper | InlineComponentWrapper;

object: string;
handler: {
uses_context: boolean;
mutation: (Node | Node[]);
contextual_dependencies: Set<string>;
snippet?: Node;
lhs?: Node;
};
snippet: Node;
is_readonly: boolean;
needs_lock: boolean;

constructor(block: Block, node: Binding, parent: ElementWrapper) {
constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) {
this.node = node;
this.parent = parent;

Expand All @@ -33,7 +35,7 @@ export default class BindingWrapper {

// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') {
parent.select_binding_dependencies = dependencies;
(parent as ElementWrapper).select_binding_dependencies = dependencies;
dependencies.forEach((prop: string) => {
parent.renderer.component.indirect_dependencies.set(prop, new Set());
});
Expand Down Expand Up @@ -207,7 +209,7 @@ export default class BindingWrapper {
}

function get_dom_updater(
element: ElementWrapper,
element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper
) {
const { node } = element;
Expand Down Expand Up @@ -270,21 +272,17 @@ function get_event_handler(
contextual_dependencies: Set<string>;
lhs?: Node;
} {
const value = get_value_from_dom(renderer, binding.parent, binding);
const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);

const context = block.bindings.get(name);
let set_store;

if (context) {
const { object, property, modifier, store } = context;

if (lhs.type === 'Identifier') {
lhs = modifier(x`${object}[${property}]`);

contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
const { object, property, store, snippet } = context;
lhs = replace_object(lhs, snippet);
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
contextual_dependencies.delete(name);

if (store) {
set_store = b`${store}.set(${`$${store}`});`;
Expand All @@ -297,6 +295,8 @@ function get_event_handler(
}
}

const value = get_value_from_dom(renderer, binding.parent, binding);

const mutation = b`
${lhs} = ${value};
${set_store}
Expand All @@ -305,20 +305,21 @@ function get_event_handler(
return {
uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
mutation,
contextual_dependencies
contextual_dependencies,
lhs,
};
}

function get_value_from_dom(
renderer: Renderer,
element: ElementWrapper,
element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper
) {
const { node } = element;
const { name } = binding.node;

if (name === 'this') {
return x`$$node`;
return x`$$value`;
}

// <select bind:value='selected>
Expand Down
217 changes: 107 additions & 110 deletions src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import EventHandler from './EventHandler';
import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action';

interface BindingGroup {
events: string[];
bindings: Binding[];
}

const events = [
{
event_names: ['input'],
Expand Down Expand Up @@ -436,11 +441,6 @@ export default class ElementWrapper extends Wrapper {
}

add_directives_in_order (block: Block) {
interface BindingGroup {
events: string[];
bindings: Binding[];
}

type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;

const bindingGroups = events
Expand Down Expand Up @@ -487,7 +487,7 @@ export default class ElementWrapper extends Wrapper {
});
}

add_bindings(block: Block, bindingGroup) {
add_bindings(block: Block, bindingGroup: BindingGroup) {
const { renderer } = this;

if (bindingGroup.bindings.length === 0) return;
Expand All @@ -500,131 +500,128 @@ export default class ElementWrapper extends Wrapper {

if (lock) block.add_variable(lock, x`false`);

[bindingGroup].forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.add_to_context(handler.name);
const handler = renderer.component.get_unique_name(`${this.var.name}_${bindingGroup.events.join('_')}_handler`);
renderer.add_to_context(handler.name);

// TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock);
// TODO figure out how to handle locks
const needs_lock = bindingGroup.bindings.some(binding => binding.needs_lock);

const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();
const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();

group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies);
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
bindingGroup.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);

binding.render(block, lock);
});
binding.render(block, lock);
});

// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
}
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (bindingGroup.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
}

const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;

let callee = renderer.reference(handler);
let callee = renderer.reference(handler);

// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));

// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.chunks.init.push(b`
function ${handler}() {
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
}
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.chunks.init.push(b`
function ${handler}() {
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
}
`);
}

callee = handler;
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
}
`);
}

const params = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));

this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);

group.events.forEach(name => {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);

block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
callee = handler;
}

block.chunks.destroy.push(
b`${resize_listener}();`
);
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
}
});
const params = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));

this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${bindingGroup.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);

const some_initial_state_is_undefined = group.bindings
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);

const should_initialise = (
this.node.name === 'select' ||
group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);
bindingGroup.events.forEach(name => {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);

if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
}

if (group.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
block.chunks.destroy.push(
b`${resize_listener}();`
);
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
}
});

const some_initial_state_is_undefined = bindingGroup.bindings
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);

const should_initialise = (
this.node.name === 'select' ||
bindingGroup.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);

if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}

if (bindingGroup.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}

if (lock) {
block.chunks.update.push(b`${lock} = false;`);
}
Expand All @@ -635,7 +632,7 @@ export default class ElementWrapper extends Wrapper {

renderer.component.has_reactive_assignments = true;

const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
const binding_callback = bind_this(renderer.component, block, this_binding, this.var);
block.chunks.mount.push(binding_callback);
}

Expand Down
Loading

0 comments on commit cdbceca

Please sign in to comment.