简体   繁体   中英

How can you create a reactive & non-reactive Meteor template?

I'm struggling with an odd problem. As soon as a document gets updated with a certain attribute, it no longer matches the publish query. Meteor rips it out of the DOM and removes it from the clientside collection.

I need the listings to stay in place so the user can still go back and undo their choice. However I need them to not show up on the next page load.

I can simulate what i'm after if I send the entire collection to the client and then make reactive false if it returns more than 0 results. On refresh only listings that have not been chosen show up. Obviously this is not good for performance.

My publish query basically just filters out the correct type of listing and also pulls in listings that the user hasn't chose like/dislike. It was incrementing the limit but I couldn't make it work without losing the previously chosen listings

Meteor.publish('listings', function(type, limit, ulid) {
  var query, options;

  query = {
    type:      type,
    completed: true,
    'public':  true
  };

  options = {
    sort:  {title: 1},
    limit: 1000        // debugging, should be param
  };

  // if user is logged in, get their listing id
  if (ulid) {
    query.likedBy    = { $ne: ulid };
    query.dislikedBy = { $ne: ulid };
  }

  return Listings.find(query, options);
});


// client, simulates the effect I need, but doesn't work if you increase limit

Template.listings.helpers({
  listings: function() {
    var listType, results;

    listType = Session.get('listingType');

    results = Listings.find({type: listType }, {reactive: false}).fetch();

    // prevents race condition where sometimes listings come through anyway
    if (Meteor.user()) {
      var ul = Session.get('userListing')._id;
      results.likedBy =    { $ne: ul };
      results.dislikedBy = { $ne: ul };
    }

    // if results haven't arrived fetch again with reactivity
    if (!results.length) {
      results = Listings.find({ type: listType }).fetch();
    }

    // append a 'no listings avail.' card to the end of results
    // If no results are found, user only sees not found card
    results.push({type: 'empty'});
    return results;
  }
}); 

For visual, each document is displayed one at a time and the user swipes right to get new listings and left to go back and see/undo old one.

-----------------------
|                     |
| |-----------------| |
| |                 | |
| |                 | |
| |                 | |
| |    listing #1   | |
| |                 | |
| |                 | |
| |                 | |
| |                 | |
| |                 | |
| |                 | |
| |                 | |
| |                 | |
| |-----------------| |
|                     |
-----------------------

This is an interesting problem. You're not really looking to make the template reactive and non-reactive, but find a way to keep certain documents published during a user "session" even though a field has changed which would normally cause the document to be un-published.

One solution that came to mind would be to have a second publication that keeps those documents that have been liked or disliked published if the like or dislike was added during the current session. Two publications can publish from the same Collection and the result is that the Collection on the client is the union of these two publications.

So when a user clicks "like" or "dislike" you would add the _id of that document to a session variable which is an array of documents to keep published. Using a subscription within a deps.autorun you could reactively send this list of _ids to the server and tell it to publish those documents along side your normal publication.

One issue would be the order of the documents in the collection changing as they are taken out of one subscription/publication and put into another. You might need use a sort on the Listings.find() inside your helper to keep the order from changing. Also depending on latency the list might still "flicker" as the document is removed from one publication and added to another.

Here's what the code might look like:

//Server side publish
Meteor.publish('listingsForSession', function(listingIds) {
check(listingIds, [String]);
  if(listingIds.length > 0){
      return Listings.find({_id: {$in: listingIds}});
  }
});

//Client side subscription
Deps.autorun(function () {
    if(Meteor.userId()){
        Meteor.subscribe("listingsForSession", Session.get("listingIdsToKeep")); 
    }     
});

//Click handler to keep track of documents that have been liked/disliked
Template.listings.events({
    'click .like, click .dislike': function(event){
        event.preventDefault();
        var listingId = $(event.currentTarget).data("id"); //Or however you get the document _id
        var listingIdsToKeep = Session.get("listingIdsToKeep");
        listingIdsToKeep.push(listingId);
        Session.set('listingIdsToKeep', listingIdsToKeep);
    }
});

Not sure if this is the best solution, but it could work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM