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 e9dbd4f
Show file tree
Hide file tree
Showing 12 changed files with 408 additions and 161 deletions.
23 changes: 11 additions & 12 deletions src/compiler/compile/render_dom/wrappers/Element/Binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
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 @@ -17,7 +18,7 @@ export default class BindingWrapper {
uses_context: boolean;
mutation: (Node | Node[]);
contextual_dependencies: Set<string>;
snippet?: Node;
lhs?: Node;
};
snippet: Node;
is_readonly: boolean;
Expand Down Expand Up @@ -271,20 +272,17 @@ function get_event_handler(
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 @@ -305,7 +303,8 @@ 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,
};
}

Expand All @@ -318,7 +317,7 @@ function get_value_from_dom(
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 e9dbd4f

Please sign in to comment.