Search code examples
jsrenderjsviews

Rendering two arrays linked by ID's with two-way data binding


I built this rudimentary tool for creating a Menu where multiple/identical Source items may be attached (1 to many). This isn't really a merge, because I need Source items to be editable and data bound to all it's instances in the Menu. This involves two arrays: one the Source and the other for Menu where a common ID connects the items and ID's for their sub-items. Menu includes inputs for override/customization.

The problem I am having is with refreshing the data when changes are made. I've had to resort to using $.templates("#myTmpl").link("#renderMenu", data) into two different places as data is changed. This is not ideal as it re-renders everything; although in my case everything in two-way data bound, so no loss. view.refresh() does not work either

I think the problem is because itemVar does not get updated when it's underlying data is changed...itemVar is for single pass rendering from what I understand. I also tried helpers in place of itemVar like ^~items=myItems...they didn't work either.

So the question is how to trigger refreshing on certain view's/parts without resorting to: <$.templates("#myTmpl").link("#renderMenu", data)>

To test:

  1. Comment out line 152 $.templates("#menuTmpl").link("#renderMenu", data)
  2. Run
  3. Press Insert new item and pick any item from select list.
  4. Problem is here...it doesn't render the matching item detail from the Source item. I believe it all starts with itemVar="~currentItem" on line 8, where itemVar does not get updated nor trigger a refresh. The underlying data is changed however.

Perhaps there is a better way of building this tool or parts of it; as it includes a bit of trickery (filter ... step=100) and probably unnecessary 'for' looping. I'll admit...it took me 5 versions to build as it was very tricky getting all things working together.

Any pointers would be much appreciated.

Fiddle showing the problems: https://jsfiddle.net/alnico/mt5d2v7j/
There are ID's on all rendered items for debugging.


Solution

  • You are right about itemVar not updating - in the sense that if a new array item is added, the view for that item does not have an itemVar contextual parameter (such as ~currentItem in your sample). I will fix that in the next update. I created an issue here https://github.com/BorisMoore/jsviews/issues/424, and you can test it out with the updated jsviews.js file you will find there.

    The other immediate issue you have is that you have:

    {^{for ~root[1].allItems filter=~menuItem id=id}}
    

    But the menuItem filter depends on the id, so it needs to be refreshed whenever id changes. You can achieve that either by declaring the dependency on the menuItem function:

    $.views.helpers.menuItem.depends = "id";
    

    or by using mapDepends:

    {^{for ~root[1].allItems filter=~menuItem id=id mapDepends='id'}}
    

    You can simplify things a bit by removing the id=id and writing

    menuItem: function(item, index, items) {
      return item.id == this.view.data.id;
    }
    

    Or better, don't use a filter on the {{for}}, but simply use a helper function to return the filtered items directly:

    getItem: function(id) {
      return data[1].allItems.filter(function(item) {
        return item.id == id;
      });
    }
    

    used like this:

    {^{:#index}}
    {^{for ~getItem(id)}}
      {^{if ~currentItem.isNewItem != '1'}}
    

    If you do that you no longer need to use mapDepends or .depends on your filter function. Changes in id will trigger a refresh of {^{for ~getItem(id)}}