Override backbone 'set' method - backbone.js

I want to override backbone set method so that whenever I set a value to backbone Model the callbacks registered on that attribute get called without checking for same previous value of that attribute .
var model = Backbone.Model.extend({
defaults : {
prop1 : true
}
});
var view = Backbone.View.extend({
initialize : function(){
this.listenTo(this.model,"change:prop1", this.callback);
},
callback : function(){
// set is called on prop1
}
});
var m1 = new model();
var v1 = new view({model:m1});
m1.set("prop1",true); // It doesn't trigger callback because I'm setting the same value to prop1

You can write a new method in backbone model set like this :
var model = Backbone.Model.extend({
defaults: {
prop1: true;
},
// Overriding set
set: function(attributes, options) {
// Will be triggered whenever set is called
if (attributes.hasOwnProperty(prop1)) {
this.trigger('change:prop1');
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
});

Actually, the signature of function set differs from the one depicted in the accepted answer.
export var MyModel = Backbone.Model.extend({
set: function (key, val, options) {
// custom code
var result = Backbone.Model.prototype.set.call(this, key, val, options);
// custom code
return result;
}
});
See:
http://backbonejs.org/docs/backbone.html
https://github.com/jashkenas/backbone/issues/1391

Related

How to determine what attributes changed on sync

As the title says, is there any way for me to determine what attributes on my model changed when synced?
For me the ideal situation would be a mechanism that functions like the change listener, so I'd have something like:
var MyModel = Backbone.Model.extend({
initialize: function(){
this.on({
'sync:Name' : 'onSyncName'
})
onSyncName: function(){
alert('Name saved!');
}
}
})
From what I can tell this mechanism doesn't exist for individual attributes as a whole, sync will only refer to the entire model itself.
You can use the changedAttribute function from the model which returns a hash of the attributes changed from last set.
var MyModel = Backbone.Model.extend({
initialize: function(){
this.on({
'sync' : 'onSync'
});
},
onSync: function(model){
_.each( model.changedAttributes(), function(value, key, list) { console.log("key" + key + " value " + value);
}
})
The sync event for only an attribute doesn't exist and doesn't make sense as it is the whole model which is syncing
You could add a validate method in your Model and use it to save the changed attributes inside a variable:
var lastChanged = {};
var MyModel = Backbone.Model.extend({
initialize: function(){
this.on({
'sync' : 'onSync'
});
},
onSync: function(){
if(lastChanged.hasOwnProperty("Name")){
alert("Name changed!");
}
},
validate : function(attrs, options){
lastChanged = options.changed;
}
});

Backbone Model gives this.set not a function in Model.initialize

I've a model listen on the vent for a event update:TotalCost, which is triggered from (unrelated) Collection C when any model M belonging to collection C changes.
This event is coded in the initialize method as below. On receiving the event I get the following error:
TypeError: this.set is not a function
this.set({ "totalsale": value});
CostModel = Backbone.Model.extend({
defaults: {
totalSale: 0,
totalTax: 0
},
initialize: function(attrs, options) {
if(options) {
if(options.vent) {
this.vent = options.vent;
}
}
this.vent.on("update:TotalCost", function(value) {
this.set({ "totalSale": value}); **//ERROR HERE**
});
}
});
It is highly possible you've forgot to add the new keyword before your model for example you have:
var user = UserModel();
// instead of
var user = new UserModel();
Have you tried using a closure?
CostModel = Backbone.Model.extend({
defaults: {
totalSale: 0,
totalTax: 0
},
initialize: function(attrs, options) {
var self = this;
if(options) {
if(options.vent) {
this.vent = options.vent;
}
}
this.vent.on("update:TotalCost", function(value) {
self.set({ "totalSale": value});
});
}
});
Perhaps you want this to refer to current CostModel instance, to do so you need to pass this to this.vent.on call so event callback will be executed in context of model:
this.vent.on("update:TotalCost", function(value) {
this.set({ "totalSale": value});
}, this);
it may be due to 'set' works on model not on object. so you can, first convert your object in to model then try..
in example:
new Backbone.Model(your_object).set('val', var);
Another cause of this error can be if you try to create a new model without using the "new" keyword
I was getting this mysterious error when using it with Parse. I had:
Parse.User().current().escape("facebookID")
... when I should have had:
Parse.User.current().escape("facebookID")
Removed the extra () and it works fine now.
Another cause:
// render() method in view object
setInterval(this.model.showName, 3000);
// showName() method in model object
showName: function(){
console.log(this.get('name')); // this.get is not a function
}

Change event is not getting trigger when updating model in backbone js

I have a view called layerPanel that is using screenData model. Now on model.set i get update event from model itself, but its not working on view.
MODEL
var screenData = Backbone.Model.extend({
initialize : function() {
_.bindAll(this,"update");
this.bind('change:vdata', this.update);
},
update: function() {
var obj = this.vdata;
alert("update");
},
vdata:[{id : 0, title : "Welcome to Persieve 0"}]
});
VIEW
var layerPanel = Backbone.View.extend({
el: "#allLayers",
model: new screenData(),
initialize: function() {
this.render();
this.model.bind('change:vdata', this.render);
},
render: function() {
this.template = _.template(LayersTpl, {splitData: this.model.vdata});
this.$el.html(this.template);
return this;
}
});
Here is how I set values in Model.
screendata = new screenData;
var obj = screendata.vdata;
obj[obj.length] = {id : $(".bullet").length, title : "Welcome to Persieve"};
var tempData = [];
for ( var index=0; index<obj.length; index++ ) {
if ( obj[index]) {
tempData.push( obj );
}
}
obj = tempData;
screendata.set({vdata:[obj]});
The event should fire. But your render wont work as the 'this' context needs setting.
try:
this.model.bind('change:vdata', this.render, this);
or even better, use listenTo and the context is implicit (+ you can clean up easily this.remove())
Edit. From the edit you made above, I can see that you are creating a new screendata instance. The binding you created is for a different instance model: new screenData() .
You must reference the binded object and set it if you want the event to trigger.
If all the model setting happens in the actual model. Call this.set({vdata:[obj]});

How can I bind the model to the view?

When the view is initialized, how can I bind the model to the specific View that is created? The view is current initialized at the start of the application. Also, how can I bind the model to the collection?
(function ($) { //loads at the dom everything
//Creation, Edit, Deletion, Date
var Note = Backbone.Model.extend({
defaults: {
text: "write here...",
done: false
},
initialize: function (){
if(!this.get("text")){
this.set({"text": this.default.text});
}
},
edit: function (){
this.save({done: !this.get("done")});
},
clear: function (){
this.destroy();
}
});
var NoteList = Backbone.Collection.extend({
model:Note
});
var NoteView = Backbone.View.extend ({
el: "body",
initialize: function(){
alert("initialized");
var list = new NoteList;
return list;
},
events: {
"click #lol" : "createNote"
},
createNote : function(){
var note = new Note;
this.push(note);
alert("noted");
}
});
var ninja = new NoteView;
})(jQuery);
Update
I just took a look at #James Woodruff's answer, and that prompted me to take another look at your code. I didn't look closely enough the first time, but I'm still not sure what you're asking. If you're asking how to have a model or view listen for and handle events triggered on the other, then check out James's example of calling bind() to have the view listen for change (or change:attr) events on the model (although I'd recommend using on() instead of bind(), depending what version of Backbone you're using).
But based on looking at your code again, I've revised my answer, because I see some things you're trying to do in ways that don't make sense, so maybe that's what you're asking about.
New Answer
Here's the code from your question, with comments added by me:
var NoteView = Backbone.View.extend ({
// JMM: This doesn't make sense. You wouldn't normally pass `el`
// to extend(). I think what you really mean here is
// passing el : $( "body" )[0] to your constructor when you
// instantiate the view, as there can only be one BODY element.
el: "body",
initialize: function(){
alert("initialized");
// JMM: the next 2 lines of code won't accomplish anything.
// Your NoteList object will just disappear into thin air.
// Probably what you want is one of the following:
// this.collection = new NoteList;
// this.list = new NoteList;
// this.options.list = new NoteList;
var list = new NoteList;
// Returning something from initialize() won't normally
// have any effect.
return list;
},
events: {
"click #lol" : "createNote"
},
createNote : function(){
var note = new Note;
// JMM: the way you have your code setup, `this` will be
// your view object when createNote() is called. Depending
// what variable you store the NoteList object in (see above),
// you want something here like:
// this.collection.push( note ).
this.push(note);
alert("noted");
}
});
Here is a revised version of your code incorporating changes to the things I commented on:
var NoteView = Backbone.View.extend( {
initialize : function () {
this.collection = new NoteList;
},
// initialize
events : {
"click #lol" : "createNote"
},
// events
createNote : function () {
this.collection.push( new Note );
// Or, because you've set the `model` property of your
// collection class, you can just pass in attrs.
this.collection.push( {} );
}
// createNote
} );
var note = new NoteView( { el : $( "body" )[0] } );
You have to bind views to models so when a model updates [triggers an event], all of the corresponding views that are bound to the model update as well. A collection is a container for like models... for example: Comments Collection holds models of type Comment.
In order to bind a view to a model they both have to be instantiated. Example:
var Note = Backbone.Model.extend({
defaults: {
text: "write here..."
},
initialize: function(){
},
// More code here...
});
var NoteView = Backbone.View.extend({
initialize: function(){
// Listen for a change in the model's text attribute
// and render the change in the DOM.
this.model.bind("change:text", this.render, this);
},
render: function(){
// Render the note in the DOM
// This is called anytime a 'Change' event
// from the model is fired.
return this;
},
// More code here...
});
Now comes the Collection.
var NoteList = Backbone.Collection.extend({
model: Note,
// More code here...
});
Now it is time to instantiate everything.
var Collection_NoteList = new NoteList();
var Model_Note = new Note();
var View_Note = new NoteView({el: $("Some Element"), model: Model_Note});
// Now add the model to the collection
Collection_NoteList.add(Model_Note);
I hope this answers your question(s) and or leads you in the right direction.

Trouble saving a model within a view with backbone.js

I've got a view setup that shows a couple of input fields. When either of the fields is changed I want to save the changed value for the model.
Here's my view:
EditWordView = Backbone.View.extend({
tagName: 'article',
className: 'word',
initialize: () ->
_.bindAll this, 'render'
this.template = _.template($('#edit-word-template').html())
this.model.bind 'change', this.render
,
events: {
"change input": "changed"
},
render: () ->
this.model.fetch()
$word = $('#word')
$word.empty()
renderContent = this.template(this.model.toJSON())
$(this.el).html(renderContent)
$word.append(this.el)
this
,
changed: (event) ->
changed = event.currentTarget
value = $("#" + changed.id).val()
obj = {}
obj[changed.id] = value
this.model.save(obj)
,
save: (event) ->
this.model.save()
view = new WordView({model: this.model})
view.render()
event.preventDefault()
})
And here's the model:
SpellingWord = Backbone.Model.extend({
url: () ->
'/spelling_words/' + this.id
})
When I get to the changed event and try to save the model
this.model.save(obj)
has no effect
The set(attrs) method is called on the model within the save method in backbone.js but the value is never changed in the model and hence never persisted back to the backend. Is this something to do with the scope of the this.model object?
If I call
this.model.set(obj)
before this.model.save(obj) then value of the model's attributes never changes.
set(attrs) works within the console for objects so I'm a bit stumped as to why it won't in my view.
Ideas?
EDIT>>>>>>>
I've dug into the backbone code and in the save method set is called:
save : function(attrs, options) {
options || (options = {});
if (attrs && !this.set(attrs, options)) return false;
var model = this;
var success = options.success;
After the this.set(attrs, options) method is called the attributes of the model aren't changed.
Now, looking in the set method I don't see how the attribute is supposed to be changed when it's actually changing the now variable?
var now = this.attributes, escaped = this._escapedAttributes;
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
// We're about to start triggering change events.
var alreadyChanging = this._changing;
this._changing = true;
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
Well, looks like
this.model.bind 'change', this.render
was calling the render method from inside save, which first did a fetch and overwrote my changes. The save then didn't take place because the attributes hadn't been changed.
It was always going to be something like that.....

Resources