Add custom property to every item of a collection - backbone.js

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.

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/

Marionette layout view -- why is a template necessary

In spite of reading the marionette docs several times over, I am still not able to fully comprehend some aspects of it correctly.
I am creating a layout view 'AppLayout' as below:
var AppLayoutView = Marionette.LayoutView.extend({
regions: {
headerRegion: "#ecp_header",
bodyRegion: "#ecp_layout_region"
},
...
The html snippet for my app is having the two dom nodes for above defined regions:
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">
<div class="row" id="ecp_body">
...
in app.js, my calling code is like this..
ECPApp.on('start', function() {
require(['controller_cp', 'header_view'], function(ControllerCP, HeaderView) {
console.log("On start event executing...");
// create a event aggregator vent object and attach to app.
ECPApp.vent = new Backbone.Wreqr.EventAggregator();
var appLayoutView = new AppLayoutView();
appLayoutView.render();
//appLayoutView.showLayout();
//$('div.toolbar > ul > li:first > a').tab('show');
if (Backbone.history) Backbone.history.start();
});
This gives me error Cannot render the template since it is null or undefined.
I thought that the default render() behavior of layout always looks for a template, so I rolled out my own version of render, as below:
render: function() {
var $self = this;
/* if no session exists, show welcome page */
var promise = ECPApp.request('entities:session');
promise.done(function(data) {
if (data.result==0) {
console.log('Valid session exists. Showing home page...!');
$self.showHome();
} else {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
}
}).fail(function(status) {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
});
return $self;
},
showWelcome: function() {
var self = this;
require(['header_view', 'welcome_view'],
function(HeaderView, WelcomeView) {
var headerView = new HeaderView();
var welcomeView = new WelcomeView();
self.bodyRegion.show(welcomeView);
});
}
This time, I get another error saying, An "el" #ecp_layout_region must exist in DOM. However I am sure that the element is existing in the DOM, as I can see it by checking in the debug console window. Running $('#ecp_layout_region') shows a valid element.
Marionette layout view is pretty confusing. Going forward I need multiple nested views. I am stuck here.
How is your template located? Is your template wrapped by <script type = “text/template”> tag?
It may look like this:
Inside your html, in head section:
<script type = “text/template” id="yourLayout">
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">...</div>
</script>
And in Layout definition:
var AppLayoutView = Marionette.LayoutView.extend({
template: '#yourLayout'
...
});

Using Backbone model in jQuery function/plugin

On my page, I will have a slider which will have html inside, that will be populated using data fetched from the server.
I would like for the slider to be built and populated using jQuery, leaving Backbone to handle the viewing and events.
For this, I would need the data from the Backbone model, which leads to my question:
Is it fine to pass the model into jQuery OR would it be better to leave everything to the jQuery function and do a $.ajax fetch in the function instead.
Backbone
define(['backbone', 'models/model','slider'], function(Backbone, Model, Slider) {
var View = Backbone.View.extend({
render: function() {
$('#slider').Slider( this.model );
var template = _.template();
this.$el.html(template);
return this;
}
});
return View;
});
jQuery
(function($, window, document, undefined) {
"use strict";
$.fn.Slider = function(model) {
// do stuff with model
}
})(jQuery, window, document);
I'd implement the event handling and logic directly in the backbone view, where you already have the model and all other backbone objects you might possibly want to use. If you need the model data in the slider initialization you should not pass the model itself, but the json representation of the model data using
$('#slider').Slider( this.model.toJSON() );

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

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.

Can't get iscroll4 to play with backbone view

I am trying to use iScroll4 inside a backbone.js application. I have several dynamically loaded lists, and I want to initialize iScroll after the appropriate view has loaded.
I'm trying to call 'new iScroll' when the list view finishes loading, but cannot for the life of me figure out how to do this.
Has anyone gotten these two to work together? Is there an example out there of a backbone view initializing a scroller once its element has loaded?
you are correct, you have to load the view first,
or defenately refresh iscroll afterwards
in our applications, we usually use the render method to render the view
and have a postRender method that handles initialization of these extra plugins like iscroll
of course you need some manual work to get it done but this is the gist of it:
var myView = Backbone.View.extend({
// more functions go here, like initialize and stuff... but I left them out because only render & postRender are important for this topic
// lets say we have a render method like this:
render: function() {
var data = this.collection.toJSON();
this.$el.html(Handlebars.templates['spotlightCarousel.tmpl'](data));
return this;
},
// we added the postRender ourself:
postRender: function() {
var noOfSlides = this.collection.size();
$('#carouselscroller').width(noOfSlides * 320);
this.scroller = new IScroll('carouselwrapper', {
snap: true,
momentum: false,
hScrollbar: false
});
}
});
now the calling of these methods
we did this outside our view as we like some view manager to handle this
but it boils down to this
var col = new myCollection();
var view = new myView({ collection: col });
$('#wrapper').html(view.render().$el); // this chaining is only possible due to the render function returning the whole view again.
// here we always test if the view has a postRender function... if so, we call it
if (view.postRender) {
view.postRender();
}

Resources