BorisMoore / jsviews

Interactive data-driven views, MVVM and MVP, built on top of JsRender templates
http://www.jsviews.com/#jsviews
MIT License
856 stars 130 forks source link

Conditionally switching two-way data binding #402

Closed alnico2001 closed 6 years ago

alnico2001 commented 6 years ago

I am using a checkbox to conditionally switch two-way data binding.

<input type="checkbox" data-link="customChecked" /> Use Custom Name?

Two-way binding works here as expected:

{^{if customChecked == true}}
      <input type="text" data-link="{:customFirstName:}" />
{{else}}
      <input type="text" data-link="{:firstName:} disabled{:customChecked == false}" />
{{/if}}

One-way binding works here, but two-way does not work (editing text does not update the json for customFirstName). Is this by design or is my syntax wrong? I couldn't find anything in the documentation.

<input type="text" data-link="{:(customChecked == true ? firstName : customFirstName):} disabled{:customChecked == false}" />
BorisMoore commented 6 years ago

With two-way binding, the 'from' behavior can bind to multiple observable properties, and will update the value when any of them change. In your case it updates appropriately when either customChecked or firstName or customFirstName change.

But for the 'to' behavior, it determines a single binding to the appropriate observable property. It heuristically determines which of the three (in your case) is the target property, or you can tell it which one by setting linkTo=firstName for example (http://www.jsviews.com/#link2way@linkto).

But it can't change the target on the fly, when data changes. You could achieve that with two-way computed observables, or with a smart convertBack converter. But the simpler approach is to use the {^{if}} approach that you use above.

alnico2001 commented 6 years ago

Thank you Boris!

BorisMoore commented 6 years ago

That said, any of the following might work too for you, depending on your exact needs

<input data-link="{:!customChecked?firstName:customFirstName:} disabled{:!customChecked}" />

or

<input data-link="{:customChecked?customFirstName:firstName linkTo=customFirstName:} disabled{:!customChecked}" /> 

or

<input data-link="{:customFirstName:} disabled{:!customChecked} visible{:customChecked}" />
<input data-link="{:firstName:} disabled{:!customChecked} visible{:!customChecked}" />

The first two only work if you keep the disabled feature on firstName editing. If not disabled, then the editing target is wrong for firstName editing.

Here is another approach that works when allowing editing of either firstName or customFirstName:

<input data-link="{cvt: firstName customFirstName :cvtBk}" />

with converters that work with multiple arguments:

$.views.converters({
  cvt: function (first, custom) {
    return this.tagCtx.view.data.customChecked ? custom : first;
  },
  cvtBk: function(val) {
    return this.tagCtx.view.data.customChecked ? [undefined, val] : [val, undefined];
  }
});
$.views.converters.cvt.depends = "customChecked";