Backbone, RequireJS - Collection JSON - backbone.js

I want to load JSON data from file and load into a Collection.
Collection:
define(['backbone', 'model'], function(Backbone, Model) {
return Backbone.Collection.extend({
model: Model,
url: 'data/data.json'
});
});
Edit:
The problem now seem to be the data is collected after the render function is executed the first time. So if I comment out the render function and make the template update from the success function, it works, but this is of course not the proper way of doing it. Any better ideas?

this.template(this.coll.toJSON())
will probably solve your problem. Never use the collection itself when forwarding data to template.
If you're using handlebars or mustache, you should event use :
this.template({col : this.coll.toJSON()})
it's usually good practice to not use an array as root element for context.

Related

Backbone Multiple Collection Fetch and Rendering

I am developing a Backbone application.
I have a view named as "Dashboard". In this dashboard View, I will be fetching two collections of data; one for the List of recent articles from the articles table and the other for the list of Authors from the authors table.
Please note that I have two different tables in mySQL Database, I use SlimPHP Api to make queries and retrieve data.
I am also using Handlebars templating Javascript library to compile the HTML.
Question:
My Question is that what is the best approach to make the two queries and then send both collections data to the Handlebars compile which will create the HTML (after looping through collection models) and eventually be taken into render function.
Here is what I have tried so far (Dashboard View Javascript File):
http://jsfiddle.net/n6c3q16r/2/
I tried to fetch two models data and pass it into the render function on line # 17 on jsFiddle:
this.$el.html(this.template(this.collection, this.topAuthorsCollection));
But this does not work.
Try this:
initialize: function() {
this.listenTo(this.topAuthorsCollection, 'reset', this.render);
this.topAuthors = new topAuthorsModel();
this.topAuthorsCollection = new topAuthorsCollection({model: this.topAuthors});
this.collection.fetch({
reset: true,
success: function() {
this.topAuthorsCollection.fetch({reset: true})
}
});
}
You can use promises. My personal favourite in such a scenario is jQuery.when
$.when(collection1.fetch(),collection2.fetch()).done(function(){
//Do stuff here
});
API - http://api.jquery.com/jquery.when/
Updated fiddle - http://jsfiddle.net/n6c3q16r/4/

How do I iterate a Backbone Firebase Collection?

