backbone views not working with fetched json - javascript

I'm having issues syncing JSON data received from the server with my views after a fetch.
I do not have a collection of "mainmodel", because I'm only working with one "mainmodel" at a time but numerous "mymodel", anyhow, the structure follows:
var mymodel = Backbone.Model.extend({
defaults: {k1:"",
k2:"",
k3:""}
});
var collection = Backbone.Collection.extend({model:mymodel,});
var mainmodel = Backbone.Model.extend({
defaults: {v1:"",
v2:"",
v3:"",
v4:new collection()
});
I create the nested views for "mymodel" from a render function in a parent view. This works..., only when I'm working with a new model.
// My ParentView render function
render: function() {
for (var i = 0; i < this.model.v4.length;i++) {
var view = new MyModelView({model:this.model.v4.at(i)});
this.$el.append($(view.render().el));
}
this.$el.append(this.template(this.model.toJSON()));
return this;
},
// The MyModelView render function below
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
Now, the above works if I open my application and create models from there. However, If I open my app and supply an id, I make a fetch to the server, retrieve the data, and create a new ParentView I end up getting an error that says "this.model.v4.at not a function". Ugh.
So now, if I change the FIRST render function to be, changing the at(i) to [i]
var view = new MyModelView({model:this.model.v4[i]});
And change the second render function, removing toJSON, to be:
this.$el.html(this.template(this.model));
It renders. But I still can't move around views without errors. No surprise.
I have used console.log(JSON.stringify(this.model)); as they arrive into the parentView and MyModelView. The JSON returned looks like this, whether fetched or created.
{"v1":"val1",
"v2":"val2,
"v3":"val3",
"v4":[{"k1":"key1","k2":"key2","k3","key"}, { ... }, { ... }]
}
The JSON data structures appear to be identical. I thought the JSON format was incorrect, so I tried using JSON.parse before handing the model to the view, but that didn't work. Maybe I'm way off, but I originally thought I had a JSON formatting issue, but now I don't know. The server is returning content as 'application/json'.
Edit: The JSON values for v1,v2,v3 render correctly.
Any ideas?

You have two problems: one you know about and one you don't.
The problem you know about is that your mainmodel won't automatically convert your v4 JSON to a collection so you end up with an array where you're expecting a collection. You can fix this by adding a parse to your mainmodel:
parse: function(response) {
if(response.v4)
response.v4 = new collection(response.v4);
return response;
}
The problem you don't know about is that your defaults in mainmodel has a hidden reference sharing problem:
var mainmodel = Backbone.Model.extend({
defaults: {
//...
v4: new collection()
}
});
Anything you define in the Backbone.Model.extend object ends up on your model's prototype so the entire defaults object is shared by all instances of your model. Also, Backbone will do a shallow copy of defaults into your new models. So if you m1 = new mainmodel() and m2 = new mainmodel(), then m1 and m2 will have exactly the same v4 attribute. You can solve this by using a function for defaults:
var mainmodel = Backbone.Model.extend({
defaults: function() {
return {
v1: '',
v2: '',
v3: '',
v4: new collection()
};
}
});

Related

Backbone architecture and view management

I am struggling with when to destroy backbone views. I know I need to destroy the view somewhere, but I am not sure where.
I have the following code in router.js
routes: {
"names/search": "nameSearch",
"companies/search": "companySearch"
},
initialize: function(){
Backbone.history.start();
this.navigate("#/", true);
}
nameSearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var obj = {};
obj.Status = [utils.xlate("On Assignment"), utils.xlate("Candidate")];
var view = new RecordSearchView({ model: obj, el: $(".content") }, { "modelName": "Candidate" });
view.delegateEvents();
});
},
companySearch: function () {
require(["app/views/RecordSearch"], function (RecordSearchView) {
var view = new RecordSearchView({ model: {}, el: $(".content") }, { "modelName": "Company" });
view.delegateEvents();
});
}
And then in RecordSearchView.js I have the following function that is called when a user clicks the search button
doSearch: function () {
require(["app/utils/SearchHelper", "app/models/" + modelName, "app/views/SearchResults"], function (SearchHelper, Model, SearchResultsView) {
var obj = $("#searchForm").serializeArray();
var params = SearchHelper.getQuery(obj);
params["page"] = 1;
params["resultsPerPage"] = 25;
var collection = new Model[modelName + "Collection"]({}, { searchParams: params });
params["Fields"] = collection.getSearchFields();
collection.getPage(params["page"], function (data) {
require(["app/views/SearchResults"], function (SearchResultsView) {
App.Router.navigate(modelName + "/search/results");
var view = new SearchResultsView({ collection: data, el: $(".content") });
view.delegateEvents();
});
});
return false;
});
And SearchResults.js
return BaseView.extend({
init: function () {
this.render();
},
render: function () {
var data = this.collection.convertToSearchResults();
this.$el.html(template(data));
return this;
}
});
The problem is the second time I perform any search (calling the doSearch function from RecordSearch.js). As soon as I perform the second search, the data shown is that belonging to the previous search I performed. (For example I do a name search and it works, then do a company search but the screen shows company search results but then is quickly replaced with name search results).
My questions are
I suspect I need to call some cleanup code on the view before it is re-used. Where is the proper place within a backbone application to run this.
Is there anything wrong with the way I load SearchResults view from within RecordSearch view? SearchResults does not have a path on my router, but it is basically a form post, so I assume it shouldn't?
Any help is appreciated.
This problem is quite common and is known as Zombie Views. Derick Bailey explains this issue very well here: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
However unfortunately you can't simply solve it without changing the way you are loading your views.
Because you are loading them inside RequireJS modules that will keep it in the local var scope, you are losing the reference to the views once the route has been fully processed.
In order to solve this problem, you would need to keep the reference of the current view somewhere, and then properly dispose it before calling another view, something like this:
showView: function(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.currentView.render();
$('#content').html(this.currentView.el);
}
More about this solution here: http://tiagorg.com/talk-backbone-tricks-or-treats-html5devconf/#/6
I personally suggest you adopting a solution that will take care of this for you, like Marionette.js
It will handle this and quite many other issues, by providing the missing gaps of every Backbone-based architecture.

backbone js json collection result

So I'm still a bit of a beginner when it comes to Backbone as I was trying to play around with it today to produce some results. The issue I am coming across is being able to see the results from the json collection object. Was hoping someone could help me out a bit and point me in the right direction.
So far my set up is like so:
var Game = Backbone.Model.extend({});
var GameList = Backbone.Collection.extend({
model: Game,
url: 'link to server json object',
parse: function(response) {
return response;
}
});
var GameListView = Backbone.View.extend({
el: $('#games-list'),
initialize: function() {
var self = this;
this.collection = new GameList();
this.collection.fetch().done(function() {
self.render();
});
},
render: function() {
this.collection.each(function(game) {
console.log('Game.', game);
});
}
});
var testApp = new GameListView();
This produces in the console:
Game.
r {cid: "c2", attributes: Object, collection: r, _changing: false, _previousAttributes: Object…}
I'm not sure where I am going wrong, would like to first see the json object, then be able to cycle through each item.
You are looking for toJSON() method, this method converts the model from Backbone collection or model to JSON, for example:
var json = game.toJSON();
This method is used very often in Backbone development,usually for passing the model to the html template

Retrieve Backbone Collection from Model and keep it as a Collection

I'm currently fooling around with backbone.js and came across some wierd behaviour trying to create some relationships between models and collections.
So here is my Model/Collection
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
initialize: function() {
this.elements = new Elements(this.get('elements'));
}
});
When I retrieve the elements via application.get('elements') I get a 'false' asking if this new Object is an instanceof Backbone.Collection.
var gotElements = application.get('elements');
var isCollection = gotElements instanceof Backbone.Collection;
So am I doing something wrong, or do I have to create a new Collection-Instance and fill it up with the Collection I receive from the application?
In your initialize function doing this.elements sets a property called 'elements' directly on your model (so model.elements will be your Backbone collection). The problem is when you try to retrieve this.elements by calling application.get('elements'), you will see it returns undefined (which is why it is not an instance of a Backbone collection).
In order for a model attribute to be retrievable using model.get it needs be set with model.set(attribute). If you examine the model in the console you will see the difference. model.set(myData) adds your data to the model's attributes hash. model.myData = myData adds a 'myData' property directly to the model.
To get this to work you can do the following:
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
elements: null
});
var application = new Application({
elements: new Elements()
});
var myElements = application.get('elements');
myElements should now be an empty Backbone Collection
Instead of putting your collection into another model, you should put it in the according view:
var ListView = new Backbone.View.extend({
initialize:function(){
this.Elements= new Elements();
// more stuff to go
},
render:function(){
_.forEach(this.Elements, function(){
//Do rendering stuff of indivdual elements and get HTML
});
//put HTML in the DOM
}
});
There is no need of a model containing a collection containing several models.
Edit: And instead of putting your app into a Model, you should put it into a view.

