简体   繁体   中英

Meteor Blaze order sub-documents by sub-document property

Profile:

_id: Pe0t3K8GG8,
videos: [
   {id:'HdaZ8rDAmy', url:'VIDURL', rank: 2},
   {id:'22vZ8mj9my', url:'VIDURL2', rank: 0},
   {id:'8hyTlk8H^6', url:'VIDURL3', rank: 1},
]

The profile is displayed together with the list of videos. I have a Drag & Drop which updates the videos rank using a Server Method.

1) the database updates correctly on Drop .

2) To sort the videos Array - I declare a helper on the Profile Template and SORT the videos array based on a custom comparison function .

Template.Profile.helpers({
    'videosSorted': function(){
        let videos = (this.videos);
        let videosSorted = videos.sort(function(a, b) {
        return parseFloat(a.rank) - parseFloat(b.rank);
    });
    return videosSorted;
  }
});

Problem:

A) In Blaze the {{#each videosSorted}} does not reactively update. If I F5 refresh then i can see the new order.

I think the issue is because I am providing videosSorted which does not update on changes to the document in the db.

How can I make videosSorted reactive?

Update:

All related code: Iron Router Controller - I subscribe and set the data context for the layout

ProfileController = RouteController.extend({
 subscriptions: function() {
    this.subscribe('profile',this.params.slug).wait();
  },
  data: function () {
     //getting the data from the subscribed collection
     return Profiles.findOne({'slug':this.params.slug});
  },
})

Publication:

Meteor.publish('profile', function (slug) {
  const profile = Profiles.find({"slug":slug});
  if(profile){
    return profile;
  }
  this.ready();
});

The Profile HTML template:

<template name="Profile">
     <ul  class="sortlist">
        {{#each videosSorted}}
            {{> Video}}
        {{/each}}
     </ul>
</template>

I am using mrt:jquery-ui - sortable function

Template.Profile.onRendered(function () {
  thisTemplate = this;

  this.$('.sortlist').sortable({
    stop: function(e, ui) {

      el = ui.item.get(0);
      before = ui.item.prev().get(0);
      after = ui.item.next().get(0);

      if(!before) {
        newRank = Blaze.getData(after).rank - 1
      } else if(!after) {
        newRank = Blaze.getData(before).rank + 1
      }
      else {
        newRank = (Blaze.getData(after).rank +
          Blaze.getData(before).rank) / 2
      }

      let queryData = {
          _id: thisTemplate.data._id,    //the id of the profile record
          videos_objId: Blaze.getData(el).objId,    //the id of the sub document to update
          new_rank: newRank  //the new rank to give it
      };

      //Update the sub document using a server side call for validation + security  
      Meteor.call("updateVideoPosition", queryData, function (error, result) {
          if(!result){
            console.log("Not updated");
          }
          else{
            console.log("successfully updated Individual's Video Position")
          }
      });
   }
 })
});

And finally the Meteor method that does the updating

'updateVideoPosition': function (queryData){
    let result = Individuals.update(
      {_id: queryData._id, 'videos.objId': queryData.videos_objId },
      { $set:{ 'videos.$.rank' : queryData.new_rank } }
    )
    return result;
  }

Note :

As i mentioned - the database updates correctly - and if i have an Incognito window open to the same page - i see the videos reactivly (magically !) switch to the new order.

The schema

const ProfileSchema = new SimpleSchema({
  name:{
    type: String,
  }
  videos: {
    type: [Object],
    optional:true,
  },
  'videos.$.url':{
    type:String,
  },
  'videos.$.rank':{
    type:Number,
    decimal:true,
    optional:true,
    autoform: {
      type: "hidden",
    }
  },
  'videos.$.subCollectionName':{
    type:String,
    optional:true,
    autoform: {
      type: "hidden",
    }
  },
  'videos.$.objId':{
    type:String,
    optional:true,
    autoform: {
      type: "hidden",
    }
  } 
});

I came up with really crude solution, but I don't see other options right now. The simplest solution I can think of is to rerender template manually:

Template.Profile.onRendered(function () {
  var self = this;
  var renderedListView;
  this.autorun(function () {
    var data = Template.currentData(); // depend on tmeplate data
    //rerender video list manually
    if (renderedListView) {
      Blaze.remove(renderedListView);
    }
    if (data) {
      renderedListView = Blaze.renderWithData(Template.VideoList, data, self.$('.videos-container')[0]);
    }
  });
});
Template.VideoList.onRendered(function () {
  var tmpl = this;
  tmpl.$('.sortlist').sortable({
    stop: function (e, ui) {
      var el = ui.item.get(0);
      var before = ui.item.prev().get(0);
      var after = ui.item.next().get(0);
      var newRank;
      if (!before) {
        newRank = Blaze.getData(after).rank - 1
      } else if (!after) {
        newRank = Blaze.getData(before).rank + 1
      }
      else {
        newRank = (Blaze.getData(after).rank +
          Blaze.getData(before).rank) / 2
      }
      let queryData = {
        _id: tmpl.data._id,    //the id of the profile record
        videos_objId: Blaze.getData(el).objId,    //the id of the sub document to update
        new_rank: newRank  //the new rank to give it
      };
      //Update the sub document using a server side call for validation + security
      Meteor.call("updateVideoPosition", queryData, function (error, result) {
        if (!result) {
          console.log("Not updated");
        }
        else {
          console.log("successfully updated Individual's Video Position")
        }
      });
    }
  });
});
Template.VideoList.helpers({
  videosSorted: function () {
    return this.videos.sort(function (a, b) {
      return a.rank - b.rank;
    });
  }
});

And HTML:

<template name="Profile">
  <div class="videos-container"></div>
</template>

<template name="VideoList">
  <ul class="sortlist">
    {{#each videosSorted}}
      <li>{{url}}</li>
    {{/each}}
  </ul>
</template>

Reativeness was lost in your case because of JQuery UI Sortable. It doesn't know anything about Meteor's reactiveness and simply blocks template rerendering.

Probably you should consider using something more adopted for Meteor like this (I am not sure it fits your needs).

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