templateHelpers in Marionette.CompositeView - javascript

I have no idea why this code is not working.
Reading the documentation,
the templateHelpers should be called.
My goal is to pass the this.collection.length to the template.
Any hints? thanks.
I am using Backbone.Marionette v0.9.5
return Marionette.CompositeView.extend({
className: 'user-board',
template: usersTemplate,
itemView: userItemView,
initialize: function () {
this.collection = new UseList();
this.collection.fetch();
},
appendHtml: function (collectionView, itemView) {
collectionView.$el.find('ul.users-list').append(itemView.el);
},
templateHelpers: function () {
console.log(this.collection.length);
},
serializeData: function () {
return {
weekly: this.options.weekly,
users_length: this.collection.length // here the length is zero
// after the fetch the length is > 0
// but in template remains 0
};
}
});
To fix my issue I have to make the following...
initialize: function () {
_.bindAll(this, 'render');
this.collection = new NewCollection();
this.collection.fetch({
success: this.render
});
}
Is there a better way to make it working?

Reading the Marionette Documentation serializeData method, which is the one using mixinTemplateHelpers, is only called on Item View.render method here and in your current code you do not render at all
UPDATE:
This way everytime the collection receives new data it will update your view the new length
initialize: function () {
_.bindAll(this, 'render');
this.collection = new NewCollection();
this.collection.fetch();
this.collection.bind('reset', this.render);
}

This code only declares the a view. Can you share the code the instantiates the view and displays it? templateHelpers will be called and the data passed to the template when the template is rendered. That is, you either need to show the view in a region which implicitly calls the render method on the view, or explicitly call the render method.
To be useful, templateHelpers should return an object. For instance:
templateHelpers: function() {
return {colLength: this.collection.length};
}
One important thing to keep in mind: fetch trigger an AJAX request that is done asynchronously. If you want to wait for the fetch to succeed before rendering the view, then you need to use Marionette.Async.
Update based on the update question
To avoid calling render from the view's initialize and only do it when render is called externally, change your code to:
return Marionette.CompositeView.extend({
className: 'user-board',
template: usersTemplate,
itemView: userItemView,
initialize: function () {
this.collection = new UseList();
var that = this;
this.defer = $.Deferred();
this.collection.fetch({
success: that.defer.resolve,
error: that.defer.resolve
});
},
appendHtml: function (collectionView, itemView) {
collectionView.$el.find('ul.users-list').append(itemView.el);
},
templateHelpers: function () {
console.log(this.collection.length);
// For greater flexibility and maintainability, don't override `serializeData`.
return {
weekly: this.options.weekly,
users_length: this.collection.length
};
},
render: function() {
var that = this,
args = arguments;
$.when(this.defer).done(function() {
Marionette.CompositeView.prototype.apply(that, args);
});
}
});
I'm resolving this.render both on success and error, otherwise if there is an error the view will never render (unless that's what you want).
Note that if you use Marionette.Async then you would return this.defer in the view's beforeRender and Marionette.Async would take care of delaying the rendering.
Also note that once this.defer is resolved, future renders will run when called as there is nothing to wait for, until this.defer has been reset programmatically.

At least in Marionette v1.0.3, I'm liking the pattern that rendering is handled automatically during a call to Region.show(), so I call that from a controller object which has the collection and passes it to the view on instantiation then shows the view. I don't even have to put this logic in a fetch success callback or explicitly bind to the 'reset' event, because the Marionette composite/collection view already knows to (re-)render itself on fetch success (which a debugger will show you).

After using a setup like has been detailed, you can also use template helpers a bit more usefully than has been described so far.
For example,
If you simply drop in <%= functionName %> into the template where you are trying to get the number to show up visually on the front end page (since you want .length I see), marionette will simply do the work for you.
So like this:
--Template File--
<div id="followerCount"> <%= showCount %> </div>
--Helper Function in View--
templateHelpers: {
showCount: function(){
return this.collection.length;
}
}
Hope that made sense or at least helps someone else perhaps looking for a simpler way to integrate database returned json to their templates.

Related

Preventing Marionette CompositeView render until fetch complete

