Git Product home page Git Product logo

backbone.mutators's Introduction

Backbone.Mutators

Backbone plugin to override getters and setters with logic

Build Status, Project Page, Annotated Source & Tests

Build Status Unit Test Status

Project Page
Docs
Tests
NPM registry

Introduction

Ever wanted Backbone to have getters and setters you can override with your own logic? Yes?! Then Backbone.Mutators is the missing tool in your chain...

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

VOLO

$ volo add Backbone.Mutators

NPM

$ npm install Backbone.Mutators

Integration

AMD

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

CommonJS

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

Vanilla JS

<!-- Vanilla javascript -->
<script src="path/to/underscore.js"></script>
<script src="path/to/backbone.js"></script>
<script src="path/to/backbone.mutators.js"></script>
<script>
	console.log(Backbone.Mutators); // 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.5

  • Extended mutators check #38
  • Allow to specify custom isSaving() method to detect when toJSON() is cal... #36

0.4.4

  • Add mutator dependancies and change events #33

0.4.3

  • Made compatible with browserify #32
  • Backbone dependency version bump #32

0.4.2

0.4.1

0.3.1

  • Change get context to modal instead of attributes
  • Added single getter / setter method

0.3.1

  • Change get context to modal instead of attributes
  • Added single getter / setter method

0.3.0

  • Removed the Cake based build process and moved to grunt
  • Mutators now integrates itself to backbone, no more manual extending needed
  • Added the {mutator: {silent: true}} option to prevent mutator set events from firering
  • Added unit tests for the new features
  • Moved from jslint to jshint
  • Tweaked docs
  • Removed not needed jquery and qunit-logging submodule / npm dependencies

0.2.0

  • Added the original Backbone.Model.set function as a fourth paramter for the mutated set
  • Added a 'mutators:set:{{YOUR_MUTATOR_PROPERTY}}' event when setting mutated properties
  • Added unit tests for the new features
  • Extended/fixed documentation
  • Added inline version tag [NOTE: Version 0.2.0 is fully backwards compatible]

0.1.0

  • Initial Release

License

MIT - https://tldrlegal.com/license/mit-license

backbone.mutators's People

Contributors

andrewhenderson avatar asciidisco avatar btakita avatar chrisbarless avatar der-on avatar eicca avatar hippich avatar louisbl avatar marothstein avatar rubiii avatar sandreu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

backbone.mutators's Issues

dependent fields?

hey @asciidisco,

i may be missing something, but it doesn't look like mutators supports dependent fields, right?
is there any good reason against this? if so, would you recommend any other library?

cheers,
daniel

Using w/ Require AMD - this.mutators is undefined throws Type Error - Line 169

When passing Mutators.js as a dependency on a model that did not have any mutators defined, a type error is thrown. Cannot read property 'forEach' of undefined - Line 169. This caused the entire app to break.

Adding a simple if statement resolved this. I will submit a pull request to help avoid this user error and allow the app to build as normal.

Bug in the documentation

