How to structure a backbone application? - javascript

I am creating a backbone application which deals with user authentication and after successfully authenticating user loads the main app.
In the main app there is a main view which deals with loading menus and basic UI for app then router navigate to a page sub view which loads content dynamically into a container, the issue here is router navigate is called before base view is rendered and base view cannot find container to append content.
here is my appinit.js
/*global App, $*/
(function(){
'use strict';
window.Application = {
Models: {},
Collections: {},
Views: {},
Routers: {},
Configs: {},
init: function () {
var token = Cookies.get("access_token");
var router = new Application.Routers.Approuter;
Backbone.history.start();
//Check token to find if user is logged in or not
if(token!=undefined){
//Load base view for app
var appFrame = new Application.Views.Appframe;
} else {
//If user is not logged in load login view
router.navigate('login', {trigger: true});
}
}
};
$(document).ready(function () {
Application.init();
});
})();
My router file
/*global App, Backbone*/
Application.Routers = Application.Routers || {};
(function () {
'use strict';
Application.Routers.Approuter = Backbone.Router.extend({
routes: {
"login": "login",
"logout": "logout",
"products": "products"
},
login: function () {
new Application.Views.Login();
ReadyLogin.init();
},
products: function () {
var productsView = new Application.Views.Product;
$('#page-content-data').html(productsView.el);
},
logout: function () {
Cookies.remove('access_token');
Application.Configs.Users.token = '';
var router = new Application.Routers.Approuter;
router.navigate('login', {trigger: true});
}
});
})();
My base view file
/*global App, Backbone, JST*/
Application.Views = Application.Views || {};
(function () {
'use strict';
Application.Views.Appframe = Backbone.View.extend({
template: JST['app/scripts/templates/appframe.ejs'],
tagName: 'div',
initialize: function () {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
var _this = this;
this.render = _.wrap(this.render, function(render) {
_this.beforeRender();
render();
_this.afterRender();
return _this;
});
this.render();
},
beforeRender: function () {
},
render: function () {
var self = this;
var userModel = new Application.Models.Users;
userModel.fetch({ headers: {'Authorization' :'Bearer '+Application.Configs.Users.token} }).done(function () {
self.$el.html(self.template(userModel.toJSON()));
});
return this
},
afterRender: function(){
$('#page-container').html(this.el);
//After appending base load products route
var router = new Application.Routers.Approuter;
router.navigate('products', {trigger: true});
}
});
})();
I have been stuck on this since 3 days, Please help.

If you are using many iife's in a global module pattern:
var router = (function() {
return Backbone.Router.extend({ /** stuff... */ });
})();
var models = (function() {
return Backbone.Model.extend({});
})();
//more stuff
(function(router, models) {
var app = { router: router, models: models };
//init inside the iife
})(router, models);
If you want to keep everything in one iife
(function(router, models) {
var app = {
router: router(),
models: models()
};
//init in here
})(
function router() {
return Backbone.Router.extend({});
},
function models() {
return {
User: Backbone.Model.extend({}),
Foobar: Backbone.Model.extend({})
};
},
//etc...
);
But, this is messy, right? That's why bundlers exist :)

From my experience building backbone apps, I put all the navigation logic inside appRouter.
Try restructuring you app by transferring the various bits that you put in window.Application into the "" route and into appRouter.initialize. It might solve your problem. For instance something like this:
var AppRouter = new (Backbone.Router.extend({
routes: {
"": 'home',
"login": 'login'
},
initialize: function(){
//everything you need to initialize you app
},
start: function(){
Backbone.history.start();
},
home: function(){
//your initial routing logic (navigate to login if token is undefined)
},
login: function(){}
}));
$(function(){
AppRouter.start();
});

Related

How to remove Backbone views with require.js?

I have a backbone app that uses require.js.
Prior to using require my Backbone router looked something like this.
APP.views = {};
APP.Router = Backbone.Router.extend({
routes: {
'(/)' : 'index',
'about(/)' : 'about'
},
initialize : function(){
Backbone.history.start({ pushState: true });
},
index: function() {
this.showView( new APP.Views.IndexView() );
},
about: function() {
this.showView( new APP.Views.AboutView() );
},
showView : function( view ) {
if ( APP.views.current ) {
APP.views.current.remove();
}
APP.views.current = view;
$( '#page' ).html( view.render().$el );
}
});
I would stash the 'current' view in a global variable and kill the existing view each time a route was changed and life was good.
But, how do I achieve this with require.js ?
My requirejs router currently looks like the following but I'm not sure how to remove the existing views. Although, I have not noticed any of the typical "zombie view" symptoms I feel like I should be removing the existing views.
define( function( require ){
// DEPS
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone');
// ROUTER
var Router = Backbone.Router.extend({
routes: {
'(/)' : 'index',
'about(/)' : 'about'
},
initialize : function(){
Backbone.history.start({ pushState: true });
},
index: function(){
this.showPage('index');
},
about: function() {
this.showPage('about');
},
showPage : function( pageName ) {
var view = 'views/pages/' + pageName;
require( [ view ] , function( Page ) {
var page = new Page();
$('#page').html( page.render().el );
});
}
});
return Router ;
});
Even before using require.js, a global wasn't needed.
Just put the current view into a router property.
initialize : function() {
this.$page = $('#page');
Backbone.history.start({ pushState: true });
},
showView : function(view) {
if (this.current) this.current.remove();
this.$page.html((this.current = view).render().el);
}
Then, same thing applies to your async require case:
showPage : function(pageName) {
if (this.current) this.current.remove();
var view = 'views/pages/' + pageName,
self = this;
require([view], function(Page) {
self.$page.html((self.current = new Page()).render().el);
});
}
But even then, I don't feel like requiring each view with an async require is worth it. You're just slowing down your application with a lot of extra requests.
Just define the dependencies for each module.
define([
'jquery',
'backbone',
'views/index',
'views/about'
], function($, Backbone, IndexView, AboutView){
// ...
});
While in development, you'll see a lot of request each time you refresh, but when ready for production, build a minified bundle of all the js files with require optimizer.
Also note that you can have module scope global, which are just local variable declared at the root of a module scope (IIFE or with require.js).
(function() {
var currentView;
var Router = Backbone.Router.extend({
// ...snip...
showView: function(view) {
if (currentView) currentView.remove();
this.$page.html((currentView = view).render().el);
}
});
})();