I'm having a problem where render is being called autimatically in my Marionette CompositeView which is correct, the problem is that I'm fetching collection data in the initialize and want this to be present when the render happens. At the moment I'm running this.render() inside the done method of the fetch which re-renders but this causes problems as now I have 2 views per model. Can anyone recommend how I can properly prevent this initial render or prevent the duplicate views. 1 entry will output view1 and view2.
JS CompositeView
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.render();
});
},
First of all, I don't believe there is a way to stop rendering outright, but you have a bunch ways around that.
Option 1: fetch data first, then create your view and pass data into it when it's done.
//before view is rendered, this is outside of your view code.
var teamsCollection = new TeamsCollection();
teamsCollection.fetch().done(function(results) {
var options = {res: results};
var myView = new CompositeView(options);
myView.setElement( /* your element here */ ).render();
});
Option 2:
// don't use render method, use your own
initialize: function() {
var self = this;
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.doRender();
});
},
render: function(){}, // do nothing
doRender: function(){
// override render here rather than using default
}
Option 3: (if using template)
// if you have a template, then you can simply pass in a blank one on initialization
// then when the fetch is complete, replace the template and call render again
initialize: function() {
var self = this;
this.template = "<div></div"; // or anything else really
this.teamsCollection = new TeamsCollection();
this.teamsCollection.fetch().done(function() {
self.template = /* my template */;
self.render();
});
},
In reality I need more info. How is the view created? is it a region? is it added dynamically on the fly? Do you use templates? Can you provide any more code?

How to access an object/var from view1 but instantiated in view2 in Backbone js?

