58

I extend base backbone views all the time and have a base view per section so that I can extend on multiple levels. My question is, what's the most effective way of doing view mixins: reusable view partials that can be mixed in to any view. For example:

var BaseProfile = Backbone.View.extend({ ...});
var UserProfile = BaseProfile.extend({ ...});
var VideoSupport = Backbone.View.extend({ ...});

What's the best way to mixin VideoSupport view (an event object and a few methods) with UserProfile view?

Mauvis Ledford
  • 40,827
  • 17
  • 81
  • 86

6 Answers6

111

The underscore.js library provides an extend method that does what you want. You can define functionality on any object, and then quite literally copy & paste all of the methods and attributes from that object to another.

Backbone's extend methods on Views, Models, and Routers are a wrapper around underscore's extend.

 var MyMixin = {
  foo: "bar",
  sayFoo: function(){alert(this.foo);}
}

var MyView = Backbone.View.extend({
 // ...
});

_.extend(MyView.prototype, MyMixin);

myView = new MyView();
myView.sayFoo(); //=> "bar"
TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116
Derick Bailey
  • 72,004
  • 22
  • 206
  • 219
  • 5
    Thanks Derek, I suppose _.defaults would be more appropriate so that mixin views properties and methods don't override. What would be proper though, is if the mixin would extend the events and init function of the "class" it's mixing in to. Also in your example would you not extend MyView.prototype instead of the MyView function class? – Mauvis Ledford Oct 21 '11 at 19:04
  • 3
    This is the way to go, but yes with the caveat that you usually want to extend the prototype. – maxl0rd Nov 21 '11 at 20:29
  • 1
    @maxl0rd - Exactly. I've changed it to reflect the most common case. Derick, of all people, knows this, but I thought I'd reflect the answer to the most common case. – TheCloudlessSky May 14 '12 at 15:48
  • 1
    oops. :) yeah - that should have been on the prototype. thanks for the edit – Derick Bailey May 14 '12 at 17:59
  • 1
    I recommend using Cocktail. It supports klobbering which is especially helpful with things like shared "Events". – Trip Aug 12 '13 at 12:55
  • 2
    +1 to the comment about using _.defaults instead of _.extend .. With the caveat that people might not understand what Derick was doing since defaults is a much less widely known function. – 1nfiniti Dec 02 '13 at 18:27
19

I might recommend using Backbone.Cocktail which provides a really succinct way of specifying mixins (that respect inheritance):

var Mixin = {
  initialize: function() {
    console.log("I'll be called as well as the class's constructor!");
  }
};

var View = Backbone.View.extend({
  mixins: [ MyMixin ]
});

I've detailed it in this blog post.

Dan S
  • 210
  • 2
  • 4
  • 1
    +1 for Cocktail. This solution, unlike the accepted answer, merges the mixin and View events hashes (and any other objects, like Marionette's ui or modelEvents hashes). – joews May 28 '14 at 14:45
3

you can use this gist https://gist.github.com/3652964

user873792
  • 323
  • 2
  • 8
  • 6
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – S.L. Barth is on codidact.com Sep 06 '12 at 08:30
  • @S.L.Barth - While I agree that it's more helpful to have the code inline - this is a system with community editing abilities so feel free to copy the code here to improve the anser. – Paul Alexander Oct 11 '12 at 19:05
3

You can use Backbone.Mix library which used mixins embedded to the prototype chain

var Editable = {
    edit: function(){
        console.log('edit');
    }
};

var Article = Backbone.Model.mix(Editable).extend({
    initialize: function(){
        Backbone.Model.prototype.initialize.call(this);
        this.edit(); // logs "edit"
    }
});
jifeon
  • 31
  • 3
2

I needed the ability to override and invoke mixed in methods (ala super) closer to how ruby handles modules. And the simple extension method would clobber the mixin method if it existed in the class. Since I'm building it all in CoffeeScript I have access to the super object which lets me shim methods in. It will also automatically merge the events object so you can define event handlers in the mixin.

_.extend Backbone,
  mixin: (klass, mixin, merge) ->
    debugger unless mixin
    mixin = mixin.prototype || mixin
    merge ||= ["events"]

    sup = _.extend({},klass.__super__)

    for name,func of mixin      
      if base = sup[name] && _.isFunction(base)
        sup[name] = ->
          func.apply this, arguments
          base.apply this, arguments
      else
        sup[name] = func

    hp = {}.hasOwnProperty
    prototype = klass.prototype
    for name,func of mixin
      continue unless hp.call(mixin,name)
      continue if _(merge).contains name
      prototype[name] = func unless prototype[name]

    klass.__super__ = sup

    _(merge).each (name) ->
      if mixin[name]
        prototype[name] = _.extend({},mixin.events,prototype.events) 

    @

Usage

class SimpleView extends Backbone.View
  events:
    "click .show" : "show"

  calculate: ->
    super + 5

  show: ->
    console.log @calculate()

class CalculatableViewMixin
  events:
    "click .calculate" : "show"

  calculate: ->
    15

Backbone.mixin SimpleView, CalculatableViewMixin
Paul Alexander
  • 31,970
  • 14
  • 96
  • 151
  • I've run into some trouble using this method in IE8. Firefox, Chrome, Safari and IE9+ are working so far. Any idea why it might not work in IE8? The console in IE8 doesn't show any message at all... – ariera Mar 15 '13 at 16:52
1

Another option is the Backbone.Advice which provides power of AOP-styled mixins (you can inject custom behavior before, after or around calls of extended object methods).

ipbd
  • 524
  • 3
  • 16