3

Im displaying a CompositeView, in which each model has a property which is an url for an image. If I just .show the view in a region the images have not finished loading, which doesnt look very sexy (they are displayed when they have loaded).

I would like to wait until all images have loaded, before I display the view. Right now, Im trying to make it work by deferring image loads, but this is probably not the right way (perhaps this should be done on the model?).

applications/list_view.js

App.module("ApplicationsApp.List", function(List, App, Backbone, Marionette, $, _){

  List.Application = Marionette.ItemView.extend({
    tagName: 'li',
    template: Templates.applications.application
  });

  List.Applications = Marionette.CompositeView.extend({
    className: "applications",
    template: Templates.applications.applications,
    childView: List.Application,
    childViewContainer: "ul"
  });

});
applications/list_controller.js

App.module("ApplicationsApp.List", function(List, App, Backbone, Marionette, $, _) {

  List.Controller = {

    listApplications: function() {
      // Set layout
      App.trigger("set:layout:authenticated");

      // Fetch the applications
      var fetchingApplications = App.request('application:entities');

      $.when(fetchingApplications).done(function(applications) {

        var applicationsListView = new List.Applications({
          collection: applications
        });


        var deferreds = [];

        applications.each(function(application) {

          deferreds.push(function() {
            var loader = new Image();
            loader.onload = function() {
              console.log(application.get("image_path"));
            };
            loader.src = application.get("image_path");
          });

        });

        $.when.apply($, deferreds).then(function() {
          App.layout.mainRegion.show(applicationsListView);
        });

      });

    }

  };

});
tolborg
  • 612
  • 4
  • 21
  • Are you asking if this is something to be done in the model or what? I think that view should handle image loads – Dethariel Oct 27 '14 at 14:12
  • Naah. The model question is only a subquestion :) I would like the best solution. – tolborg Oct 27 '14 at 14:27
  • The best solution is to check if all images were loaded and then render your view. You can extract that logic (image load check) to collection. Look [here](http://stackoverflow.com/questions/18974517/javascript-check-if-images-are-loaded-before-gameloop) and [here](http://stackoverflow.com/questions/15587330/test-if-all-images-are-loaded) – vvahans Oct 27 '14 at 14:35

2 Answers2

1

I think your approach is correct, I will try to encapsulate the data/image loading in one function, using a repository probably

    $.when(applicationsRepository.getAll()).then(function (apps) {
        var appsView = new App.Views.ApplicationListView({ collection: apps});
        App.layout.mainRegion.show(appsView );
    });

So, this code will be part of you controller, as you already have it, when the repository finish the loading the controller just render the data on the view, something similar to server side MVC with repositories,

getAll() will contain the code that you have now on the controller to load the data and images, the repository will be an extra module on your marionette application.

So your method getAll will be something like

    var getAll = function () {
        var deferred = $.Deferred();
        _getAll(function (apps) {
            deferred.resolve(apps);
        });
        return deferred.promise();
    };

    var _getAll = function (callback) {
        //code to create the app collection
        apps.on("reset", function(apps){
            //load images
            //callback(apps);
        });
        apps.fetch({ reset: true });
    };

To identify and perform some action(callback) after the last image loads, I will probably use a counter set at the total number of images(from the collection), and decrement it each time a load handler is executed. $(window).load() could be and option this will fire after the entire page is loaded.

Thanks.

Arturo Soto
  • 199
  • 2
  • this seems really promising (get the joke?). Could you perhaps elaborate a bit more on it in the context of my code? – tolborg Oct 27 '14 at 21:27
  • Thanks. You helped me write a solution. How do you like it? (I know the placement of the loadImage function is probably not very good, but so far it's the only place in the app where I use it). – tolborg Oct 27 '14 at 22:45
0

I ended up doing like this (inspired by Wait for image to be loaded before going on):

applications/list_controller.js

App.module("ApplicationsApp.List", function(List, App, Backbone, Marionette, $, _) {

  List.Controller = {

    listApplications: function() {
      self = this;
      // Set layout
      App.trigger("set:layout:authenticated");

      // Fetch the applications
      var fetchingApplications = App.request('application:entities');

      // Perform actions when applications have been fetched
      $.when(fetchingApplications).done(function(applications) {

        // Load all images in applications
        var imgLoaders = [];

        applications.each(function(application) {
          var src = application.get("image_path");
          imgLoaders.push(self.loadImage(src));
        });

        // Perform actions when images have been loaded
        $.when.apply($, imgLoaders).done(function() {
          var applicationsListView = new List.Applications({
            collection: applications
          });
          App.layout.mainRegion.show(applicationsListView);
        });

      });

    },

    loadImage: function(src) {
      var deferred = $.Deferred();
      var img = new Image();
      img.onload = function() {
        deferred.resolve();
      };
      img.src = src;
      return deferred.promise();
    }

  };

});
Community
  • 1
  • 1
tolborg
  • 612
  • 4
  • 21