Backbone.js render collection in View

I have a Backbone Collection that I'm trying to render in the View. The JSON data seems correct, however I can't access the values from within the view.
Here's the basic collection:
define(['backbone', 'BaseModel'], function(Backbone, BaseModel) {
var BaseCollection = Backbone.Collection.extend({
model: BaseModel,
url: "/collection/get?json=true",
initialize: function() {}
});
return BaseCollection;
});
Here's the View:
define(['backbone', 'BaseCollection'], function(Backbone, BaseCollection) {
var BaseView = Backbone.View.extend({
el: $('#baseContainer'),
template: _.template($('#baseTemplate').html()),
initialize: function() {
_.bindAll(this);
this.collection = new BaseCollection();
this.collection.bind('all', this.render, this);
this.collection.fetch();
},
render: function() {
//This returns 3 objects which is correct based on the JSON data being returned from the server
console.log(this.collection.toJSON());
var html = this.template(this.collection.toJSON());
this.$el.html(html);
return this;
},
});
return BaseView;
});
I think I need to iterate through this.render for each model within the collection. But, I'm not sure, because it shouldn't 'render' until it completes all iterations.
Any suggestions would be great! Thank you!
You need to give your template access to the models via name. When you do this:
var html = this.template(this.collection.toJSON());
You end up passing an array to the template function, which normally expects a context object (name/value pairs). Try this:
var html = this.template({collection: this.collection});
Then in your template you can iterate through them using the collection.each iterator function or any of the underscore utility methods for iteration/filtering/map/etc. I also recommend NOT using toJSON when giving your template access to the collection as it makes your data dumber and harder to work with. toJSON is best left for when you are making HTTP requests.

