Have one attribute passed to another attribute on the same Backbone model - backbone.js

I have a Backbone model named Summary which has attributes Total and DisplayTotal. What I would like to do is simply duplicate the value of Total and pass it to DisplayTotal.
Also is it possible to set a default for DisplayTotal?
defaults:{
"DisplayTotal" : this.Total,
}
If possible, as soon as the Total attribute is set, it should set its value to DisplayTotal too.

A few things are against this approach.
P.1. defaults is interpreted in Class definition time
So this shouldn't be what you expect, probably this would be window or something like this.
P.2. Backbone attributes are accessed through the Model.get() function
So your example is not correct it should be something like:
defaults:{
"DisplayTotal" : this.get( "Total" ),
}
But point P.1. is still making this to not work.
You have also a list of approaches to achieve want you want:
S.1. Initialize DisplayTotal in Model.initialize()
initialize: function(){
this.set( "DisplayTotal", this.get( "Total" ) );
}
S.2. Delay the DisplayTotal initialization to run time
defaults:{
"DisplayTotal" : function() { return this.get( "Total" ) }
}
Very danger solution, and probably you would have context problems with that this.
S.3. Update DisplayTotal anytime Total is updated
initialize: function(){
this.on( "change:Total", this.setDisplayTotal, this );
},
setDisplayTotal: function(){
this.set( "DisplayTotal", this.get( "Total" ) );
}
But I'm affraid you should combine this one with the S.1. due I don't think the event change is triggered in the first set of the attributes.

Related

Backbone does POST instead of PUT on updates when composite key is used

