How does Chaplin.js handle passing a collection to a view? - backbone.js

I can create a simple model like so:
define(["models/base/model"], function(Model) {
"use strict";
var IssueModel = Model.extend({
defaults:{
lastName: "Bob",
firstName: "Doe"
}
});
return IssueModel;
});
And then from my controller I can do this:
this.model = new IssueModel();
And then when I create my view I can pass it my model like so:
this.view = new IssueView({model: this.model});
Finally, in my template I can successfully get properties on the model by doing this:
Hi {{firstName}} {{lastName}}
But when I define a collection using IssueModel and I try to pass the collection to my view (and not the model like I showed previously) I can't figure out how to reference the models in my Handlebars template:
var self = this;
this.collection = new IssueCollection();
this.collection.fetch({
success: function(collection) {
self.view = new IssueView({collection: collection});
console.log(self.collection);
},
error: function(collection, error) {
// The collection could not be retrieved.
}
});
I know fetch properly retrieves 5 models from my Parse.com backend because this is what I get on the console:
My question is this. I know Chaplin.js uses getTemplateData, but when I pass a model I don't have to do anything special in order to reference the properties in my view. How would I reference, specifically iterate, over the collection I passed to my view in my Handlebars template?
{{#each [Model in the collection I passed to the view]}}
{{title}}
{{/each}}

Chaplin will render a collection using a CollectionView, it's basicly an extention of a normal view that listens for changes in your collection and adds/removes subviews accordingly.
this.view = new IssueCollectionView({collection: this.collection});
Also there is no need to wait for success call when using a collection view since it will automaticly render every child item when data is added.

Related

BackboneJS nested view not rendering

I have backbonejs form inside the lightbox with html <select> as child view.
For the select <option> data I am loading from server and I have separate model and collection for this select
<select name="organization" id="organization" class="main__form--select main__form--select--js">
<option value="no">Organizations not found, Please add one</option>
</select>
Model for option (optionModel)
return Backbone.Model.extend({
defaults : {
"name" : 'KFC',
"email" : 'info#kfc.com',
"image" : '/kfc.jpg',
"descrption" : 'Lorem Ipsum'
}
});
This is view for the model
return Backbone.View.extend({
model : optionModel,
template : _.template(template),
render : function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
This is options collection
return Backbone.Collection.extend({
model : optionModel,
getQuery : function(){
//all my query codes
}
});
Options collections view render() code
this.collection.each(function (optionModel) {
// inserting each options view to an array
_this._optionsViewArray.push(
new OptionView({
model: optionModel
}).render().el
);
});
//inserting array to collection view container
_this.$el.html(_this._optionsViewArray);
return this;
My Parent view (form view) i create after render function with underscore _.wrap and inside that function
//<select>
var _selector = this.$el.find('#organization');
optionsView = new OptionsCollectionView({
collection : optionsCollection,
$el: _selector
});
optionsCollection.getQuery();
optionsView.render();
But Form is loading perfectly and Options collection querying successfully but nothing changes on <select> html, It's not updating.
Assuming that getQuery() does an asynchronous query (either using jQuery or Collection.fetch()), the problem (or at least one problem) is that you're calling optionsView.render() to soon, before the query results have returned.
You could throw in an arbitrary 5 second timeout to verify that this is the problem like this: setTimeout(function() { optionsView.render(); }, 5 * 1000);, but the correct way to do it is to call render from a jQuery done handler function ($.ajax({done: function() { ... }})) or (better) a Backbone sync event handler (this will only get called if you did the query via a Backbone collection fetch()): optionsCollection.on('sync', function() { ... });
You example is incomplete. However, given that fetches are generally asynchronous in Backbone and jQuery, this is the most obvious issue is that optionsView.render will run before optionsCollection has had a chance to fully load the collection. You can remedy this by listening to the collection update event:
optionsView = new OptionsCollectionView({
collection : optionsCollection,
$el: _selector
});
optionsView.listenTo(optionsCollection, 'update', optionsView.render);
optionsCollection.getQuery();
You could listen to the sync event instead, but listening to update means that local changes to the collection will also trigger updating your view.
There is also an issue with your render function, as you are passing an array of html elements into the jQuery html function.
render: function() {
var $el = this.$el;
$el.empty();
this.collection.each(function (optionModel) {
var view = new OptionView({ model: optionModel });
$el.append(view.render().$el);
});
return this;
}
However you may be better off using a standard collection view pattern, or something like Backbone.CollectionView - http://rotundasoftware.github.io/backbone.collectionView/

Backbone Nested Views & Nested Models

I am a newbie to backbone and trying to start using in our projects.
My requirement is I have something like this
var TextFields = Backbone.Model.extend({});
var TextFieldsCollection = Backbone.Collection.extend({});
var Module = Backbone.Model.extend({
defaults: {
fields: new TextFieldsCollection()
});
var ModuleCollection = Backbone.Collection.extend({
model: CTModule
});
Now I have views defined for TextFields & Modules.
If I change a value in TextFields a event gets fired for the model and I am changing the value in the model but the collection is not getting updated.
I tried to trigger backbone events in the child model but at the collection view I am not able to map to the correct model that triggered the change event.
Any comments are helpful. I am not in a position to use more libraries. Can this be done in Backbone?
I do not know what is full code, but if you do it in a fashion that Backbone apps are usually written everything should work.
Quick tutorial:
var TestModel = Backbone.Model.extend({});
var TestModelCollection = Backbone.Collection.extend({ model: TestModel });
// now if you have a view for collection
var TestModelCollectionView = Backbone.View.extend({
initialize: function(opts) {
this.collection = opts.collection;
this.collection.on('change', this.render, this) // so - rerender the whole view
}
});
//execution:
var modelData = { foo: 'bar' };
var collection = new TestModelCollection([modelData]);
var view = new TestModelCollectionView({collection: collection});
view.render();
collection.first().set('your_attr', 'new_value'); // will cause rerender of view
//////
One more thing - if you have collection, which is kept by the model (in your case Modules model, which have collection as one of its attributes), and your view keeps that model, you will have to either bind directly from view to collection via model (this.model.get('fields').on( .... )), or rethrow collection event in your model
I hope I helped at least a bit.
//////
There might be some syntax errors in my code - didn't run it on js fiddle.

Add custom property to every item of a collection

I'm trying to add a custom property to every item of a collection, but it doesn't show up in the template.
I have a lot of quotations which have a client_id. Now I want to fetch the client by the client_id and add it to the collection entry. In general, it works when inspecting the populated object with console.log, but it doesn't show up in the template.
That's how I tried it:
sprocket.QuotationsView = Backbone.View.extend({
id: 'content-inner',
initialize: function(options) {
// instantiate Collection
this.collection = new Quotations();
// compile Handlebars template
this.tpl = Handlebars.compile(this.template);
},
render: function() {
var self = this;
var obj = this.el;
// get quotations and set data to handlebars template
$.when(this.collection.fetch()).then(function(quotations) {
$.each(quotations, function(i, quotation) {
var loadContact = new Contact({id: quotation.contact_id}).fetch();
$.when(loadContact).then(function(contact) {
quotations[i]['contact'] = contact;
});
});
$(obj).html(self.tpl(quotations));
// if response is empty (no quotations in database), set empty template
}, function() {
$(obj).html(self.tpl);
});
return this;
}
});
My template looks like this:
<div>
{{#if .}}
{{#each .}}
{{number}} <!-- this works -->
{{contact}} <!-- this doesn't work -->
{{contact.name}} <!-- this doesn't work too -->
{{/each}}
{{/if}}
</div>
That is because the callback that actually changes the data inside the Quotation.attribute.contact (ie. your quotations[i]['contact'] = contact; line) is being executed after fetching the Contact which happens to be after the template is being rendered.
$.each(quotations, function(i, quotation) {
var loadContact = new Contact({id: quotation.contact_id}).fetch();
// This is the callback that is being executed after the backend responds
$.when(loadContact).then(function(contact) {
quotations[i]['contact'] = contact;
});
});
// This is the template rendering which is before the server has time to respond
$(obj).html(self.tpl(quotations));
Render instead the template after all Contacts are being fetched and added to Quotations.
A quick way to solve this:
Make the loop which load all the Contacts inside a function that contains a callback.
Call the callback after all Contacts have loaded.
The callback should render the template.
This is a personal opinion and in no way an answer to this question: I don't like the data logic with the backend and the view rendering and logic in the same class. I use Backbone.Marionette to split the View and the Controller in two different entities loosely coupled by events. Only the Controller is aware of the View and only the View is aware of the DOM.

Add model to collection after fetching it

Im having trouble figuring out how to populate a model's attributes from the server and then add the populated model to a collection and have that collection rendered by a view. Here's the code I have:
var movieDetails = new cinephile.Models.MovieDetailsModel({ id: movie.get('id') });
this.collection.add(movieDetails);
Inside of the MovieDetailsModel:
cinephile.Models.MovieDetailsModel = Backbone.Model.extend({
url: function()
{
return '/cinephile/api/index.php?action=getMovieDetails&movieId=' + this.id;
},
initialize: function()
{
this.fetch();
}
});
And this.collection is just a collection with the model set to be a cinephile.Models.MovieDetailsModel
I am listening for items to be added to the collection and when they are, the following is executed:
displayMovie: function(movie)
{
var view = new cinephile.Views.MovieView({
model: movie,
className: 'movie clearfix',
template: JST['app/scripts/templates/MovieView.ejs'],
});
this.$("#my-movies").append(view.el);
},
MovieView looks like this:
cinephile.Views.MovieView = Backbone.View.extend({
initialize: function(options)
{
this.template = options.template;
this.render();
},
render : function()
{
this.$el.html(this.template(this.model.attributes));
return this;
},
});
The problem I have is that the template I'm using is trying to access an attribute of the model that is undefined. Im pretty sure it's undefined because the MoveDetailsModel hasn't finished fetching before the model is added to the collection and subsequently rendered to the view.
How can I solve this issue? I'd like to be able to create a MovieDetailsModel that takes in an id, use that id to get the movie details from the server and then add the populated model to a collection and then render that collection to the screen.
Any help is appreciated.
Backbone fetch returns a jqXHR object, which is a Deferred objects Promise.
When fetch is called, the attributes are not populated yet. Promise objects have a don
ejqXHR function, where a callback can be passed to be executed once the request is done.
I would recommend moving the fetch into another method not the constructor, because there You can return the jqXHR object and access its done function.
Here is an example:
var movieDetails = new cinephile.Models.MovieDetailsModel({ id: movie.get('id') });
var promise = movieDetails.fetch();
promise.done(function() {
var view = new cinephile.Views.MovieView({model: movieDetails});
view.render();
});

Backbone.js - fetch data and display

I'm just beginning out on Backbone.js. Here's my code.
$(function(){
//Backbone Model
var Cat = Backbone.Model.extend({});
// create a collection
var CatCollection = Backbone.Collection.extend({
model: Cat,
url: 'http://localhost/cats/index.php/cats/index'
});
var catCollection = new CatCollection();
catCollection.fetch();
// Backbone view
var CatView = Backbone.View.extend({
el: $("#contents"),
initialize: function() {
this.render();
},
render: function() {
this.el.html(catCollection);
}
});
var catView = new CatView();
});
What I am doing is.
Create a backbone model
Create a collection using the model I created.
Fetch data from MySQL database - this returns a JSON data object.
Display the fetched data in the div "#contents".
On google Chrome, I can see that the "fetch()" method works, because I can see my JSON object returned as
[{"id":"1","name":"stella","age":"5"},{"id":"2","name":"Max","age":"2"}]
But if I do "alert(catCollection)" after the fetch, it displays "[object] [object]".
What is the best way of displaying this?
You should use the templates in JST array.
$(this.el).html($(JST["comments/item"](model.toJSON())));
"comments\item" is the template path and name
If you are using Rails, just use the Jammit and write templates with ERB (default) or Jade
The backbone.js documentation give an example like so:
alert(JSON.stringify(catCollection));
http://backbonejs.org/#Collection-toJSON
Use console.log.

Resources