Isolate reactivity in an ordered list

2019-08-03 03:01发布

问题:

I have got a template that shows tiles in a particular order:

<template name="container">
{{#each tiles}}{{>tile}}{{/each}}
</template>

Now the container is a list of tiles that is stored as an array in mongodb.

Since I want the tiles to be shown in the same order as they appear in that array, I'm using the following helper:

Template.container.tiles = function () {
        return _.map(this.tiles || [], function(tileId) {
              return _.extend({
                   container: this
              }, Tiles.findOne({_id: tileId}));
        }, this);
    };
};

The problem is, that I:

  • Do not want the entire container to rerender when the any of it's contain tiles changes. (Only the relevent tile should be invalidated).

  • Do not want the entire container to rerender when a new tile is inserted. The rendered tile should be simply appended or insteted at the respective location.

  • Do not want the entire container to rerender when the order of the tiles is changed. Instead, when the order changes, the DOM objects that represent the tile should be rearranged without re-rendering the tile itself.

With the above approach I will not meet the requirements, because the each tiles data is marked as a dependency (when running Tiles.findOne({_id: tileId})) of the entire container and the entire array of tile-ids is part of the containers data and if that changes the entire container template is invalidated.

I'm thinking I should mark the cursor for the container as non-reactive. Something like this:

Containers.findOne({_id: containerId}, {reactive:false});

But I still need to find out when this container changes it's tiles array.

So something like

 Deps.autorun(function() {
    updateContainer(Containers.findOne({_id: containerId}));
 });

But I want that container template to be highly reusable. So whatever solution there it should not require some preparations with dependencies.

Where do declare I run that autorun function? (surely i cannot do that in that helper, right?)

Is this the right approach?

Does anybody have better ideas on how to solve this problem?

Thanks in advance...

回答1:

The way I usually approach this problem is by creating an auxiliary Collection object and populate it with a help of appropriate observer. In your case this might be something like:

// this one should be "global"
var tiles = new Meteor.Collection(null); // empty name

Now, depending on the current container, you can populate the tiles collection with corresponding data. Also, you'll probably need to remember each object's index:

Deps.autorun(function () {

    var containerId = ... // get it somehow, e.g. from Session dictionary
    var tilesIDs = Containers.findOne({_id:containerId}).tiles;

    tiles.remove({}); // this will be performed any time
                      // the current container changes

    Tiles.find({ _id : { $in : tilesIDs } }).observeChanges({
        added: function (id, fields) {
            tiles.insert(_.extend({
                _index : _.indexOf(tilesIDs, id),
                _id    : id,
            }, fields);
        },
        changed: ... // you'll also need to implement
        removed: ... // these to guys
    });

});

The helper code is now really simple:

Template.container.tiles = function () {
    return tiles.find({}, {sort : {_index : 1}});
}

EDIT:

Please note, that in order to prevent the whole list being rerendered every time the container object changes (e.g. the order of tiles changes), you'll need to make a separate listOfTiles template that does not depend on the container object itself.