Marionette LayoutView not rendering

Here is my LayoutView:
define(["marionette", "lodash", "text!home/template.html"],
function(Marionette, _, templateHTML) {
var HomeView = Marionette.LayoutView.extend({
template: _.template(templateHTML),
regions: {
ad: ".adspace",
products: ".products"
}
});
return HomeView;
});
Here is my app.js:
define(["marionette", "backbone", "router", "home/view"],
function(Marionette, Backbone, Router, HomeView) {
'use strict';
var App = new Marionette.Application();
App.on('start', function() {
Backbone.history.start();
new HomeView();
});
return App;
});
And here is my router.js
define(["backbone", "home/view"], function(
Backbone,
HomeView
){
'use strict';
var Router = Backbone.Router.extend({
routes: {
"": "index"
},
index: function() {
console.log("HEYEEY");
return new HomeView().render();
}
});
return new Router();
});
When I try to view the page in the browser, the layoutview isn't rendered. Not sure what I am doing wrong here
Try to either define an el property on the layoutView:
var HomeView = Marionette.LayoutView.extend({
el: "#dom-element",
template: _.template(templateHTML),
regions: {
ad: ".adspace",
products: ".products"
}
});
or use a region to show the layoutview:
var region = new Marionette.Region({"el": "#container"});
var layoutView = new HomeView();
region.show(layoutView);
This should work because LayoutViews are extended from regular Marionette Itemviews.

Backbone.js - Pass variable from route to view/collection/model

So I'm building a mobile website and I have a directory called 'api' with various php files hat echo JSON formatted data from a remote API. I did this to avoid the cross-domain issue.
But one of the php files needs a GET parameter (i.e. id) so that I can echo the JSON data for a specific object based on it's id.
My collection will need to do this (assuming this will work):
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
url: '/api/tournament.php?id=' + id,
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
I have this in my router, but how do I pass the 'id' value to the view or collection:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournamentView = new TournamentView({id: id});
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Couple of things:
1) Your definition of the url property of the collection will not work as id is likely not defined when defining the TournamentCollection class. You can use a function rather than a property. TournamentCollection will become something like this:
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
initialize: function (options) {
this.id = options.id;
},
url: function () {
return '/api/tournament.php?id=' + this.id
},
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
This way you can initialize the object with an id, and later, when the url is fetched it will include the correct id.
2) I would probably initialize and fetch the collection from the router. Then from the initialize of the view, listen for that fetch to complete and ultimately re-render the view. Something like this:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournaments = new TournamentCollection({ id: id });
tournaments.fetch();
var tournamentView = new TournamentView({ collection: tournaments });
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
// Tournament View define stuff
var TournamentView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
//...
}
});
return TournamentView
hope that helps. :)

Backbone non-default root url

I'm trying to integrate Backbone inside a PHP CMS.
The root url for my Backbone app is:
http://localhost/administrator/index.php?option=com_test&controller=product.list
I have setup my router like this:
var AppRouter = Backbone.Router.extend({
routes: {
'': 'test',
}
});
var initialize = function () {
var router = new AppRouter;
router.on('test', function () {
console.log('match');
});
Backbone.history.start({
pushState: true,
root: '/administrator/index.php?option=com_test&controller=product.list'
});
router.navigate('/', {trigger: true});
};
The navigate function is correctly called but the route never matches.
I tried to add a trailing backslash in the root, but it doesn't not change anything.
The problem is that you should either add test method to Router like that:
var AppRouter = Backbone.Router.extend({
routes: {
'': 'test',
},
test: function(){
console.log('test route');
}
});
or listen to route:test event:
router.on('route:test', function () {
console.log('match');
});
because Backbone.Router triggers routes names when matched with prefix route:

BackboneJS: getting uncaught type error in backbone.js

I am new to backbonejs. I am facing the following error
Uncaught TypeError: undefined is not a function backbone.js:26
Here is my code
index.php inside script tag
<script>
new App.Router;
Backbone.history.start();
App.contacts = new App.Collections.Contacts;
App.contacts.fetch().then(function () {
new App.Views.App({ collection: App.contacts });
});
</script>
main.js
(function ($) {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.vent = _.extend({}, Backbone.Events);
})(jQuery);
router.js
App.Router = Backbone.Router.extend({
routes: {
'': 'index'
},
index: function () {
console.log('index page');
}
});
collections.js
App.Collections.Contacts = Backbone.Collection.extend({
model: App.Models.Contact,
url : '/contacts'
});
models.js
App.Models.Appoinment = Backbone.Model.extend({
//validate
});
views.js
//Global App view
App.Views.App = Backbone.View.extend({
initialize: function () {
console.log(this.collection.toJSON());
}
});
What am I doing wrong here?
I found the solution. Here what I did
Inside collections.js I was pointing to App.Models.Contact whereas I did not have this model. I created it and now it works.

Categories