How to call the same function in different view in backbone - backbone.js

I create a customer class in my backbone project :
function Cart(){
//some working code here
this.updateQtyLabel = function(labelName){
$('#'+ labelName).text(getTotalQtyItemCart());
};
}
I called this function in my Home view :
var myCart = new Cart();
var HomeView = Backbone.View.extend({
initialize: function() {
myCart.updateQtyLabel("qtyCart");
},
//..... code of home view
}
Here is block of html in index.html:
<div id="qtyCart"></div>
So the updateQtyLable() only work in home view of the header in my web page.
Could anyone give me some ideas, how can/where should I call the updateQtyLable() to make it work for all page of my web site.
Thanks so much.

There is no globally metohod to call some function for all views.
You can therefore write base object for your all views, and call method in their initialize method. Something like this:
var BaseView = Backbone.View.extend({
initialize: function () { ..call here what you need...}
});
var HomeView = BaseView.extend({
initialize: function () {
BaseView.prototype.initialize.call(this);
..other code here...
}
});

Related

Marionette Layout's onShow function not getting called

I have a controller where I am starting my marionette app and defining a main region. Inside my main region, there is a mainLayout which further contains two sub-regions which i am rendering these two subregions but the onShow/onRender functions for my main itemview don't get called. :
var eventController = my.class{
create: function(){
//This is the model
var mainModel = new model();
this.createEventApp = new Backbone.Marionette.Application();
this.createEventApp.addRegions({
//This is the main container
mainRegion: ".event-container"
});
this.createEventApp.addInitializer(function(options){
var self=this;
//This is the main Item View for the app
new EventView({
model: mainModel,
region: self.mainRegion
});
});
this.createEventApp.start();
}
}
Now my corresponding Item View to this application is like :
var eventView = Backbone.Marionette.ItemView.extend{
//Template for the item view
template: eventViewTemplate;
initialize: function(options){
_.bindAll(this);
this.model = options.model;
this.region = options.region;
//Creating a main layout inside mainRegion of the app
var myLayout = Backbone.Marionette.Layout.extend({
template: EventViewTemplate,
regions: {
//Region 1
//Region 2
}
});
this.mainLayout = new myLayout();
this.region.show(this.mainLayout);
this.region1view = new region1({
model: this.model
});
//same for region 2
//showing both the regions in the layout
this.mainLayout.region1.show(this.region1view);
this.mainLayout.region2.show(this.region2view);
},
onShow: function(){
//I want to do something here but this function doesn't event gets called
}
}
You are never actually rendering your eventView. The onShow method will get called if you actually render the eventView by doing this in your application:
var view = new EventView({
model: mainModel
});
this.mainRegion.show(view);
If you want your eventView to contain another view then you should make it the layout. You can probably combine your layout and your eventView together. If you don't want your eventView to render anything then just make it a controller and put the onShow function on the layout.

BackboneJS How to import module and insert in specific html element of main view

I have a Main view where I import external views(modules), fetch the collections and insert them as new Views inside specific HTML elements. But now I get an error I dont understand...
I have this view defined:
function (App, Backbone, auth) {
var userNav = App.module();
UserNav = Backbone.View.extend({
...
)}
return UserNav;
}
then in my Main view, I want to import the View userNav.
define(['app', 'backbone', 'modules/userNav'],
function (App, Backbone, UserNav,) {
var Main = App.module();
Main.View = Backbone.View.extend({
template: 'main',
...
afterRender: function(){
var userNav = new UserNav.View();
this.insertView('.usernav', userNav);
}
But I get Uncaught TypeError: undefined is not a function - and its referring to the line
var userNav = UserNav.View();
what is the issue here?
I guess that the problem is the UserNav.
try to define this as a module using this code.
define(['Backbone','App','auth'], function(Backbone,App, Auth){
var UserNav = Backbone.View.extend({
//Your code
)}
return UserNav;
});
This is the way that requireJs define a module.
Hope it helps.
new UserNav() instead of new UserNav.View()
UserNav is the View

how to delegate a "load" DOM event?

I would like to call a function when a "load" event is triggered:
events: {
"load #eventPicture" : "_resizeHeaderPic"
}
I don't want to do something like this.$("#eventPicture").on("load", _resizeHeaderPic); because I have a lot of views (it's a Single Page App) and I could go back to show another view before the image was loaded. So, if I then come back to this view I would have two listener for that "load" event. Right? By putting everything in my events hash, I can undelegate properly.
But it seems that "load #eventPicture" does not work. Any suggestion?
You cannot track load event from Backbone events because this event fires only on image instance and doesn't bubble. So Backbone.View's $el cannot track it.
jQuery callback on image load (even when the image is cached)
UPDATE
I would suggest to use another concept (JSFiddle). This is best practice:
var LayoutView = Backbone.View.extend({
el : '[data-container]',
show : function (view) {
// remove current view
this.$view && this.$view.remove();
// save link to the new view
this.$view = view;
// render new view and append to our element
this.$el.html(this.$view.render().el);
}
});
var ImageView = Backbone.View.extend({
template : _.template('<img src="https://fbcdn-sphotos-g-a.akamaihd.net/hphotos-ak-prn2/1375054_4823566966612_1010607077_n.jpg"/>'),
render : function () {
this.$el.html(this.template());
this.$('img').on('load', _.bind(this.onLoad, this));
return this;
},
onLoad : function () {
console.log('onLoad');
}
});
var OtherView = Backbone.View.extend({
template : _.template('lalala'),
render : function () {
this.$el.html(this.template());
return this;
}
});
var Router = Backbone.Router.extend({
routes : {
'other' : 'other',
'*any' : 'image'
},
initialize : function (options) {
this.layout = new LayoutView();
},
other : function () {
this.layout.show(new OtherView());
},
image : function () {
this.layout.show(new ImageView());
}
});
new Router();
Backbone.history.start();

Backbone Views as CommonJS modules

I'm trying to get my head around using CommonJS modules within a Backbone application, so I have a skeleton Backbone View defined in /views/categories/edit.js:
app.Views.quoteCategoriesEdit = app.Ui.ModalView.extend({
className: '',
template: JST["templates/quotes/categories/quote-categories-edit.html"],
events: {
'click [data-key="save"]': 'save',
'click [data-key="cancel"]': 'cancel'
},
initialize: function (options) {
var that = this;
_.bindAll(this, 'save', 'cancel');
app.Collections.quotesCategories.on('change add', function () {
that.remove();
});
},
render: function () {
var that = this;
// boilerplate render code
return this;
}
});
If someone could show me how I can convert this into a CommonJS module to be used with Browserify, then I would be very grateful and it'd really help me understand how I go about modularising the rest of the application! Thanks
//once you get things into folders/files, this path may change
//but for now I'm assuming all your views will live in the same directory
var ModalView = require('./modal-view');
var QuoteCategoriesEdit = ModalView.extend({
className: '',
template: JST["templates/quotes/categories/quote-categories-edit.html"],
events: {
'click [data-key="save"]': 'save',
'click [data-key="cancel"]': 'cancel'
},
initialize: function (options) {
var that = this;
_.bindAll(this, 'save', 'cancel');
app.Collections.quotesCategories.on('change add', function () {
that.remove();
});
},
render: function () {
var that = this;
// boilerplate render code
return this;
}
});
//Simplest convention is just 1-class-per-module
//Just export the constructor function
module.exports = QuoteCategoriesEdit;
Follow-up question from the comments:
Very much appreciate this! How would you approach: app.Collections.quotesCategories as I house everything under the app namespace? Do I just require the Collection itself?
So the idea of an "app" namespace is the opposite of being modular/commonjs/browserify/requirejs. You don't need an app object anymore. Any module that needs to create a new instance of this collection would just do var QuotesCategories = require('app/collections/quotes-categories'); and that is all. No more globals or namespace objects. Mostly your views will get the models/collections they need in their constructor function options. Most of your models will get created by calling fetch on a collection, and most of your collections will be instantiated by your router.
Oh, and yes in this specific example it's probably best if non-view code creates the collection and passes it to the view via the constructor options.collection parameter. However, if you decided yes you really wanted your view to instantiate the collection, it wouldn't come from the app global namespace object, it would just come from a require call as you describe in your comment.

Accessing collection in multiple views - Backbone + RequireJS

I am working on my first RequireJS/Backbone app and I've hit a wall. There's a lot of code smell here, and I know I'm just missing on the pattern.
I have a route that shows all promotions, and one that shows a specific promotion (by Id):
showPromotions: function () {
var promotionsView = new PromotionsView();
},
editPromotion: function (promotionId) {
vent.trigger('promotion:show', promotionId);
}
In my promotions view initializer, I new up my PromotionsCollection & fetch. I also subscribe to the reset event on the collection. This calls addAll which ultimately builds a ul of all Promotions & appends it to a container div in the DOM.
define([
'jquery',
'underscore',
'backbone',
'app/vent',
'models/promotion/PromotionModel',
'views/promotions/Promotion',
'collections/promotions/PromotionsCollection',
'text!templates/promotions/promotionsListTemplate.html',
'views/promotions/Edit'
], function ($, _, Backbone, vent, PromotionModel, PromotionView, PromotionsCollection, promotionsListTemplate, PromotionEditView) {
var Promotions = Backbone.View.extend({
//el: ".main",
tagName: 'ul',
initialize: function () {
this.collection = new PromotionsCollection();
this.collection.on('reset', this.addAll, this);
this.collection.fetch();
},
render: function () {
$("#page").html(promotionsListTemplate);
return this;
},
addAll: function () {
//$("#page").html(promotionsListTemplate);
this.$el.empty().append('<li class="hide hero-unit NoCampaignsFound"><p>No campaigns found</p></li>');
this.collection.each(this.addOne, this);
this.render();
$("div.promotionsList").append(this.$el);
},
addOne: function (promotion) {
var promotionView = new PromotionView({ model: promotion });
this.$el.append(promotionView.render().el);
}
});
return Promotions;
});
Each promotion in the list has an edit button with a href of #promotion/edit/{id}. If I navigate first to the list page, and click edit, it works just fine. However, I cannot navigate straight to the edit page. I understand this is because I'm populating my collection in the initialize method on my View. I could have a "if collection.length == 0, fetch" type of call, but I prefer a design that doesn't have to perform this kind of check. My questions:
How do I make sure my collection is populated regardless of which route I took?
I'm calling render inside of my addAll method to pull in my template. I could certainly move that code in to addAll, but overall this code smells too. Should I have a "parent view" that's responsible for rendering the template itself, and instantiates my list/edit views as needed?
Thanks!
Here's one take. Just remember that there is more than one way to do this. In fact, this may not be the best one, but I do this myself, so maybe someone else can help us both!
First off though, you have a lot of imports in this js file. It's much easier to manage over time as you add/remove things if you import them like this:
define(function( require ){
// requirejs - too many includes to pass in the array
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone'),
Ns = require('namespace'),
Auth = require('views/auth/Auth'),
SideNav = require('views/sidenav/SideNav'),
CustomerModel = require('models/customer/customer');
// blah blah blah...});
That's just a style suggestion though, your call. As for the collection business, something like this:
Forms.CustomerEdit = Backbone.View.extend({
template: _.template( CustomerEditTemplate ),
initialize: function( config ){
var view = this;
view.model.on('change',view.render,view);
},
deferredRender: function ( ) {
var view = this;
// needsRefresh decides if this model needs to be fetched.
// implement on the model itself when you extend from the backbone
// base model.
if ( view.model.needsRefresh() ) {
view.model.fetch();
} else {
view.render();
}
},
render:function () {
var view = this;
view.$el.html( view.template({rows:view.model.toJSON()}) );
return this;
}
});
CustomerEdit = Backbone.View.extend({
tagName: "div",
attributes: {"id":"customerEdit",
"data-role":"page"},
template: _.template( CustomerEditTemplate, {} ),
initialize: function( config ){
var view = this;
// config._id is passed in from the router, as you have done, aka promotionId
view._id = config._id;
// build basic dom structure
view.$el.append( view.template );
view._id = config._id;
// Customer.Foo.Bar would be an initialized collection that this view has
// access to. In this case, it might be a global or even a "private"
// object that is available in a closure
view.model = ( Customer.Foo.Bar ) ? Customer.Foo.Bar.get(view._id) : new CustomerModel({_id:view._id});
view.subViews = {sidenav:new Views.SideNav({parent:view}),
auth:new Views.Auth(),
editCustomer: new Forms.CustomerEdit({parent:view,
el:view.$('#editCustomer'),
model:view.model})
};
},
render:function () {
var view = this;
// render stuff as usual
view.$('div[data-role="sidetray"]').html( view.subViews.sidenav.render().el );
view.$('#security').html( view.subViews.auth.render().el );
// magic here. this subview will return quickly or fetch and return later
// either way, since you passed it an 'el' during init, it will update the dom
// independent of this (parent) view render call.
view.subViews.editCustomer.deferredRender();
return this;
}
Again, this is just one way and might be terribly wrong, but it's how I do it and it seems to work great. I usually put a "loading" message in the dom where the subview eventually renders with replacement html.

Resources