Search code examples
javascriptbackbone.jsmarionette

Using Backbone.Marionette, why can I not reference @ui elements when creating a new instance of a view?


I have created a simple view, MyView, that extends from ItemView. Then when I create the instance of MyView, I am trying to add references to UI elements within the view as well as events that use those UI elements.

HTML

<div id="container"></div>

<script type="text/template" id="my-template">
    <p>This is a rendered template.</p>
  <button data-ui="changeModelNameButton">Change Model Name</button>
</script>

JS

// Define a custom view that extends off of ItemView
var MyView = Marionette.ItemView.extend({
  template: "#my-template"
});

// Instantiate the custom view we defined above
var view = new MyView({
  el: "#container",

  ui: {
    changeModelNameButton: '[data-ui~=changeModelNameButton]'
  },

  events: {
    'click @ui.changeModelNameButton': function() {
      alert('here'); 
    }
  }
});

// Render the view in the element defined within the custom view instantiation method
view.render();

I am receiving the following error in the console:

Uncaught TypeError: Cannot read property 'changeModelNameButton' of undefined


I noticed that if I move the ui declarations to the view definition, it works fine, but I would like to know why I can't add those when I create the instance. Is there no way to add them to the instance or am I missing something?

Note: I am using Backbone 1.3.3, Marionette 2.4.4, Underscore 1.8.3, and jQuery 3.1.1


Solution

  • Options overriding view's properties

    Not all the options are automatically overriding the view class properties when passed on instantiation.

    ui looks like it's not one of them.

    Backbone will automatically apply the following (private) viewOptions on a view:

    // List of view options to be set as properties.
    var viewOptions = [
        'model', 
        'collection', 
        'el', 
        'id', 
        'attributes', 
        'className', 
        'tagName', 
        'events'
    ];
    

    In the view constructor, this is extended with the chosen options (source):

    _.extend(this, _.pick(options, viewOptions));
    

    How to pass the ui option?

    You either need to put the ui hash in the view class definition, or to apply the ui hash yourself.

    var MyView = Marionette.ItemView.extend({
        template: "#my-template",
        initialize: function(options) {
    
            // merge the received options with the default `ui` of this view.
            this.ui = _.extend({}, this.ui, this.options.ui);
        }
    });
    

    Note that options passed to a view are available in this.options automatically.


    What's the real goal behind this?

    If you're going to mess around the ui and the events with its callbacks, it would be best to define a new view.

    It looks like a XY problem where the real problem lies in the architecture of the app, but we can't help since you're asking about what's blocking you right now.