BackboneJS - View class array variables retained after re-instantiation - backbone.js

I encounter a scenario/bug which I can't explain, as I have a Backbone View class with array variable, but the variable value persist even after I re-instantiate it. The backbone view has following setup:
var TestView = Backbone.View.extend({
a:"",
b:"",
items:[],
initialize:function(){
},
add:function(value){
this.items.push(value);
}
});
Here's how I instantiate the class:
this.formView = new TestView();
this.formView.add('halo');
this.formView.a = 'abc';
this.formView = new TestView();
this.formView.add('test');
this.formView.b = 'bcd';
console.log("a - " + this.formView.a);
console.log("b - " + this.formView.b);
console.log("items - ");
console.log(this.formView.items);
​
Result:
a -
b - bcd
items - ["halo", "test"]
Surprisingly, the array variable 'items' persist and it shown both ['halo','test'] together. But not for the normal variables.
Here's the JsFiddle link.
It is solvable by clearing the array at initialize function.
initialize:function(){
this.items = [];
},
But I would like to know is this a bug or I misunderstood something.

Well, the issue is tricky but it has an explanation.
First of all is not good idea to define instance variables in a class context and this is what you are doing with this:
var TestView = Backbone.View.extend({
a: "",
b: "",
items: []
});
This is one of the reasons Model.defaults exists. You rather should to this:
var TestView = Backbone.View.extend({
initialize: function(){
this.a = "";
this.b = "";
this.items = [];
}
});
What is happening in your example code is that all the new references of TestView will share the same instances of String and Array. this is because changing the Array in one instance will reflect in the other instance.
The most tricky thing, and you was agile to observe, is that it looks like the sharing behavior is not happening with the Strings but in fact it is happening.
What is happening is that when you do this:
this.formView.a = 'abc';
You are not transforming the String instance but replacing it for a new one so the original String instance remains untouched.
I would like to offer an example of manipulating the String in one TestView instance and seeing the changes reflect in the other TestView instance but I don't found any JS String method that manipulates the String in-place.

Related

backbone.js set in model initialize not effecting models in collection