I'm using a composite key in my model and generate the ID based on my composite key:
app.Assignment = Backbone.Model.extend({
idAttribute : [ 'personId', 'jobId' ],
parse : function(resp) {
resp.id = resp.personId + "_" + resp.jobId;
return resp;
}
});
but Backbone still thinks that all instances of Assignment are new, allthough I'm setting the id in the parse method when fetching them from the API. As a result Backbone does no DELETEs and does a POST instead of PUT on updates. How can I work around this or what is the "right way" to do it?
Update:
Looks like replacing resp.id with this.id solves the issue.
The results of the parse method of a Backbone.Model are passed to the set method, which sets the attributes of the model. The point of confusion for you I think is that the model's ID isn't one of it's attributes; it's one of its properties.
So, what happens is this:
Your raw data comes back from the server and is passed to parse
That same raw data, now augmented with a id attribute, is passed to set
set looks and your idAttribute ([ 'personId', 'jobId' ]) and all of the keys in the raw data
Since none of those keys match the idAttribute, none of them are used as the model's ID, and thus you get your problem.
Your solution of setting this.id inside parse works, but it might cause problems down the road because parse is generally designed to operate on it's input (the raw data), not to modify the model itself; that part is supposed to happen next when set is called. A cleaner solution would instead be to do something like the following:
app.Assignment = Backbone.Model.extend({
// note that no idAttribute is specified, leaving it as the default "id"
parse : function(resp) {
resp.id = resp.personId + "_" + resp.jobId;
return resp;
}
}
Or, if you want a different ID attribute ...
app.Assignment = Backbone.Model.extend({
idAttribute: 'personAndJobId',
parse : function(resp) {
resp.personAndJobId = resp.personId + "_" + resp.jobId;
return resp;
}
}
Aside from the idAttribute issues here, you can always force Backbone to use a certain HTTP method via the type options passed to save().
model.save(null, { type: 'put' })
I've never work with composite ID in Backbone, but I think this could be an easy answer to your problem:
initialize: function() {
this.set("id", this.generateID());
},
generateID = function () {
return this.personId + + "_" + this.jobId;
}
With this code in you Backbone model definition you are creating a unique ID for each model and you shouldn't have problems for update and save it (and you don't need to set any idAttribute).

How to update attribute of an existing model?

I wanted to update the rank attribute of an existing model which I passed from another view. However, I get the error Uncaught TypeError: Object # has no method 'set'.
In the initialize part of the view, I have :
this.collection = new tgcollection({model : this.options.model });
I define a function updateModel intended to update the attribute value as:
updateModel: function(){
var val= $("#textbox_id").val();
console.log(val);
console.log(JSON.stringify(this.options.model));
JSON.stringify(this.options.model);
this.options.model.set({"rank": val});
this.render();
//
},
Where am I going wrong?
I can see the value and the model with its previous attribute values in the console.
The model:
define(['jquery','underscore', 'backbone', 'deepmodel'],
function($,_, Backbone) {
var model = Backbone.DeepModel.extend({
// Default attributes for the model.
defaults : {
id: null,
rank: null,
},
initialize: function(){
_.bindAll(this,"update");
this.bind('change : cost', this.update);
},
update: function(){
console.log(this.get("cost"));
},
// Remove this model from *localStorage*.
clear : function() {
this.destroy();
},
});
return model;
});
Just do
this.model.set({"rank": val});
instead of
this.options.model.set({"rank": val});
The model within a view is accessed via this.model not this.options.model
I love a good mystery. Here is my best guess based on what I see. The problem is probably even further back. Where you call:
this.collection = new tgcollection({model : this.options.model });
this.options.model is probably not what you think it is. It would be helpful to see the view BEFORE this view that is instantiating and passing in this.options.model. BTW, with models and collections passed into the view, you can always shorten it to this.model Model, Collection and a handful of others are special in that they get attached directly to the View once passed in.
I'm assuming that in your updateModel() the following SEEM to work:
console.log(JSON.stringify(this.options.model));
JSON.stringify(this.options.model);
The error is coming up on the set(), not the lines above. So the assumption is that you passed in a model. Or did you? My wild guess is that what this.options.model actually is, is just a json object of your model. This might explain why you "see" the model in your console when you stringify it, but then Backbone protests when you call set() on it.
Instead of JSON.stringify to test this.options.model try just console.log(this.options.model). Well, you don't have to test really. The fact that Backbone can't find set() on this object is a tell tale sign. If you're not seeing the complexity of a Backbone model in your console - it's not a model.
Also, for testing and debugging particularly models, I tend to use the model.toJSON() function as a quick check that it's a model and I'm seeing attributes I expect.
Let us know if you have more clues.

Correct way to process data inside of Backbone view template

Tell me please, what is best practice of data processing inside of view?
Example: I have a User model and there is field age. In this field is an age of user. It is an integer value - amount of months. And how can I implement this in my template:
17 => 1 year 5 months
11 => 11 months
24 => 2 years
Where can I store this helper-method? Inside of template is incorrect way. Otherwise I need to do some function that will generate correct model json. (not model.toJSON()) or extend existed JSON... Or ....
What is the best way to do this?
Thanks.
i gave an answer to a similar question months ago:
find it in this question backbone toJSON with helper methods
it comes down to adding methods to the json, before you go to the template
like this:
var userModel = Backbone.Model.extend({
initialize: function(){
_.bindAll(this, 'fullname', 'toFullJSON');
},
fullname: function(){
return this.get('name') + " " + this.get('lastname');
},
toFullJSON: function(){
var json = this.toJSON();
return _.extend(json, {fullname : this.fullname()});
}
});
var user = new userModel();
user.set({name: 'John', lastname: 'Doe'});
// you will see in this console log, that the toFullJSON function returns both the toJSON properties, and your added propert(y)(ies)...
console.log(user.toFullJSON());
another thing you could do is override the toJSON method
like this:
var myModel = Backbone.Model.extend({
// other methods and functions go here...
toJSON: function (attr) {
var defaultJSON = Backbone.Model.prototype.toJSON.call(this, attr)
return _.extend(defaultJSON, {calculateAge : this.calculateAge()});
},
calculateAge: function(){
// here you calculate the years and what not, then return it.
}
});
and a third way of doing this would be to give the model to your template instead of the .toJSON() return. then you can call model.CalculateAge() in your template.
There are two approaches.
You can put it on the model and then pass model to the template - it will mean that in the template you'll have to get the attributes using model.get('age') etc. but it will also make it possible to use helper methods for this model.
Another option is to have some kind of global helpers collection that later on you could access from your template like helpers.verboseAge(age) (don't know what templating scripts you are using so it could be it.age, this.age, age... but you get the idea.

Backbone - using a different field name for id

I'm porting an old app to use backbone.js (hopefully)
Problem is none of the various objects in the system use 'id' for id -
each object is different. Reading around, I've come up with the solution below when initializing the Backbone.Model.
initialize: function(){
this.idAttribute = 'user_ID';
this.set({'id':this.get('user_ID')});
}
I'm worried however that as I develop with backbone this approach is gonna bite me.
Does anyone have any experience with this.
Any pointers much appreciated.
edit: I just called isNew() on the model and got true, even though the id is now set to 1.
///////////////////////////////////////////
Using the following seems to sort it out.
User = Backbone.Model.extend({
idAttribute : 'user_ID'
})
When you use idAttribute, backbone basically keeps the id property in sync with the user_ID property. The way it's normally used is when you define your class
var UserModel = Backbone.Model.extend({
idAttribute: 'user_ID',
initialize: function() {
//do stuff
}
});
var me = new UserModel({user_ID: 1234, name: 'Tim'});
console.log(me.id); //1234
console.log(me.get('user_ID')); //1234
console.log(me.isNew()); //false
var me2 = new UserModel({name: 'newUser'});
console.log(me2.isNew()); //true
Good Luck

ExtJS: Ext.grid.Panel: how to keep sort order after store.sync()?

I have a nice working Ext.grid.Panel, with column headers you can click on to "automatic" sort.
The store has "autoSync: true".
I have a button "new", when the user clicks on it, it creates an empty record without the id property:
onAddClick: function(){
this.down('#new').setDisabled(true);
var rec = new GroupeSynergies.data.Partenaire({
/* Valeurs par défaut des colonnes */
description: 'Nouveau partenaire'
});
this.store.insert(0, rec);
},
I insert the record in the #0 position, because I know that it'll be automatically synced (and that's what's happening actually).
The problem is: if you click on the "id" column, it's sorted by id asc, if you click again, reverse order.
Then you click on the button "New", it creates empty new record, sends it to the server, and gets the resulting record with the id field completed, updates the grid, but... don't take in account the sort: when it's synced, the returned id is very high and it stays on the top, no matter what the sort order is. What am I doing wrong?
Thank you very much
(PS I'm asking at stackoverflow because Sencha's forum seems to be overwhelmed)
In a similar situation, the solution I've found is to add a store.sort in the 'write' event handler of the store.
Ext.application({
(...)
launch: function() {
var myStore = this.getStore('myStore');
myStore.on('write', function(store, operation){ store.sort('id','ASC') }; );
}
});
Adapt the sorting parameter to your needs. Documentation for the 'sort' method.
If you have a writer set on the proxy of your store, the sorting will also fire for a simple "update". To avoid this situation you can test the value of the operation parameter.
Documentation for the 'write' event.
I don't know if it is the better way of doing it, it's the only one I've found for now.
Here's another solution:
Ext.define('My.data.Store', {
extend: 'Ext.data.Store',
sortOnWrite: false,
resort: function () {
var me = this;
me.doSort(me.generateComparator());
},
onProxyWrite: function (operation) {
var me = this;
if (me.sortOnWrite && operation.wasSuccessful()) {
me.resort();
}
}
});

Resources