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

UI element with wrong values after observable refresh #353

Closed in32bit closed 7 years ago

in32bit commented 7 years ago

JsViews is a great library. And i have been working with it for some time now. But still i can't find why the hell this event call won't work. The data after the sorting is right and the arr is good.

sortTiers: function () {
var arr = this.StructureActivitiesTiers.slice()
          .sort(function (a, b) { return parseFloat(a.TierTo) - parseFloat(b.TierTo); });
            for (var i = 0; i < arr.length; i++) {
                arr[i].TierTo = parseFloat(arr[i].TierTo);
                arr[i].TierFrom = (i == 0 ? 0 : arr[i - 1].TierTo)
            }
            $.observable(this.StructureActivitiesTiers).refresh(arr);
}

The template:

{^{for StructureActivities}}
<tr>
 <td><input type="text" data-link="Currency"></td>
 <td><input type="text" data-link="Price"></td>
</tr>
{^{for StructureActivitiesTiers}}
<tr>
<td></td>
  <td>From: <input disabled data-link="TierFrom" type="text">&emsp;&emsp;
    To: <input type="text" data-link="TierTo">
  </td>
</tr>
{{/for}}
<tr>
<td>
  <button data-link="{on 'click' ~sortTiers}"/>
</td>
<td> 
</td>
</tr>
{{/for}}

https://jsfiddle.net/in32bit/ertsu5qc/2/ Thanks!

Paul-Martin commented 7 years ago

You don't show the objects you are passing to .link, but my suspicion is that StructureActivitiesTiers is not initialized to an empty array. Thus at the time of initial binding there is no array to bind to. And thus at the time of .refresh there is no array to refresh.

A JSFiddle demonstrating the issue would be helpful -- this is pure speculation based on the information provided.

in32bit commented 7 years ago

Here

https://jsfiddle.net/in32bit/ertsu5qc/2/

thanks!

Paul-Martin commented 7 years ago

That link has no saved javascript. Perhaps you forgot to save?

BorisMoore commented 7 years ago

It seems to work correctly to me. The sortTiers function modifies TierFrom and sorts by TierTo. The refresh does render in the new order, sorted by To. Am I missing something?

in32bit commented 7 years ago

Hi Boris. Thank you.

The little algorithm set the tierfrom by the previous tierto. As you can see after the sort event the result in the array is correct. But the UI showing different numbers.

The array content after the sort:

0-4 4-5 5-15

the UI show something else...

BorisMoore commented 7 years ago

Ah OK. What you are doing is first sorting the StructureActivitiesTiers array, then modifying its values non-observably, then calling $.observable(this.StructureActivitiesTiers).refresh(arr);.

The effect of the refresh(arr) is to first determine if the objects in the new array, arr actually reference existing objects in StructureActivitiesTiers array. If so it will simply shuffle the rendered views to put them in the right order, and will not re-render each of them. So the non-observable property changes are ignored.

You can fix it by making the property changes observable too, either before or after doing the array refresh call:

for (var i = 0; i < arr.length; i++) {
  $.observable(arr[i]).setProperty({
    TierTo: parseFloat(arr[i].TierTo),
    TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
  });
}
$.observable(this.StructureActivitiesTiers).refresh(arr);

or:

$.observable(this.StructureActivitiesTiers).refresh(arr);
for (var i = 0; i < arr.length; i++) {
  $.observable(arr[i]).setProperty({
    TierTo: parseFloat(arr[i].TierTo),
    TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
  });
}

See https://jsfiddle.net/ertsu5qc/4/.

Alternatively you can fill the arr with new objects:

for (var i = 0; i < arr.length; i++) {
  arr[i] = {
    TierTo: parseFloat(arr[i].TierTo),
    TierFrom: i == 0 ? 0 : arr[i - 1].TierTo
  };
}
$.observable(this.StructureActivitiesTiers).refresh(arr);

Now the refresh(arr) call finds new objects, not references to existing ones, so it removes the old views for each item and inserts new ones for the new items, (so obviously renders them fully).

See https://jsfiddle.net/ertsu5qc/5/.

in32bit commented 7 years ago

WoW! You are great!

I just learned a lot more about the refresh behavior. Hope other will learn too. Thank you very much.

BorisMoore commented 7 years ago

I'll try to add more info to the docs (http://www.jsviews.com/#refresh) at some point...

Meantime I also put the above answer to your question on stackoverflow: http://stackoverflow.com/a/40516957/1054484

BorisMoore commented 7 years ago

Oh btw, @in32bit: it would be good to go and 'accept' the stackoverflow answer, so folks know it does answer the issue you raised... Thanks! http://stackoverflow.com/a/40516957/1054484