Ember.js 'Objects' and 'ArrayController'

I'm getting my feet wet with a bit of Ember.js. I'm trying to create a super simple form that lets you submit a query.
App = Ember.Application.create();
App.QueryFormView = Ember.View.extend({
submitQuery: function () {
App.queries.pushObject({
firstName: this.get('name'),
message: this.get('message')
});
App.queries.save();
}
});
// Model
App.Query = Ember.Object.extend();
// Collection
App.queries = Ember.ArrayController.create({
content: [],
save: function () {
$.post('api/query', JSON.stringify(this.toArray()), function (data) {
// Queries saved
});
}
});
Each time the query form it submitted, I push an object to the queries ArrayController, and then run save.
However, I'm struggling to understand where the Ember.Object aka model comes into play. It's not being used at all here, and I'd like to know how to properly utilise it.
You don't have to use Ember.Object. If you never want to do any bindings, have calculated properties or observe any property changes, there's no need.
However if you do want to do any of those things you'd modify your code like this:
Document expected fields in the model.
// Model
App.Query = Ember.Object.extend({
firstName: null, // just to indicate what props you're expecting
lastName: null
});
Create your model object instead of anonymous object.
submitQuery: function () {
App.queries.pushObject(App.Query.create({ // .create() here
firstName: this.get('name'),
message: this.get('message')
});
App.queries.save();
}
And now for the big drawback. JSON.stringify() will serialize internal stuff you don't want. So each object sent over the wire must be simplified to the properties you want first. Help with this can be found here: Reflection on EmberJS objects? How to find a list of property keys without knowing the keys in advance
save: function () {
var obj = buildSimpleObject(this); // implements this somehow
$.post('api/query', JSON.stringify(obj.toArray()), function (data) {
// Queries saved
});
}

Categories