Work with unrecoverable values ​​in Backbone

Is there a standard way to deal with unrecoverable values ​​in Backbone.

eg.

MyModel = Backbone.extend(Backbone.Model, { initialize: function () { this.set({'inches': this.get('mm') / 25}); } }) 

If I call save () on this model, it will throw an error, since there is no corresponding database field for inches . I can come up with several ways to fix this, but I wonder if in this case it is best to use a tried and tested approach?

My current preferred solution is to extend the Backbone toJSON method and allow the dontCleanup boolean parameter to be dontCleanup so that it still returns all model values ​​(including non-reproducible ones) when needed, for example. to go to the template.

+7
source share
3 answers

I like the idea of ​​Peter Lyon. I have thought about this several times, but have not actually used it. However, for all the ways I dealt with, here are my two favorites:

  • Values ​​without attribute
  • Browse Models

Values ​​without attribute

It is simple: do not save the values ​​that you need in the standard attributes of the model. Instead, attach it directly to the object:

 myModel.someValue = "some value"; 

The big problem is that you are not getting all the events associated with calling set in the model. Therefore, I am inclined to this method, which does everything for me. For example, the general method that I put on the models is select to say that this model is selected:

 MyModel = Backbone.Model.extend({ select: function(){ if (!this.selected){ this.selected = true; this.trigger("change:selected", this, this.selected); } } }); 

In your case, I'm not sure if this will be a good approach. You have data that should be calculated based on the values ​​that are already in your attributes.

For this, I tend to use view models.

View models.

The basic idea is that you create a basic model that is saved as usual. But you come and create another model that inherits from your original, and adds all the data you need.

There are so many ways you can do this. Here is what could be a very simple version:

 MyModel = Backbone.Model.Extend({ ... }); MyViewModel = function(model){ var viewModel = Object.create(model); viewModel.toJSON = function(){ var json = model.toJSON(); json.inches = json.mm / 25; return json; }; return viewModel; }); 

The big advantage of wrapping this with Object.create is that you now have a prototype inheritance situation, so all of your standard model functionality still exists. We just redefined the toJSON method in the view model so that it returns a JSON object with the inches attribute.

Then, in the view for which this is necessary, you must wrap your model in an initialization function:

 MyView = Backbone.View.extend({ initialize: function(){ this.model = MyViewModel(this.model); }, 

render: function(){ var data = this.model.toJSON(); // returns with inches } });

code>

You can call new MyViewModel(this.model) if you want, but in the end do not change anything, because we explicitly return an instance of the object from the MyViewModel function.

When your view rendering method calls toJSON , you will get the inches attribute with it.

Of course, there are some memory and performance issues in this implementation, but they can be easily solved with some better code for the view model. This quick and dirty example should lead you to the next path.

+11
source

I think this should do it. Define your Model defaults as a valid schema and then return only the subset of this.attributes that acts during toJSON .

 var Model = Backbone.Model.extend({ defaults: { foo: 42, bar: "bar" }, toJSON: function () { var schemaKeys = _.keys(this.defaults); var allowedAttributes = {}; _.each(this.attributes, function (value, key) { if (_.include(schemaKeys, key)) { allowedAttributes[key] = value; } return allowedAttributes; } }); 

Note that _.pick will make the code a little shorter if you have an underscore 1.3.3. I have not seen the “tested and verified” agreement in my travels through the base community, and since the main part leaves so many options open, sometimes the agreements do not appear, but we will see what this question gives in stackoverflow.

+3
source

Working with attributes that are not stored in Backbone.js makes my head for a while, especially because I started using ember / ember-data, which handles various situations using computed properties, ember-data attributes, or controllers.

Many solutions suggest customizing the toJSON method. However, some popular Backbone plugins (especially those related to nested models) implement their own toJSON method and call Backbone.Model.prototype.toJSON to get an object representation of the model attributes. Therefore, by replacing the toJSON method in the model definition, you will lose some (potentially important) functions of these plugins.

The best I came up with is to include an excludeFromJSON key array in the model excludeFromJSON and overwrite the toJSON method on Backbone.Model.prototype :

 Backbone.Model.prototype.toJSON = function() { var json = _.clone(this.attributes), excludeFromJSON = this.excludeFromJSON; if(excludeFromJSON) { _.each(excludeFromJSON, function(key) { delete json[key]; }); } return json; }; MyModel = Backbone.Model.extend({ excludeFromJSON: [ 'inches' ] }); 

Thus, you will need to identify only the keys that have not been saved (if you forget to do this, you will soon be reminded when your server issues an error!). toJSON will behave as usual if there is no excludeFromJSON property.


In your case, inches is a computed property derived from mm , so it makes sense to implement this as a method on your model (guaranteeing the correct value for inches when changing mm):

 MyModel = Backbone.Model.extend({ inches: function() { return this.get('mm') / 25; } }); 

However, this has the flip side of accessing another anyother attribute. Ideally, you want to keep compatibility with other attributes. This can be achieved by extending the default get method :

 var getMixin = { get: function(attr) { if(typeof this[attr] == 'function') { return this[attr](); } return Backbone.Model.prototype.get.call(this, attr); } }; MyModel = Backbone.Model.extend({ inches: function() { return this.get('mm') / 25; } }); _.extend(MyModel.prototype, getMixin); 

That will allow:

 new MyModel().get('inches'); 

This approach does not affect the base hash of attributes , which means that inches will not be displayed in the toJSON , unless you set the inches value later, I need something like an excludeFromJSON array.

If you need to set the inches value, you can also listen to the changes and adjust the mm value:

 MyModel = Backbone.Model.extend({ initialize: function() { this.on('change:inches', this.changeInches, this); }, inches: function() { return this.get('mm') / 25; }, changeInches: function() { this.set('mm', this.attributes.inches * 25); } }); _.extend(MyModel.prototype, getMixin); 

See the full JSBin example .

It is also worth noting that the (official?) Purpose of the toJSON method toJSON recently been redefined as preparing the model for synchronization with the server. For this reason, the toJSON call should always return only "persistable" (or "saveable") attributes.

+2
source

All Articles