ractivejs / ractive

Next-generation DOM manipulation
http://ractive.js.org
MIT License
5.94k stars 396 forks source link

Revisit forced resolution on two-way bindings #1776

Closed martypdx closed 7 years ago

martypdx commented 9 years ago

Currently two-way bindings force resolution of the keypath on which they are bound, and current binding rules specify that an ambiguous reference resolves to the instance root. It can lead to situations like this:

{{#with foo}}<input value='{{bar}}'>{{/with}}

In the above, if no foo in data, the input is bound to data.bar, not data.foo.bar.

So @Rich-Harris added #1765, which will helpfully let you know that you should probably initialize data.

The problem I'm seeing is that with asynchronous data retrievals, particularly when used with an adaptor, it's not a viable option to sketch out an empty schema. Which means you either have to only use restricted references:

{{#with foo}}<input value='{{.bar}}'>{{/with}}

or use an additional if block:

{{#if foo}}{{#with foo}}<input value='{{bar}}'>{{/with}}{{/if}}

Neither of which seem developer friendly, IMO. And the if block approach as the added disadvantage of not rendering the DOM while waiting for the data to load anyway!

Two possible options I can think of:

  1. Delay the timing of forced resolution until the bound control is actually used
  2. Redefine forced resolutions rules so that closest context, not root, is used.

Not a 0.7 thing, but I think we're only going to see more async data use as things roll forward.

evs-chris commented 9 years ago

Option 2 sounds like the way to go to me, given that there is no data.bar in existence when the binding is created.

Option 1 sounds like the best possible scenario, but I think it might be troublesome with controls that don't have a clear not-yet-used state and instances where you want to let the control initialize the data.

Rich-Harris commented 9 years ago

I like option 2 as well. Not initialising the values of <select> elements, or a group of checkboxes where checked is initially true in some cases, could be problematic. The combination of defaulting to local context and printing warning messages is probably enough.

Re {{#if foo}}{{#with foo}}... I've occasionally wondered if {{#with}} shouldn't be both with and if. That's actually how it is in Handlebars, and it's what with becomes if it has an associated else/elseif block. (In other words, it's an if that also creates a new context.)

arxpoetica commented 9 years ago

:+1: on the {{#with}} equals {{#if}} part.

As for the other discussed items, I'm wary, and would prefer restricted references. Maybe we over here are doing something wrong, but we often have a god object that has higher level data, and I'd rather just deal w/ namespacing problems by using restricted references rather than not having access to the root.

It's also possible that I'm not understanding what "forced resolutions" means, however...

Rich-Harris commented 9 years ago

@arxpoetica if we do make this switch (which I still think is probably the right move), there is an easy way to access the god object - {{~/foo}} - which says 'skip all intermediate contexts, go straight to the root'. I'd argue that this is actually more readable, since you know straight away that you're dealing with some app-level property. Demo: http://jsfiddle.net/rich_harris/sx9pd6jy/

arxpoetica commented 9 years ago

Yup, okay, I'm cool w/ that. :+1:

Question...does this have any impact for isolated: false context values/methods?

Rich-Harris commented 9 years ago

Nope - the ~/ prefix means 'at the root of the current component', rather than anything outside the component. So I supposed you would need to propagate any global properties manually (i.e. something like <Foo bar='{{bar}}'/> if you relied on {{~/bar}} inside <Foo>, unless we added another symbol...

arxpoetica commented 9 years ago

Hmm. Yeah. Manual propagation of global properties can be a real bummer, especially when it's children of children (of children) of parent components we're talking about.

But it sounds like we'd be fine because I won't have to manually propagate due to ~/ or ~/../ or ../../ or ../ or whatever. :panda_face: Panda approves.

evs-chris commented 7 years ago

{{with foo}}<input value="{{bar}}" />{{/with}} with an ambiguous binding results in the input binding to foo.bar in 0.8+.