I currently started using Firebase as my backend solution for persistance.
I found it easy to create new objects and persist it to Firebase with Backfire with a simple
collection.add(obj)
The issue comes when I try to get a Firebase collection from the server.
For example when i try
console.log(collection);
I get this output:
=> {length: 0, models: Array[0], _byId: Object, _events: Object, constructor: function…}
Which result in an empty models array
console.log(collection.models);
=> []
After some searching, I figured out that Backbone Collections aren't yet loaded at the time I try to log it to the console (see this previous question).
I also tried using
Backbone.Collection.extend({
model: Todo,
firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
To explicitly call fetch from the server and use success callback with no success either.
My question is: How can I get the Firebase Collection and populate the DOM from it?
When you call Backbone.Firebase.Collection.add, it does not get added to the collection synchronously. Rather, it sends the request to Firebase and then waits for the return event. See the code here
Thus, if you immediately try to read the collection, you will see zero elements. However, if you try something like this:
collection.once('add', function() { console.log(collection.length); });
You'll see the element you have added.
Remember that we're dealing with real-time data here, so when you want to populate the DOM, you shouldn't think in terms of a single transaction, but instead rely on events and take everything as you get it (in real time).
So to populate the DOM, do something like this in your view:
Backbone.View.extend({
render: function() {
this.listenTo(this.collection, 'add', this.rowAdded);
},
rowAdded: function(m) {
/* use `m` here to create your new DOM element */
}
});
Additionally, you'll probably want to check out a nice binding library like ModelBinder to help you deal with the constantly changing DOM, so you don't have to re-invent any wheels.
It seems you have to use a Backbone.Firebase.Collection and not a Backbone.Collection which will tell you that your calls to fetch or sync are silently ignored.
Also, Backbone.Firebase's got a read and a readall methods that should get you started. It seems Backbone.Firebase.Collection doesn't inherit this method, but I'm not sure though.
Edit:
As Kato stated in his comment, it seems you don't have to do anything. Just use Backbone.Backfire.Collection and Backbone.Backfire.Model.

How do I design MarionetteJS ItemView to properly show loading message?

First off - I am a MarionetteJS noob.
I am having trouble making an ItemView display a loading message or throbber while it is being fetched. This is especially problematic when this ItemView is being displayed from a deep link in Backbone's history (i.e. the ItemView is the first page of the app being displayed since the user linked directly to it). I want to indicate that the page is loading (fetching), preferably with a simple view, and then show the real templated view with the fetched model.
I have seen other answers on SO like Marionette.async, (which has been deprecated) and changing the template during ItemView.initalize().
Anybody (Derrick?) got any suggestions or best practices here?
UPDATE:
I am getting the model from the collection using collection.get(id), not using model.fetch() directly.
After thinking about this, the real question is where should this be implemented:
I could change my controller to see if the model exists in the collection (and if the collection is loaded) and decide which view to show accordingly. this seems like a lot of boilerplate everywhere since this could happen with any ItemView and any controller.
I could change my ItemView initialize to test for existence of the model (and a loaded collection), but same comment here: every ItemView could have this problem.
UPDATE 2:
This is what I ended up with, in case anybody else want this solution:
app.ModelLayout = Backbone.Marionette.Layout.extend({
constructor: function(){
var args = Array.prototype.slice.apply(arguments);
Backbone.Marionette.Layout.prototype.constructor.apply(this, args);
// we want to know when the collection is loaded or changed significantly
this.listenTo(this.collection, "reset sync", this.resetHandler);
},
resetHandler: function () {
// whenever the collection is reset/sync'ed, we try to render the selected model
if (this.collection.length) {
// when there is no model, check to see if the collection is loaded and then try to get the
// specified id to render into this view
this.model = this.collection.get(this.id);
}
this.render();
},
getTemplate: function(){
// getTemplate will be called during render() processing.
// return a template based on state of collection, and state of model
if (this.model){
// normal case: we have a valid model, return the normal template
return this.template;
} else if (this.collection && this.collection.isSyncing) {
// collection is still syncing, tell the user that it is Loading
return this.loadingView;
} else {
// we're not syncing and we don't have a model, therefore, not found
return this.emptyView;
}
}
});
And here is how to use it:
// display a single model on a page
app.Access.Layout.CardLayout = app.ModelLayout.extend({
regions: {
detailsRegion:"#detailsRegion",
eventsRegion:"#eventsRegion"
},
template:"CardLayout", // this is the normal template with a loaded model
loadingView:"LoadingView", // this is a template to show while loading the collection
emptyView:"PageNotFoundView", // this is a template to show when the model is not found
onRender : function() {
this.detailsRegion.show( blah );
this.eventsRegion.show( blah );
}
});
thanks!
For the ItemView
I think you can add a spinner in your initialize function, I really like spin.js http://fgnass.github.io/spin.js/ because its pretty easy and simple to use, and you can hide the spinner in the onRender function of the Itemview
For The CollectionView
in the CollectionView you could handle it like this....
Take a look at the solution that Derick posted..
https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
I'd suggest using jQuery deferreds:
Start fetching your data, and store the return value (which is a jQuery promise)
Instanciate your content view
Show your loading view
When the promise is done, show the view containing the content
I've talked about implementing this technique on my blog:
http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/
http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/
The issue with the solution linked by Rayweb_on, is that your loading view will be displayed any time your collection is empty (i.e. not just when it's being fetched). Besides, the ItemView doesn't have an emptyView attribute, so it won't be applicable to your case anyway.
Update:
Based on your updated question, you should still be able to apply the concept by dynamically specifying which template to use: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#change-which-template-is-rendered-for-a-view
Then, when/if the data has been fetched successfully, trigger a rerender in the view.

When to use URL attribute for both Collection and Model?

I've been following some Backbone.js tutorials and am a bit confused about when to use 'url' inside Model when there is also a Collection which contains the model. For example I see a lot of code that looks like this, in order to synchronize with the server:
var PostModel = Backbone.Model.extend({});
var PostsModel = Bacbone.Collection.extend({
model: PostModel,
url: "/posts"
});
However I also see some where the model also has 'url' property, like so:
var PostModel = Backbone.Model.extend({
url: "/posts"
});
var PostsModel = Bacbone.Collection.extend({
model: PostModel,
url: "/posts"
});
I think I understand the first method, where I interact with the models only through the collections to which they belong, but I am not sure when you would want to have urls for both a model and its collection.
Sometimes you need a model that doesn't belong to any collection.
For example a login/sign in.
var LoginModel = Backbone.Model.extend({
url: "/login"
});
This way you can interact with login view and model just like you would with any other Backbone model, including save() (which posts the login form to the server) and validation.
There is however no login collection, so it doesn't make sense to model that.
Maybe the code you're seeing is something in between. Models are accessed via collection in some places of the application and without collection in others.
If it's a good design is another story, but there's no technical reason to not do it.
edit
As you noticed, using urlRoot is another option, since by default the url attribute is a method that uses urlRoot to construct the relative url.
It can be, however, given a string value to specify the model url directly.

How to access a calculated field of a backbone model from handlebars template?

I would like to access the calculated fields I have implemented in the model (backbone.js) from the template.
Do I need always to define a helper to do it?
I think the problem has to do with the way I pass the model to the template.
If I pass this.model.toJSON() I have access to the properties but not to the functions I have defined in it.
If I pass this.model directly I can access the function but not the properties of the backbone model.
Always pass this.model.toJSON() to your templates.
What you need to do to get your calculated values, is override your toJSON method on your model.
MyModel = Backbone.Model.extend({
myValue: function(){
return "this is a calculated value";
},
toJSON: function(){
// get the standard json for the object
var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
// get the calculated value
json.myValue = this.myValue();
// send it all back
return json;
}
})
And now you have access to myValue from the the JSON that is returned by toJSON, which means you have access to it in the view.
The other option, as you mentioned, is to build helper methods and register them with Handlebars. Unless you have some functionality that changes based on how the template is being rendered, and/or what data is being passed to the template, I wouldn't bother with that.
Here is another possibility: (from the model initialize)
initialize: function() {
this.on("change", function () {
this.set({ calculatedColumn: this.get("otherColumn") }, { silent: true });
});
},
Computed properties in Backbone
I have had the same issue. #DerickBailey is right, of course, that overriding toJSON does the job. But it also leaks into the communication with the server (see muu's comment on his answer).
So eventually, I have built a Backbone plugin to specifically handle data export to templates, and do so with a minimum of fuss: Backbone.Marionette.Export. It also deals with nested structures, takes care of circular references etc. See the docs.
Here's how it works. Include the plugin file into your project and declare
MyModel = Backbone.Model.extend({
foo: function () {
return "I am a calculated value";
},
exportable: "foo" // <-- this is the one line you have to add
});
If you are a Marionette user, you are already done at this point. foo shows up in your templates as if it were a model attribute.
In plain Backbone views, just call myModel.export() or myCollection.export() instead of their toJSON counterparts when you render.
For methods taking arguments, there is an onExport handler. Examples, again, are in the docs.
The best way to do it is to add this to your model:
function initialize() {
this.set("calculatedColumn", function () { return this.otherColumn; });
}
A backbone model normally stores the actual data values internally in "model.attributes". That is why when you pass your model directly to the template, it only has functions added directly to model and not any data. And if you use model.toJSON() it is normally implemented in backbone as _.clone(model.attributes) (see backbone.js). So you have the data and not the functions added directly to the model. That is why the above works - you set the function on model.attributes, not on the model object itself. Do not reference model.attributes directly, use model.get("calculatedColumn") and model.set("calculatedColumn", ...).
So model.get("calculatedColumn") returns a function. If you go {{calculatedColumn}} in handlebars (assuming you're using handlebars), it shows the value returned by the function. But calculatedColumn will not be sent to the server because backbone does a JSON.stringify to model.toJSON in sync (in backbone.js) and JSON.stringify ignores functions. If you want JSON.stringify to not ignore the function (so the function is turned into a data value whenever toJSON is run on the model - during view rendering and model sync-ing), override model.toJSON just as #Derick Bailey described.
Also, you can derive your own BaseModel from Backbone.Model and override .toJSON and derive all your models from BaseModel if you need to. Then you would need a generic version of .toJSON that could be applied to any model.

Resources