I am trying to access an object(this.historyComboBox) declared in a view(StatusView)'s render function and trying to access the same object from another view(HistoryView)'s extendedEvents function.
I have tried to use this.historyComboBox to access it but unable to hold any reference. Got really puzzled. If anyone has got some different idea I am ready to try it out!
Note: StatusView gets initialized prior to HistoryView.
Following is the code snippet.
StatusView = Backbone.View.extend({
init: function() {
//some code
},
render: function() {
this.historyComoBox = new sys.ComboBox();
}
}
HistoryView = Backbone.View.extend({
template: _.template(historyTemplate),
init: function() {
//some code
},
extendedEvents: {
'click #refreshButton': function() {
//want to access historyComoBox; not accessible with 'this.historyComoBox'
}
}
}
To get a property of a StatusView instance, you need a reference to that instance. So, if you have something like this:
var statusView = new StatusView();
Then from within the methods of HistoryView, you can do this:
statusView.historyComboBox;
However, while you can do it this way, I wouldn't access the StatusView instance directly like this. A better way would be to pass it to the HistoryView instance as a parameter, which you would receive in the initialize method. This keeps the views loosely coupled,
var HistoryView = Backbone.View.extend({
initialize: function (options) {
this.statusView = options.statusView;
},
events: {
'click #refreshButton': function () {
// use this.statusView;
}
}
});
(I notice you're using the names init and extendedEvents. You don't mention that you're using a third-party library with Backbone or something else that might change those, so I'll just mention that Backbone expects these to be initialize and events respectively.)

Context issue with Backbone view render function

I'm trying to include an optional argument in my Backbone view's render function, like this:
var AppView = Backbone.View.extend({
el: '#app',
initialize: function(){
_.bindAll(this,'render');
this.listenTo(app.collection.sellables,'reset', this.render);
},
render: function(sellableId){
if(typeof sellableId == "undefined"){
var sellable = app.collection.sellables.first();
}
else{
var sellable = app.collection.sellables.get(sellableId);
}
var view = new SellableView({model: sellable});
this.$('#sellables').append(view.render().el);
},
});
The first time this view renders, it occurs because the app.collection.sellables collection gets reset after receiving some data from the server. When this reset occurs, the view's render function gets called, but instead of being called with sellableId as undefined, the sellableId is equal to the app.collection.sellables variable, which is causing errors. To clarify, the view's render function is being called because of this:
app.collection.sellables.reset(data.skus);
Why would my render function be receiving the collection as an argument rather than the argument being undefined?
Also, if I render the view manually, it works correctly and the sellableId argument is undefined as expected:
var app.view.app = new AppView;
app.view.app.render();
You're binding your render to "reset" right here:
this.listenTo(app.collection.sellables,'reset', this.render);
From the Catalog of Events:
"reset" (collection, options) — when the collection's entire contents have been replaced.
So a handler for a "reset" event always gets the collection as its first argument. Keep in mind that a JavaScript function's arguments are determined when the function is called, not when the function is defined.
You have a few options. You could bind something else to the "reset" event:
initialize: function() {
//...
this.listenTo(app.collection.sellables, 'reset', this.been_reset);
},
been_reset: function() {
this.render();
}
You could leave render without arguments (like a standard render) and have a separate function to handle the arguments:
render: function() {
return this.append_model(app.collection.sellables.first());
},
append_model: function(sellable) {
var view = new SellableView({model: sellable});
this.$('#sellables').append(view.render().el);
return this;
}
Then you'd call view.append_model(app.collection.sellables.get(sellableId)) to add a specific model.

Backbone.js: Rendering json data in a model

OK, super basic Backbone question - I've been searching all round for this, but am just too slow to get it despite a multitude of similar questions. Rest assured I am suitably ashamed.
Anyway, enough self-flagellation - why doesn't this render?
var app = app || {};
app.Option = Backbone.Model.extend({
url: 'http://localhost:4711/api'
//This url contains the following JSON: {"title": "Blahblah", "author": "Luke Skywalker"};
});
app.View = Backbone.View.extend({
el: 'body',
initialize: function(){
this.model.fetch();
this.model.bind('change', this.render(), this);
},
render: function(){
this.$el.html(this.model.get('title'));
return this;
}
});
$(function() {
var option = new app.Option();
this.homeView = new app.View({ //Tried changing this to a standard var declaration but didn't work
model: option
});
this.homeView.render();
});
So I'm expecting to see the JSON "Blahblah" on the screen, but I see nothing. The JSON is being fetched correctly (I can see the successful GET request in the firebug console) and I think I've ensured the data is fetched before I attempt to render it...
So what's wrong? The console is giving me this error: "TypeError: (intermediate value).callback.call is not a function"
Thanks!
One thing is that you're calling this.render() immediately in your event binding rather than just binding the callback. Do this instead (using listenTo for best practices):
initialize: function(){
this.listenTo(this.model, 'change', this.render);
this.model.fetch();
}
Is it possible that the model is not actually changing? You might try to bind to sync instead of change to see if that works.
You also render twice. Once directly with this.homeView.render() and once via the event handler. If you really want to keep your model fetch in initialize and bind to the change event you don't need the direct render.
Play with those and see if that doesn't fix it.
Just remove the parentheses from the render method while binding:
this.model.bind('change', this.render, this);
Also using on or listenTo is a better approach then bind.
I would construct the backbone skeleton in the following way:
var app = app || {};
app.Option.Model = Backbone.Model.extend({});
app.Option.Collection = Backbone.Collection.extend({
model : app.Option.Model,
fetch : function(options) {
Backbone.Collection.prototype.fetch.call(this, options);
},
url : function() {
return 'http://localhost:4711/api';
},
parse: function(response) { // this is the ajax call
console.log(response);
}
});
Then in View just call the fetch method on initialize:
app.Option.View = Backbone.View.extend({
collection : app.Option.Collection,
initialize : {
this.collection.bind('reset', this.render, this);
this.collection.fetch();
},
render : {
var results = this.collection.toJSON();
console.log(results);
}
});
This is my minimal backbone skeleton when i need to call a webservice. I haven't tested locally, but this way the code should work.

Referencing object from within another object

I am creating a view in backbone that accepts a collection I want to then render that view then use the collection to append another view to the orginal but I don't know how to reference the original view in the success function of the collection. When I try the following code I get undefined.
new GenreView().render(new PopVideosCollection());
define (['jquery','underscore','backbone'],function($,_,Backbone) {
GenreView = Backbone.View.extend({
tagName:"div",
className:"sect",
template: _.template($("#genreView").html()),
render: function (collection)
{
this.$el.html(this.template);
collection.fetch ({success:function (video)
{
console.log(video.toJSON());
console.log(GenreView.el);
},
});
},
});
return GenreView;
});
You need to get a reference to the instance of GenreView from inside the callback. Something like this should get you there:
var context = this;
collection.fetch ({success:function (video){
console.log(video.toJSON());
console.log(context.el);
}
});
However, you should re-think your approach a little. It would be better to call fetch on your collection, and have the view subscribe the reset event of your collection. Starting with your example code, that would look something like:
var GenreView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "reset", this.appendSubView);
},
render: function() {
this.model.fetch();
},
appendSubView : function(video){
console.log(video.toJSON());
console.log(this.el);
}
});

Categories

Resources