1

I need virtual attributes in my backbone model but backbone doesn't appear to support this, how would I go about implementing this myself.

By virtual attributes, I mean attributes which are maintained client side only, and do not save/sync to the server. I would like to use them to store state information which is relevant to the client view rendering only, such as whether a model is selected / checked or not. I will use the model attributes in the template rendering of the view but I do not want these values sending to the server, or being saved in the database.

Jon Miles
  • 9,605
  • 11
  • 46
  • 66
  • Looks like similar question http://stackoverflow.com/questions/13051966/exclude-model-properties-when-syncing-backbone-js – Vokinneberg May 11 '14 at 20:55

2 Answers2

2

If you want to store attributes on the model just on the client-side, why not just use: model.set({attribute: "client-side only"}) instead of: model.fetch()

To avoid triggering change events, you can pass in: model.set({silent:true}) though the docs do not recommend this. If you're feeling more adventurous, you could also override the set method to be truly silent, as discussed in this answer:

Truly change a model attribute silently in Backbone.js?

UPDATE:

After looking around: Backbone.js partial model update and Exclude model properties when syncing (Backbone.js)

It looks like the simplest option you have is: model.save(model.changedAttributes(), {patch: true});

or if you ever need to create/update these objects in a good restful way, you can override the backbone sync like so:

 Backbone.Model.extend({

  // Overwrite save function
  save: function(attrs, options) {
   options || (options = {});

    // Filter the data to send to the server
    delete attrs.selected;
    delete attrs.dontSync;

    options.data = JSON.stringify(attrs);

    // Proxy the call to the original save function
    Backbone.Model.prototype.save.call(this, attrs, options);
  }
});

Credit: Simon Boudrias

Community
  • 1
  • 1
cs_stackX
  • 1,407
  • 2
  • 19
  • 27
  • But I need to fetch the model as it comes from the server, I just need somewhere/somehow to store temporary states attributes. I'm using marionette and a composite/collection view that is based on the collection results queried from the server. I'm worried that if I add attributes to these models, then the user edits and invokes a save these temporary attributes will be pushed to the server. – Jon Miles May 14 '14 at 19:30
  • Thanks for the update. Another option available to me. I'm still digesting the information but I think it might make more sense to override the save function than the toJSON. That way the virtual attributes are always available in views/templates, but are not sent to the server. – Jon Miles May 15 '14 at 09:59
1

A few options:

You could create a generic backbone model and use it to store & listen to properties of the view state.

StatefulView = Backbone.View.extend({
    initialize: function(options) {
        this.state = new Backbone.Model({
            selected: !!options.selected
        });
        this.listenTo(this.state, 'change:selected', this.renderSelectedState);
    },

    // ...
});

I would recommend against storing any kind of view-related property in your model, because as your app grows, it is not a scalable solution if you display the same model in multiple selectable lists, for example. But... You could override your model's toJSON function to remove attributes you don't want to persist:

ModelWithClientAttributes = Backbone.Model.extend({
    toJSON: function(withClientAttrs) {
        var json = Backbone.Model.prototype.toJSON.call(this);
        if (withClientAttrs) return json;
        return _(json).omit(this.clientAttrs);
    },

    // ...
});

// Extend from that base class when defining your model, and define the 'clientAttrs' property
Post = ModelWithClientAttributes.extend({
    clientAttrs: ['selected', 'checked'],
    // ...
});

// Then, in your view:
render: function() {
    this.$el.html(
        this.template(
            this.model.toJSON( true )
        )
    );
}

// Backbone.sync will call toJSON without arguments, so they will receive the filtered json representation
var post = new Post({
    selected: true,
    title: 'May Snow Storm',
    body: '...'
});
post.save();
console.log(post.toJSON()); // {"title": "May Snow Storm", "body": "..."}
console.log(post.toJSON(true)); // {"selected": true, "title": "May Snow Storm", "body": "..."}

Probably the safest and best solution is to simply whitelist the attributes that you want to persist in your server-side code.

colllin
  • 9,442
  • 9
  • 49
  • 65
  • Your toJSON suggestion sounds promising I will have to test it out. Although I agree about not storing this information in the model, but I'm using Marionette and the collection view renders the template from the collection of models... am I missing something, how else would you suggest I appropriate this problem? – Jon Miles May 14 '14 at 19:39
  • @JonathanMiles That's a good question. In my latest refactor, I've created a globally-accessible `App.Session` model (`App.Session = new Backbone.Model()`) that I can get & set properties on, and listen for changes to those properties. Then whenever your list is rendered, it can query the selection from this model and render appropriately (`var selected = App.Session.get('inboxSelected'); ...`) and when the selection changes, save it back (`App.Session.set('inboxSelected', ...)`). You could even add logic in `App.Session` to save the info in a cookie if you wanted to persist across page loads. – colllin May 14 '14 at 22:43
  • Thanks @colllin. I actually do something similar to this already, I've overridden templateHelpers in my views to add globally-accessible App.Settings, App.User etc which can then be accessed in any template using prefix settings.attr, user.attr. So I understand what you're suggesting. It's just every time I think about this problem, I keep coming back to the conclusion that this value should be stored against the models... but obviously without being saved to the server. Virtual attributes just seems like the logical answer to me, it's the first thing backbone has disappointed me on. – Jon Miles May 15 '14 at 09:53