Here is a bug:

 var Spicy = Backbone.Model.extend({
    // Define mutator properties
    mutators: {
        fullname: {
            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'

See https://github.com/asciidisco/Backbone.Mutators#use-mutated-setters-and-call-the-original-setter-within

As we can see setter is overridden for fullname property, but in the other code in the example iAcceptOnlyLowercaseStuff property is used.

How to prevent mutators events?

You have two things in README file: Catch model events and Silence mutator events (while keeping the model events fired). But both have same code sample... Am I missed something?

npm - TypeError: Cannot read property 'Backbone' of undefined

It looks like this.require is not defined, but require is. Same with this.exports and exports.

TypeError: Cannot read property 'Backbone' of undefined
at /Users/btakita/workspace/api-account/node_modules/backbone.mutators/backbone.mutators.js:94:41
at Backbone (/Users/btakita/workspace/api-account/node_modules/backbone.mutators/backbone.mutators.js:49:36)
at Object.<anonymous> (/Users/btakita/workspace/api-account/node_modules/backbone.mutators/backbone.mutators.js:90:2)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.require (module.js:362:17)
at require (module.js:378:17)

TypeError: Object #<Object> has no method 'apply'

Minimal example:

var AModel = Backbone.Model.extend({
    mutators: {
        foo: {
            set: function(key, value, options, set) {
                set(key, 'bar', options);
            }
        }
    },
    defaults: {
        foo: 'baz'
    }
 });

Calling (new AModel()).toJSON() results with error as above.
In the function Mutator.prototype.toJSON there is an assumption the mutator has always get method defined (under 'get' key of the mutator object or the mutator is a 'getter' itself). This is obviously wrong and such a check should be added:

if (_.isObject(mutator) === true && _.isFunction(mutator.get)) {
    attr[name] = _.bind(mutator.get, this)();
} else if (_.isFunction(mutator)) {
    attr[name] = _.bind(mutator, this)();
}

escape method cache issue

If you try to use "escape" method with mutators it will be cached, this is a problem since mutators are dynamic.

Example:

window.Example = Backbone.Model.extend({
    defaults:{
        a: 1,
        b: 1
    },
    mutators:{
        c: function(){
            return this.a + this.b;
        }
    }
});
var E = new Example();
alert(E.escape("c")); // displays 2
E.set("a", 2);
alert(E.escape("c")); // displays 2, must be 3

Possible fix:

// override get functionality to get HTML-escaped the mutator props
    Mutator.prototype.escape = function (attr){
        var val = this.get(attr);
        return _.escape(val == null ? '' : '' + val);
    };

release 0.3.1 via npm

hey guys,

thanks for this library!
any plans to release v0.3.1 via npm?

cheers,
daniel

Integrating mutators with backbone.relational

I've faced the following issue while trying to integrate Backbone.Mutators with Backbone.Relational which is used to build client side relations and helps to serialize/deserialize data into corresponding json/models.

For example lets assume we have a model (in coffeescript)

@ClientApp.module "Entities", (Entities, App, Backbone, Marionette, $, _) ->
  class Entities.Store extends Entities.RelationalModel

    defaults:
      location: -> new Entities.Location()

    mutators:
      store_name: ->
        @get("location")?.get("name")

What I have now is trying to access related model in mutator will result in an error. That is because backbone.relational haven't yet converted the model's sub-object into an instance of underlying model at the time backbone.mutators serialize data and hence @get('location') will return a json object instead of Entities.Location instance.
I've applied a quick fix by changing the library to save get and set methods from Backbone.RelationalModel.

var Mutator     = function () {},
  oldGet      = Backbone.RelationalModel.prototype.get,
  oldSet      = Backbone.RelationalModel.prototype.set,
  oldToJson   = Backbone.RelationalModel.prototype.toJSON;

...

_.extend(Backbone.RelationalModel.prototype, Mutator.prototype);

However i do not really like this fix as far as i will have to apply it every time Backbone.Mutators is updated. Perhaps someone has a more consistent solution? Also please let me know if you need any additional info.

`this` differs when in .get() and .toJSON()

I'm using the bleeding edge version. With Model#get(), this is the model. When using Model#toJSON(), this is the JSON object.

I did a console.log(this) in the mutator function and the following is the output:

>> TT.groups.at(1).get('title')
child {_queue: Backbone.BlockingQueue, cid: "c6", changed: Object, attributes: Object, _changes: Array[0]…}
<< "title"

>> TT.groups.at(1).toJSON()
Object {id: "PL8TioFHubWFtahrzBCr39Z2xNDDX3zyck", kind: "youtube#playlist", etag: ""eTr3dHIt5_K9qdGtRKL-5XdpiQI/mt4VOVwhB_pxNYU0-WoC0_4Xb8U"", snippet: Object, videosLoaded: true…}
<< Object

incompatible with browserify

Backbone.Mutators requires it's own Backbone module instead of the eventually installed parent backbone module when in CommonJS mode.

The solution would be to drop the backbone dependency like other Backbone plugins do and use it as a peerDependency only.

This way the already installed Backbone would get required instead.

Setting hashes with Mutators breaks Backbone atomicity

Consider model.set({key1: val1, key2: val2});

In traditional Backbone, this will set both keys to their respective values and then fire change:key1 and change:key2 events. In other words, Backbone guarantees the atomicity of set calls with hashes. Mutators breaks this contract by firing each change event after it's value is set. So Mutators would set key1 to val1, then fire the change:key1 event, then set key2 to val2, then fire the change:key2 event.

I believe this is because of the way that calls to oldSet works in the Mutators source.

scope of this

Seems a little strange to me that in the getter functions this refers to the model's attributes but in setters this refers to the model itself. Does there really need to be this inconsistency? It makes calling a method on the model hard in the getters.

Bug: Unable to set deep objects to models without mutators

You can not set deep objects to models without any mutators.

Example (data recieved from a form):

formData = {
  name: "Batman",
  car: "Batmobile",
  engine: "Jet Turbine",
  enemies: ["Godzilla", "Rodan", "Mothra", "Gorosaurus"],
  location: {
    atHome: false,
    inCave: true
  }
  ...
};

model.set(formData);

Error message:

Line 138: TypeError: can't convert key to string

The only solution is to change/undeclare the mutators:{} property to mutators: undefined in the model. Not that clean.

Getter update problem

I have the following problem when rending column values:

total: function() {
return this.get('baseprice') + this.get('workPrice'); --> 30
}

but when i do this.set('baseprice', 200), total value stills returns '30'

Combing Set / Get Method

Have you considered instead of writing:

 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.firstname + ' ' + this.lastname;
        }
    }

The methods could be combined into one?

 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.firstname + ' ' + this.lastname;
        }

I wouldn't mind implementing this.

Catching Models Events: Get Method Causes Uncaught TypeError: Object has no method 'get'

I received an error in my own implementation, so I used the reduced test case provided in the docs, https://github.com/asciidisco/Backbone.Mutators#catch-model-events, and receive the same error:

Uncaught TypeError: Object # has no method 'get'

It is caused by this line:

return this.get('firstname') + ' ' + this.get('lastname');

When I console log the this object within the mutator get function, it references the following object:

Object {firstname: "Big", lastname: "Mama"} 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.