BorisMoore / jsviews

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

Data-linking and observable changes with #data[propName] #311

Closed mcartmel closed 9 years ago

mcartmel commented 9 years ago

Hi Boris,

Just in reference to this (very old) issue.

I have also been trying to work with property names that have minus characters and/or other special characters.

I can see that it's possible to use #data['var-with.dots.and-minus'] to show these properties, but is it possible to also do data-linking and observable changes? I can't see to make it work.

http://jsfiddle.net/LqscLj60/2/

BorisMoore commented 9 years ago

No - you can't use that syntax in JsViews if you want the automatic observable data-linking behavior to work. JsView data-linking expressions bind to objects based on path expressions, a.b.c with variants like a.^b.c (which will listen to changes of both the leaf property, a.b.c and the object a.b, but will not listen to changes in the object a - see 'Deep paths': http://www.jsviews.com/#observe).

I don't believe the overhead in code, perf cost, and complexity of supporting live data-linking to a['b']['c'] would be justified.

So it means you have to map your objects to new ones which limit the property names to the allowed character set: valid JavaScript name characters (a-z, A-Z, 0-9, _, $) - in particular, for any properties which you need to be dynamically linked.

So this should be fine {{:#data['var-with.dots.and-minus']}} (not data-linked). But {^{:#data['var-with.dots.and-minus']}}' and data-link="{{:#data['var-with.dots.and-minus']}}" will not listen to updates in that property...

BorisMoore commented 9 years ago

Closing, since question answered.

larjohn commented 9 years ago

What about the case where you want to generate the property names dynamically?

For instance, you could filter the property names using a helper function that returns an array of property names. Then you might like to show textboxes bound to those properties only.

Your only option is to compose the property name in a string and access it through the array braces. But this is not working two-way. It does not update the underlying property.

I am talking about valid variable names.

larjohn commented 9 years ago

I solved this by getting the value of the data-link attribute dynamically as a string using another helper function, where I made the string to use the correct variable name.

I am certainly terrified this worked. Those things one day will conquer the world :smile_cat:

BorisMoore commented 9 years ago

@larjohn: Ok good.

If you want, you could create a jsfiddle showing what you did. I'd be interested to see it. And then if I have alternative suggestions/ideas, I'll let you know.

BorisMoore commented 9 years ago

@larjohn: I just created a jsfiddle which lets you choose a property/field of an object and dynamically associate it as data-link target for some inputs.

http://jsfiddle.net/BorisMoore/hqy06tm5/

eugene-panferov commented 9 years ago

On 09/28/2015 09:10 PM, Boris Moore wrote:

@larjohn: I just created a jsfiddle which lets you choose a property/field of an object and dynamically associate it as data-link target for some inputs.

i am afraid that the very requirement of dynamic data-linking testify for a bad application design (most likely bad data design)

larjohn commented 9 years ago

@eugene-panferov yes it is a bad data design. I have a judgment object which instead of having an array with 20 fact strings, it has fact1, fact2, fact3 ... fact20. I can't change that. So dynamic property selection allows me to loop an array of 1...20, and create the property name an then access it. I had tried with #data['fact1'] but it didn't work two-way.

I was not aware that you can use the {{}} notation inside the data-link attribute. I hope it works correctly inside a {:}, as I have more than one attributes linked in that element.

I don't have the actual source code available at the moment. What I did was sth like that: data-link="{{:~getTheString()}}" where getTheString returns the whole string with judgment.fact1...fact2 notation in-place. Nothing as fancy as what Boris suggested.

BorisMoore commented 9 years ago

@eugene-panferov - Well often, I agree, it may be the result of bad design. But I'm not sure if one can say that is always so. For example these examples http://www.jsviews.com/#samples/editable all let the user select an item and then provide editing UI - master/detail style.

So they include {^{for movies[selectedIndex]}} - which dynamically binds to a different item when selectedIndex changes. Of course the data-link expression itself is not modified. Similarly JsViews will work with this as two-way binding: <input data-link="movies[selectedIndex].title" />

If you do the similar thing where movie has properties which have object values, each with a title property, then this will also work: <input data-link="movie[selectedProperty].title" />

If the properties are strings and you want one-way binding, this will work too: <input data-link="{:movie[selectedProperty]}" />

But if they are strings and you want two-way binding, then it will not work. JsViews doesn't know how to dynamically bind back to a different property when selectedProperty changes. That's the scenario where you could consider the dynamic setting of the data-link expression as in my jsfiddle above.

BorisMoore commented 9 years ago

@larjohn - my use of {{}} inside a data-link attribute data-link="object.{{:selected}} will initialize the data-link - as will yours data-link="{{:~getTheString()}}" - but I did not write data-link="data-link="object.{^{:selected}} - that will give you a syntax error. So for updating I am unlinking and relinking to the new expression, from code... I'm not sure how yours is working if the results of getTheString() are supposed to change dynamically. Perhaps in your scenario you don't need that - and the initial static values remain valid...

Paul-Martin commented 9 years ago

I might add I use this technique extensively for an form based application in which the users defines the fields themselves and therefore it is impossible to know in advance which fields will exist. I am able to do things like:

{^{for fields() }}

{{if type == 'text' }}

<input data-link="{{{:~chooseConvert(#data)}}:{{:bindValue(#data) }} trigger=true :{{chooseConvertBack(#data)}}} />

{{else type == 'picklist'}}

<select data-link="{{{:~chooseConvert()}}:{{:bindValue() }} trigger=true :{{chooseConvertBack()}}}>
  {{for options() }}
  <option id="{{:id}}">{{:label}}</option>
  {{/for}}
</select>

{{else type == 'checkbox'}}

  ... etc ...

{{/if}}
{{/for}}

I find this invaluable for dynamic user interfaces.

The helper functions all return the 'correct' string for the given context (user / field type / form context etc.)

I data-bind the for loop such that if the user redefines the fields available, all the converters and data-link statements will be reevaluated. If you have such a scope you might be able to do something similar in lieu of manually relinking.

BorisMoore commented 9 years ago

Yes - refreshing the UI from higher up will most often be the best tactic, rather than the 'manual' programmatic unlink/link. This is a related discussion - and touches on that same point: https://github.com/BorisMoore/jsviews/issues/319#issuecomment-143315854.

But for JsViews as a platform, of course, providing some fallback provisions allowing programmatic re-linking is a good feature to provide - but not to encourage overuse of :)

larjohn commented 9 years ago

This worked as well:

<select class="chosen-select-withAdd facts-list-trigger"  
data-link="name{:'judgements'+ ~j_iterator+'.fact'+#data+'_uns'} 
id{:'judgements'+ ~j_iterator +'.fact'+#data}
 {:~judgement.{{:'fact'+#data+'_uns'}}:}" >

Please keep maintaining this excellent framework!

BorisMoore commented 9 years ago

@larjohn - good. Yes I will... I am currently working on completing the JsViews documentation, and after that will declare V1.0 for both JsRender and JsViews. Meantime any tweets/blogs etc. to 'spread the news' are always appreciated. Not enough people know about JsViews...