简体   繁体   中英

MeteorJS: Loading… templates sequentially

So, essentially, I have a collection with multiple categories of photos. I currently have this setup for going through the categories and loading a template for each one:

{{#each categories}}
  {{>gallery collectionName=categoryName}}
{{/each}}

So, this is great for dynamically building each section using the gallery template.

As each gallery has it's own collection of images, I have added a helper function inside the gallery template to call on the respective collection and then for each collection image, add it to the gallery.

(inside the gallery template)

{{loadPhotos collectionName}}

Now, you can probably see the problem.

The gallery divs are created first, empty, then all at once, all the photos are added to all the divs.

I would prefer, each gallery shows a loading circle until the photos are all added, then the next gallery starts loading the photos and the loading circle disappears when all it's photos are loaded. This continues until all the categories have been loaded.

I am using jquery to add the photos and FlowRouter if that helps...

Thanks for your time and any solutions offered!

I have had to do similar types of things in the past and the way that I have been able to do it is by creating a way to communicate between templates and sub-templates. The primary ways to do this is by passing a callback function to each child template or by passing a ReactiveVar , ReactiveDict , or any other reactive computation that the parent is "listening" to. I tested the below approach and it should work perfectly for what you are after.

First the main categories template.

<template name="photoGallery">
  {{#each cat in categories}}
    {{> gallery collectionName=cat.name loadNow=loadTrigger isLoaded=loadedIndicator}}
  {{/each}}
</template>

And the associated Javascript. Note, for this example I am subscribing to a publication called photoCategories which publishes a set of documents from a collection called Categories (which contains a list of gallery categories in the name property and the order I want them loaded in the loadOrder property). Your specifics are likely different.

import { Template } from 'meteor/templating';
import { Categories } from '../../../api/categories/categories.js';
import { ReactiveDict } from 'meteor/reactive-dict';
import { ReactiveVar } from 'meteor/reactive-var';

import './photoGallery.html';
import '../utils/loading-indicator.html';

Template.photoGallery.onCreated(function() {
  this.loadTrigger = new ReactiveDict('loadTrigger');
  this.lastLoaded = new ReactiveVar();
  this.loadOrder = [];

  // subscribe to category data and when complete build loading controls
  // in this example the collection that contains the categories is called
  // 'Categories' and each document contains a property called 'loadOrder'
  // to control the order to load the galleries
  this.subscribe('photoCategories', () => {
    // we are inside the 'onComplete' subscription callback
    Categories.find({}, {sort: {loadOrder: 1}}).forEach((category) => {
      // create a trigger for each gallery so we can communicate when to load
      // initialize each trigger to false for now
      this.loadTrigger.set(category.name, false);

      // we need to also keep track of our load order so we know which
      // gallery to trigger next
      this.loadOrder.push(category.name);
    });

    // now that everything is initialized, lets trigger the first gallery to load
    this.loadTrigger.set(this.loadOrder[0], true);
  });

  // re-runs everytime a gallery is finished loading
  this.autorun(() => {
    // get which gallery just finished loading
    var lastLoaded = this.lastLoaded.get();
    var lastLoadedIndex = this.loadOrder.indexOf(lastLoaded);

    // make sure we have more galleries to load
    if (lastLoadedIndex + 1 < this.loadOrder.length) {
      // get the next gallery to load
      var loadNext = this.loadOrder[lastLoadedIndex + 1];

      // trigger the gallery to load
      Tracker.afterFlush(() => {
        this.loadTrigger.set(loadNext, true);
      });
    }
  });
});

Template.photoGallery.helpers({
  // returns our photo gallery categories to iterate over in our template
  categories: function() {
    return Categories.find({}, {sort: {loadOrder: 1}});
  },

  // returns our ReactiveDict so we can communicate to our sub-template
  // galleries when they can load
  loadTrigger: function() {
    return Template.instance().loadTrigger;
  },

  // returns our ReactiveVar used to keep track of which gallery just loaded
  loadedIndicator: function() {
    return Template.instance().lastLoaded;
  },
});

Basically, what we are doing here is subscribing to the publication that contains the list of categories. Then we build a dictionary of load triggers that we will use to trigger each gallery to load. We also need to define a last loaded trigger so we know when the gallery is done loading (and can trigger the next one to load).

To make sure this all works correctly, we need to make sure the load triggers and last loaded trigger are passed to the gallery templates (so they can be triggered to load and indicate back once they are done).

Now let's look at the gallery template.

<template name="gallery">
  {{#if okToLoadNow collectionName}}
    {{loadPhotos collectionName}}
  {{else}}
    {{> loading_indicator }}
  {{/if}}
</template>

And associated javascript.

Template.gallery.helpers({
  // reactive checks if we are ok to load
  okToLoadNow: function(collectionName) {
    return Template.instance().data.loadNow.get(collectionName);
  },

  // contains logic that loads photos, renders them on the page, then triggers
  // back once the loading is complete
  loadPhotos: function(collectionName) {
    const instance = Template.instance();

    // insert the code that loads the photos and renders them on the page here!
    // if the logic is complete it probably makes more sense to put this in a sub-template
    // and put the logic in the `onRendered` callback

    // lets simulate loading by pausing for 3 seconds then communicate back
    // that we loaded
    Meteor.setTimeout(() => {
      instance.data.isLoaded.set(collectionName);
    }, 3000);
  },
});

In the gallery template, we show a loading indicator until we are triggered to load. Once we are finished loading, we then indicate (via the isLoaded trigger) that we are done. For this we just pass back the gallery name that just loaded.

As I mentioned earlier, I was able to test that this approach works. You should just be able to adapt it with your specifics.

Let me know how it goes!

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