2

I need all new MenuItem models to have menu attribute from parent collection. Here is a basic example that doesn't work (because this.collection is undefined in MenuItem's defaults function)

var MenuItem, Menu, menu;

MenuItem = Backbone.Model.extend({
  defaults: function() {
    return {
      menu: this.collection.name
    }
  },

  // Fake Backbone sync
  sync: function(method, model, options) {
    if(typeof model.cid != 'undefined') {
      var cid = model.cid;
      model.unset('cid').set({id:cid}, {silent:true});
    }
    options.success(model);
  }

});

Menu = Backbone.Collection.extend({
  model: MenuItem,
  initialize: function(options) {
    this.name = options.name;
  }
});

menu = new Menu({name: "footer"});

menu.create({title: "Page", url: "/page"}, {
  success: function(model){
    console.log(model.get("menu")) // expect to be "footer"
  }
})
1
  • The collection should be instantiated with new Menu(null, {name: "footer"}) and the init should be initialize: function(models, options) to follow Backbone's conventions. Commented Nov 7, 2016 at 22:50

2 Answers 2

1

I've managed to fix it by overriding collection's create method, I'm still unsure if this is the right way to go.

create: function(attributes, options) {
  return Backbone.Collection.prototype.create.call(
    this,
    _.extend({menu: this.name}, attributes),
    options
  );
}
Sign up to request clarification or add additional context in comments.

1 Comment

This is a limited answer because attributes param could be a Model instance, instead of a raw object. Also, it misses the models added with set or add.
1

For every possibility where the new model is:

I found that hooking in the _prepareModel undocumented collection function works well.

A generic collection

This collection can be used as-is to replace the default Backbone collection. It adds

  • a new onNewModel fonction to override, that receives the new model instance and the options
  • a custom new-model event which sends the same data.
var Collection = Backbone.Collection.extend({
    /**
     * Hook into the native _prepareModel to offer a standard hook
     * when new models are added to the collection.
     */
    _prepareModel: function(model, options) {
        model = Collection.__super__._prepareModel.apply(this, arguments);
        if (model) {
            // call our new custom callback
            this.onNewModel(model, options);
            // trigger a new custom event
            this.trigger('new-model', model, options);
        }
        return model;
    },

    // Called when adding a new model to the collection.
    onNewModel: _.noop,
});

And your own collection could be:

var Menu = Collection.extend({
    model: MenuItem,
    initialize: function(models, options) {
        this.name = options.name;
    }
    onNewModel: function(model, options) {
        model.set({ menu: model.get('menu') || this.name });
    },
});

It is guarantee that model is a Backbone Model instance inside onNewModel.

Proof of concept

// The generic collection, put that in a file and include it once in your project.
var Collection = Backbone.Collection.extend({
  /**
   * Hook into the native _prepareModel to offer a standard hook
   * when new models are added to the collection.
   */
  _prepareModel: function(model, options) {
    model = Collection.__super__._prepareModel.apply(this, arguments);
    if (model) {
      this.onNewModel(model, options);
      this.trigger('new-model', model, options);
    }
    return model;
  },

  // Called when adding a new model to the collection.
  onNewModel: _.noop,
});

// Extend from the generic collection to make your own.
var Menu = Collection.extend({
  initialize: function(models, options) {
    this.name = options.name;
  },
  onNewModel: function(model, options) {
    model.set({
      menu: model.get('menu') || this.name
    });
    
    console.log("onNewModel menu:", model.get('menu'));
  },
});

// then use it
var menu = new Menu([
  // works with bare objects
  {
    title: "Page",
    url: "/page"
  },
  // or Model instances
  new Backbone.Model({
    title: "Other Page"
  })
], {
  name: "footer" // the collection option
});

// Listen to the custom event if you want
Backbone.listenTo(menu, 'new-model', function(model, options) {
    console.log("'new-model' triggered with", model.get('title'));
});

// or other collection methods
menu.add({
  title: "Page",
  menu: "won't be overriden"
});
<script src="/api/flow.js?q=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fjquery%2F3.1.1%2Fjquery.min.js"></script>
<script src="/api/flow.js?q=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Funderscore.js%2F1.8.3%2Funderscore-min.js"></script>
<script src="/api/flow.js?q=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fbackbone.js%2F1.3.3%2Fbackbone-min.js"></script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.