1
votes

Working with Marionette for the first time and I've run into an issue with the 'itemViewContainer' attribute when I try and render a CompositeView.

I've gotten the CompositeView to work in the same app, but in this case I'm trying to render a 'PopupView' when the user clicks on a button. Within this popup, there's a table that's rendered by a CompositeView that's not appearing.

Here's the View in which the user can open the popup. This is an item in a CompositeView.

define([
    'marionette',
    'templates',
    'views/SectionPopupView'
], function(
    Marionette,
    templates,
    SectionPopupView
) {

  /** ClassListingRowView represents a row in the ClassListingView.
   *  Each Row is tied to a specific Course object that's passed in
   *  when the Row is instantiated in ClassListingView.
   *  this.model: Course model
   *  this.options.user: User model */
  return Marionette.ItemView.extend({
    template: templates.classListingRow,
    className: 'class-listing',
    popupView: null,

    ui: {
      'editBtn': 'a.edit-btn',
      'removeBtn': 'a.remove-btn',
      'popupContainer': 'div.popup-container'
    },

    events: {
      'click @ui.editBtn': 'editCourse',
      'click @ui.removeBtn': 'removeCourse'
    },

    /** Set the ID of this element to [UID]-listing. Also fetches data
     *  from the model. */
    // TODO: This calls render() twice... how do I make it better?
    initialize: function() {
      this.user = this.options.user;
      $(this.$el).attr('id', this.model.get('UID') + '-listing');

      var self = this;
      this.model.fetch({
        success: function() {
          self.render();
        }
      });
    },

    /** Spawns a popup with all sections that allows a user to choose
     *  which sections he/she does/doesn't want. */
    editCourse: function(e) {
      e.preventDefault();
      e.stopPropagation();

      var self = this;
      // Instantiate a view if it hasn't already been instantiated
      $(window.app.curtain).show();
      if (this.popupView == null) {
        this.popupView = new SectionPopupView({
          el: self.ui.popupContainer,
          model: self.model
        });
        this.popupView.render();
      } else {
        this.popupView.show();
      }
    },

    /** Removes this.course from the User's selectedCourse list.
     *  This will trigger an event which the ClassListingView should be
     *  listening for, and then this View will be discarded. */
    removeCourse: function(e) {
      e.preventDefault();
      e.stopPropagation();

      this.user.removeCourseFromSelected(this.model);
    }
  });
});

Here's the View of the popup:

define([
    'marionette',
    'templates',
    'views/PopupView',
    'views/SectionTableView',
    'jquery'
], function(
    Marionette,
    templates,
    PopupView,
    SectionTableView,
    $
) {

  /** A SectionPopupView is a View that represents a popup that shows all of
   *  the sections for a particular Course and allows a user to select
   *  which sections he wants to include and also to see more information
   *  about the course.
   *  model: Course */
  var SectionPopupView = PopupView.extend({
    template: templates.sectionPopup,
    sectionTable: null,

    ui: {
      'closeBtn': 'a.close',
      'tableContainer': 'div#table-container',
      'okBtn': 'a#submit'
    },

    events: {
      'click @ui.closeBtn': 'close',
      'click @ui.okBtn': 'continue'
    },

    /** Upon initialization, the View will have been instantiated with
     *  model: Course. A SectionListTableView must be instantiated upon
     *  init and populated with data from the model's sections attr. */
    initialize: function() {
      var self = this;
      this.on('render', function() {
        self.sectionTable.render();
      });
      this.sectionTable = new SectionTableView({
        el: self.ui.tableContainer,
        model: self.model
      });

      // this.sectionTable.render();
    },

    /** Hides the popup (and curtain). */
    show: function() {
      $(window.app.curtain).show();
      $(this.el).show();
    },

    /** Shows the popup (and curtain). */
    close: function(e) {
      e.preventDefault();
      e.stopPropagation();

      $(window.app.curtain).hide();
      $(this.el).hide();
    }
  });

  return SectionPopupView;
});

and here's the View for the table (sectionTable)

define([
    'marionette',
    'templates',
    'views/SectionTableRowView',
    'views/NoSectionsView',
    'jquery'
], function(
    Marionette,
    templates,
    SectionTableRowView,
    NoSectionsView,
    $
) {

  /**
   *  this.model = Course */
  return Marionette.CompositeView.extend({
    template: templates.sectionTable,
    itemView: SectionTableRowView,
    itemViewContainer: 'tbody',
    emptyView: NoSectionsView,

    initialize: function() {
      this.itemViewOptions = {
        user: this.model
      };
    }
  });
});

I read on here that a lot of people had this issue because they were trying to render the view before it was in the DOM, so I binded the table's render function to the popup's render event, but that didn't work either. Any help?

Apart from that, am I doing anything incorrectly with regards to MVC guidelines/best practices/etc? I'm not 100% confident in my code, so feedback would be appreciated as well! Thanks.

1

1 Answers

0
votes

A couple of things:

1) Your this.on("render"... code needs to be passed the right context (I believe this works):

this.on("render", function() {
  this.sectionTable.render();
}, this);

This whole bit could be moved to the onRender function of this view which helps with clarity:

onRender: function() {
  this.sectionTable.render();
}

2) It's important to understand what signals marionette uses around the DOM:

onRender methods get invoked when the $el for that view has been fully constructed in a virtual DOM but BEFORE it has been inserted into the user's (/jQuery accessible) DOM

onShow methods get invoked with the $el for that view is successfully in the DOM and is jQuery accessible.

There are some onShow edge cases that can come up which usually only happen if you are deeply nesting layouts. I wrote it up here if you are curious.