While performing a fetch() on my backbone collection, and instantiating models as children of that collection, I want to add one more piece of information to each model.
I thought that I could do this using set in the model initialize. (My assumption is that fetch() is instantiating a new model for each object passed into it. And therefore as each initialize occurs the extra piece of data would be set.
To illustrate my problem I've pasted in four snippets, first from my collection class. Second the initialize function in my model class. Third, two functions that I use in the initialize function to get the needed information from the flickr api. Fourth, and finally, the app.js which performs the fetch().
First the collection class:
var ArmorApp = ArmorApp || {};
ArmorApp.ArmorCollection = Backbone.Collection.extend({
model: ArmorApp.singleArmor,
url: "https://spreadsheets.google.com/feeds/list/1SjHIBLTFb1XrlrpHxZ4SLE9lEJf4NyDVnKnbVejlL4w/1/public/values?alt=json",
//comparator: "Century",
parse: function(data){
var armorarray = [];
var entryarray = data.feed.entry;
for (var x in entryarray){
armorarray.push({"id": entryarray[x].gsx$id.$t,
"Filename": entryarray[x].gsx$filename.$t,
"Century": entryarray[x].gsx$century.$t,
"Date": entryarray[x].gsx$date.$t,
"Country": entryarray[x].gsx$country.$t,
"City": entryarray[x].gsx$city.$t,
"Type": entryarray[x].gsx$type.$t,
"Maker": entryarray[x].gsx$maker.$t,
"Recepient": entryarray[x].gsx$recipient.$t,
"Flickrid": entryarray[x].gsx$flickrid.$t,
"FlickrUrl": "", //entryarray[x].gsx$flickrurl.$t,
"FlickrUrlBig": ""//entryarray[x].gsx$flickrurlbig.$t,
});
}
return armorarray;
}
});
Second, the initialization in my model.
initialize: function(){
//console.log("A model instance named " + this.get("Filename"));
item = this;
var flickrapi = "https://api.flickr.com/services/rest/?&method=flickr.photos.getSizes&api_key=<my_apikey>&photo_id=" + this.get("Flickrid") + "&format=json&jsoncallback=?";
sources = getFlickrSources(flickrapi);
sources.then(function(data){
sourceArray = parseFlickrResponse(data);
FlickrSmall = sourceArray[0].FlickrSmall;
console.log (FlickrSmall);
item.set("FlickrUrl", FlickrSmall);
console.log(item);
});
Notice here how I'm getting the "Flickrid" and using to get one more piece of information and then trying to add it back into the model with item.set("FlickrUrl", FlickerSmall);
console.log confirms that the property "FlickrUrl" has been set to the desired value.
Third, these are the functions my model uses to get the information it needs for the flicker api.
var getFlickrSources = function(flickrapi){
flickrResponse = $.ajax({
url: flickrapi,
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp"})
return flickrResponse;
}
var parseFlickrResponse = function(data){
flickrSourceArray = []
if (data.stat == "ok"){
sizeArray = data.sizes.size;
for (var y in sizeArray){
if (sizeArray[y].label == "Small"){
flickrSourceArray.push({"FlickrSmall": sizeArray[y].source});
}
else if (sizeArray[y].label == "Large"){
flickrSourceArray.push({"FlickrLarge": sizeArray[y].source});
}
}
}
return flickrSourceArray
}
But, fourth, when I try to perform the fetch and render the collection, I only get objects in my collection without the FlickrUrl property set.
//create an array of models and then pass them in collection creation method
var armorGroup = new ArmorApp.ArmorCollection();
armorGroup.fetch().then(function(){
console.log(armorGroup.toJSON());
var armorGroupView = new ArmorApp.allArmorView({collection: armorGroup});
$("#allArmor").html(armorGroupView.render().el);
});
var armorRouter = new ArmorApp.Router();
Backbone.history.start();
The console.log in this last snippet prints out all the objects/models supposedly instantiated through the fetch. But none of them include the extra property that should have been set during the initialization.
Any ideas what is happening?
What is this function ? getFlickrSources(flickrapi)
Why are you using this.get in the initialize function. Honestly it looks over-complicated for what you are trying to do.
If you want to set some parameter when you instantiate your model then do this var model = new Model({ param:"someparam", url:"someurl",wtv:"somewtv"});
If the point is to update your model just write an update function in your model something like update: function (newparam) { this.set;... etc and call it when you need it.
If I read you well you just want to set some params when your model is instantiated, so just use what I specified above. Here is some more doc : http://backbonejs.org/#Model-constructor
I hope it helps.
edit:
Put your call outside your model, you shouldn't (imo) make call inside your model this way it seems kinda dirty.
Sources.then(function(flickrdata) {
var mymodel = new Model({flicker:flickrdata.wtv});
});
It would be cleaner in my opinion.

Backbone model url method

When I set idAtribute of a model I expect it to be used in url. Is it the right thing to expect or am I missing something? I see that backbone model sees 'id' attribute on itself and uses it to build url, but I explicitly told it to use '_id' property. It doesn't look like the right behavior to me.
var model = new Backbone.Model();
model.set('id', 1); // if you remove this line everything works properly
model.set('_id', 2);
model.idAttribute = '_id';
model.urlRoot = 'models';
model.url(); // returns "models/1"
I would do that this way.
var someModel = Backbone.Model.extend({
initialize: function(attr,params) {
this.id = attr.id;
this.url= "somerooturl/"+params.id;
}
});
var model = new someModel(attr,params);
model.set ...

backbone model initialize function

I don't understand why backbone is adding the argument of the initialize function to the model.
var RippleId = Backbone.Model.extend({
initialize: function(toresolve) {
this.url= config.rippleaccount.id.urlModel+toresolve;
}
});
toresolve is a character chain (something like that "rkjnfezmeznfzln..." and the result when i create a model is that
0: "r"1: "h"2: "s"3: "b"4: "z"5: "U"6: "t"7: "o"8: "N"9: "Z"10: "C"11: "t"12: "2"13: "7"14: "Y"15: "L"16: "Q"17: "c"18: "k"19: "K"20: "V"21: "Q"22: "H"23: "n"24: "E"25: "g"26: "f"27: "Y"28: "y"29: "g"30: "J"31: "Q"32: "b"33: "5"account_data: Objectid: "keyfact4"ledger_current_index: 10414762validated: false
Basically my object is ok. But at the beginning each character of my chain has been added as an argument. It seems backbone is adding it considering the chain as an array.
I don't understand why backbone is doing that, am I doing something wrong ?
thanks a lot in advance
So, toresolve is a string? Backbone.Model expects the first parameter to be the attributes of the model, and second parameter to be the options. Both should be objects.
I think what you want is:
var options = {
toresolve: 'rkjnfezmeznfzln'
};
new RippleId({}, options);
and then in your initialize function:
initialize: function (attr, options) {
this.url = config.rippleaccount.id.urlModel + options.toresolve;
}

Subclassing Backbone.View

I have several views that have common code I'd like to abstract into a custom Backbone.View class. Is there any best practices for doing this?
is a good pattern to do something like this? :
// Base Grid view
var GridView = Backbone.View.extend({
initialize : function(){
//common view init code ..
//do the plug in overrides
if (options.addHandler)
this.addHandler = options.addHandler;
if (options.events)
//?? extend default events or override?
this.events = $.extend(this.events, options.events);
},
addHandler : function() {
//defaulthandler this code can be overridden
});
});
// in another object create some views from the GridView base
....
var overrides = { events:"xxx yyy", el: ulElement addHandler: myAddFunction }
var UserList = GridView.extend(overrides)
var userList = new UserList(users, options);
....
var coursesOverrides : {addHandler: ...}
var coursesOptions: {el: courseElement, ...}
var CourseList = GridView.extend(coursesOverrides)
var courseList= new CourseList (courses, coursesOptions)
// along the same lines maybe there's an abstraction for toolbar views
var ClassToolbarView = ToolbarBase.extend(toolOverrides)
var classtoolbar = new ClassToolbarView(actions, toolbaropts)
Any pointers to good examples of extending a View for refactoring common view code is appreciated.
First, I don't see the options being passed in your initializer(), so that's a bug.
Secondly, the .extend() method is inherited:
var GridView = Backbone.View.extend({ ... })
var GridViewWithNewFunctionsAndEvents = GridView.extend({ ... })
And you can replace or extend GridView's functionality, and call new GridViewWithNewFunctionsAndEvents() and get the extra functionality in a new object you need, just like you extend the Backbone stock View class.
If you need to extend the initializer, you can do this to call the initializer on the superclass:
var GridViewWithNewFunctionsAndEvents = GridView.extend({
initializer: function(options) {
GridView.prototype.initializer.call(this, options);
/* Your stuff goes here */
}
});

Backbone.js Collection model value not used

Backbone is not using the model specified for the collection. I must be missing something.
App.Models.Folder = Backbone.Model.extend({
initialize: function() {
_.extend(this, Backbone.Events);
this.url = "/folders";
this.items = new App.Collections.FolderItems();
this.items.url = '/folders/' + this.id + '/items';
},
get_item: function(id) {
return this.items.get(id);
}
});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
App.Models.FolderItem = Backbone.Model.extend({
initialize: function() {
console.log("FOLDER ITEM INIT");
}
});
var folder = new App.Models.Folder({id:id})
folder.fetch();
// later on change event in a view
folder.items.fetch();
The folder is loaded, the items are then loaded, but they are not FolderItem objects and FOLDER ITEM INIT is never called. They are basic Model objects.
What did I miss? Should I do this differently?
EDIT:
Not sure why this works vs the documentation, but the following works. Backbone 5.3
App.Collections.FolderItems = Backbone.Collection.extend({
model: function(attributes) {
return new App.Models.FolderItem(attributes);
}
});
the problem is order of declaration for your model vs collection. basically, you need to define the model first.
App.Models.FolderItem = Backbone.Model.extend({...});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
the reason is that backbone objects are defined with object literal syntax, which means they are evaluated immediately upon definition.
this code is functionality the same, but illustrates the object literal nature:
var folderItemDef = { ... };
var folderItemsDef = {
model: App.Models.FolderItem
}
App.Models.FolderItem = Backbone.Model.extend(folderItemDef);
App.Collections.FolderItems = Backbone.Collection.extend(folderItemsDef);
you can see in this example that folderItemDef and folderItems Def are both object literals, which have their key: value pairs evaluated immediately upon definition of the literal.
in your original code, you defined the collection first. this means App.Models.FolderItem is undefined when the collection is defined. so you are essentially doing this:
App.Collection.extend({
model: undefined
});
By moving the model definition above the collection definition, though, the collection will be able to find the model and it will be associated correctly.
FWIW: the reason your function version of setting the collection's model works, is that the function is not evaluated until the app is executed and a model is loaded into the collection. at that point, the javascript interpreter has already found the model's definition and it loads it correctly.

Resources