View on GitHub

Backbone.Model.Plus

Backbone.Model plugin that provides support for computed values (getters), mutators (setters), and nested data structures.

download .ZIPdownload .TGZ

Backbone.Model.Plus

Backbone plugin that provides support for accessors, mutators and nested objects.

Build Status, Project Page, Annotated Source & Tests

Build Status Unit Test Status

Project Page
Docs
Tests
NPM registry

Introduction

Backbone.Model.Plus is a fork of [Backbone.Mutators]((http://asciidisco.github.com/Backbone.Mutators/index.html) that adds support for nested objects in the model (e.g. name.first). I created this fork because I love and rely upon Backbone.Mutators, but since it overrides Backbone's get() and set() methods, I can't use it in conjunction with other model plugins (like backbone-deep-model).

Installation

The plugin itself implements the Universal Module Definition (UMD). You can use it with a CommonJS like loader, or with an AMD loader or via vanilla javascript.

The plugin has two dependencies, underscore.js and backbone.js

Download

You can directly download the Development Version or the Production Version from the root folder

Integration

AMD

// AMD
require(['underscore', 'backbone', 'path/to/backbone.model.plus'], function (_, Backbone, ModelPlus) {
  /* Do stuff with Backbone here */
});

CommonJS

// CommonJS
var _ = require('underscore');
var Backbone = require('backbone');
var Mutators = require('backbone.model.plus');

Vanilla JS

<!-- Vanilla javascript -->
<script src="path/to/underscore.js"></script>
<script src="path/to/backbone.js"></script>
<script src="path/to/backbone.model.plus.js"></script>
<script>
    console.log(Backbone.ModelPlus); // Backbone and the Mutators property are globals
</script>

Usage

Some lines of code explain more then thousand words...

Basic usage

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: function () {
            return this.get('firstname') + ' ' + this.get('lastname');
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

 var user = new User();
 // use get to get the 'mutated' value
 user.get('fullname') // 'Sugar Daddy'
 // serialize the model and see the 'mutated' value in the resulting JSON
 user.toJSON() // '{firstname: 'Sugar', lastname: 'Daddy', fullname: 'Sugar Daddy'}'

Override getters

 var State = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        status: function () {
            return this.get('status') === true ? 'Workish' : 'Bad bad error';
        }
    },
    defaults: {
        status: true
    }
 });

 var state = new State();
 // use get to get the 'mutated' value
 state.get('status') // 'Workish'
 // serialize the model and see the 'mutated' value in the resulting JSON
 state.toJSON() // '{status: 'Workish'}'

Use setters

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: {
            set: function (key, value, options, set) {
                var names = value.split(' ');
                this.set('firstname', names[0], options);
                this.set('lastname', names[1], options);
            },
            get: function () {
                return this.get('firstname') + ' ' + this.get('lastname');
            }
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

 var user = new User();
 // use get to get the 'mutated' value
 user.set('fullname', 'Big Mama', {silent: true});
 // serialize the model and see the 'mutated' value in the resulting JSON
 user.get('fullname') // 'Big Mama'
 user.get('firstname'); // 'Big'
 user.get('lastname'); // 'Mama'

Catch model events

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: {
            set: function (key, value, options, set) {
                var names = value.split(' ');
                this.set('firstname', names[0], options);
                this.set('lastname', names[1], options);
            },
            get: function () {
                return this.get('firstname') + ' ' + this.get('lastname');
            }
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

 var user = new User();

 // bind mutator event
 user.bind('mutators:set:fullname', function () {
    console.log('Somebody sets a full name');
 });

 // bind model events
 user.bind('change:firstname', function () {
    console.log('Somebody changed the first name');
 });

  // bind model events
 user.bind('change:lastname', function () {
    console.log('Somebody changed the last name');
 });

 // use get to get the 'mutated' value
 user.set('fullname', 'Big Mama');

 // serialize the model and see the 'mutated' value in the resulting JSON
 user.get('fullname') // 'Big Mama'
 user.get('firstname'); // 'Big'
 user.get('lastname'); // 'Mama'

Silence mutator events (while keeping the model events fired)

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: {
            set: function (key, value, options, set) {
                var names = value.split(' ');
                this.set('firstname', names[0], options);
                this.set('lastname', names[1], options);
            },
            get: function () {
                return this.get('firstname') + ' ' + this.get('lastname');
            }
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

 var user = new User();

 // bind mutator event
 // will never be run
 user.bind('mutators:set:fullname', function () {
    console.log('Somebody sets a full name');
 });

 // bind model events
 // will still run
 user.bind('change:firstname', function () {
    console.log('Somebody changed the first name');
 });

 // bind model events
 // will still run
 user.bind('change:lastname', function () {
    console.log('Somebody changed the last name');
 });

 // use get to get the 'mutated' value
 user.set('fullname', 'Big Mama', {mutators: {silence: true}});

 // serialize the model and see the 'mutated' value in the resulting JSON
 user.get('fullname') // 'Big Mama'
 user.get('firstname'); // 'Big'
 user.get('lastname'); // 'Mama'

Use mutated setters and call the original setter within

 var Spicy = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        iAcceptOnlyLowercaseStuff: {
            set: function (key, value, options, set) {
                // call the original setter with the lowercased value
                set(key, value.toLowerCase(), options);
            }
        }
    },
    defaults: {
        iAcceptOnlyLowercaseStuff: 'sugar'
    }
 });

 var spicy = new Spicy();
 // use get to get the 'mutated' value
 spicy.set('iAcceptOnlyLowercaseStuff', 'SALT');
 spicy.get('iAcceptOnlyLowercaseStuff') // 'salt'

Define one getter / setter method

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: function (key, value, options, set) {
            if(key){
                var names = value.split(' ');
                this.set('firstname', names[0], options);
                this.set('lastname', names[1], options);
            }

            return this.get('firstname') + ' ' + this.get('lastname');
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

Define multiple mutators

 var User = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: {
            set: function (key, value, options, set) {
                var names = value.split(' ');
                this.set('firstname', names[0], options);
                this.set('lastname', names[1], options);
            }
            get: function () {
                return this.get('firstname') + ' ' + this.get('lastname');
            }
        },
        password: function () {
            return md5(this.password);
        }
    },
    defaults: {
        firstname: 'Sugar',
        lastname: 'Daddy'
    }
 });

Define a getter as transient

Defining a getter as transient means that it will be omitted when Backbone saves the model. This is useful if the backend system (whatever Backbone is syncing to) fails if you send it a property that does not actually exist on the model. Note that this only works for mutators defined with a get() function.

In the example below, the fullName property will be available when toJSON is called under non-syncing circumstances--for example, when providing this model to a template--but will be omitted from the JSON when sync is called (because you called the sync() or save() method), and will not be sent to the server.

var Model = Backbone.Model.extend({
  defaults:{
    firstName:"Iain",
    middleInit:"M",
    lastName:"Banks"
  },
  mutators:{
    fullName:{
      get: function() {
        var fullName = this.get("firstName");
        fullName += " " + this.get("middleInit");
        fullName += ". " + this.get("lastName");
        return fullName;
      },
      transient: true
    }
  }
});

Further reading

James Brown (@ibjhb) has written a blog article about Mutators (Exploring Backbone.Mutators)

Changelog

0.4.1

0.3.1

0.3.1

0.3.0

0.2.0

0.1.0