How to remove Backbone views with require.js? - javascript

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);
}
});
})();

Related

How to structure a backbone application?

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();
});

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. :)

How do I reset all events in the Backbone

I want to restart all JQuery events in Backbone. The problem is this: when you go to the particular view I have the following event:
el: $('#content'),
events : {
'click #stops-drop-down' : 'stopDropDown',
},
stopDropDown: function(ui){
console.log("Event");
$("#stops-drop-down").toggleClass("focus-box");
$("#stops-list").slideToggle();
}
When you return to the previous view from which I've come to current and again go back to current and use the event, it is already running 2 times, if you do the same exercise another way it 3 times and it begins to grow. How can I deal with this problem and open the reset each time events?
Also to mention that use Requirejs. Here's what it looks like my router .js
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
app_router.on('route:homePage', function() {
var homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
var lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
SOLUTION:
I decided my problem as follows: I created the close method, which has the following content:
close: function(){
this.undelegateEvents();
$(this).empty;
this.unbind();
},
Also, here's how it seems my router.js now:
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
var lineDetailsView;
var homeView ;
app_router.on('route:homePage', function() {
if(homeView) {
homeView.close();
}
homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
if(lineDetailsView){
lineDetailsView.close();
}
lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
var YourView = Backbone.View.extend({
el: $('#content'),
events : {
'click #stops-drop-down' : 'stopDropDown',
},
close : function(){
this.remove(); // removes view from dom
this.unbind(); // unbinds all the events associated with the view
},
stopDropDown: function(ui){
console.log("Event");
$("#stops-drop-down").toggleClass("focus-box");
$("#stops-list").slideToggle();
}
});
// check if view already exists before creating new instance
// if exists call close on that view -- and then create your new view
if(yourView)
yourView.close();
yourview = new YourView();
check this article , there may be other reasons for the view to still exist
EDIT
this is almost the same way i have done in my application make sure you add close function as property in all views
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
app_router.on('route:homePage', function() {
if(homeView)
homeView.close();
var homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
if(lineDetailsView)
lineDetailsView.close();
var lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});

Extendable global config for an Angular module

I'm looking to include global options for an Angular directive/module.
I could use a .constant() (or simply an object of configs) in my module file, but since the module is designed for other people to include in their projects, and is installable via Bower, I don't like the idea of global options being blown away when the module gets an update. I'm aware that the .constant() could be included in another file, but then the user has to include it - rather I'd prefer that the module included everything (default values), and then the user could extend/modify if required.
I'm envisioning a similar approach as to a jQuery plugin pattern such as:
$('.myElement').myPlugin({
option1: '',
option2: ''
});
The Plugin
(function($) {
$.myPlugin = function( element, conf ) {
var $element = $(element);
var defaults = {
option1: '',
option2: '',
};
var config = $.extend( defaults, conf );
//...
};
$.fn.myPlugin = function(config) {
return this.each(function() {
if (undefined == $(this).data('myPlugin')) {
var plugin = new $.myPlugin(this, config);
$(this).data('myPlugin', plugin);
}
});
};
})(jQuery);
Your App and Configuring the Module
This is where we can define a configuration block and inject a provider. From this we can set our config options.
var myApp = angular.module( 'myApp', ['myModule'] )
myApp.config( function( myDirectiveConfigProvider ) {
myDirectiveConfigProvider.config = {
option1: 'A new setting'
//option2: 'A new setting'
};
// OR
myDirectiveConfigProvider.config.option1 = 'A new setting';
//myDirectiveConfigProvider.config.option2 = 'A new setting';
});
The Module
Within the module we can define a service to hold our default config options. This could also simply be included in the directive if you don't wish to inject it var config = {}.
We also define a Provider which will be injected into our configuration block.
Within the directive we simply need to extend config (injected service or var) with the providers.
angular.module( 'myModule', [] )
.value( 'config', {
'option1': 'my default setting',
'option2': 'my default setting'
})
.directive( 'myDirective', [ 'config', 'myDirectiveConfig', function( config, myDirectiveConfig ) {
return {
link: function( scope, element, attrs ) {
angular.extend( config, myDirectiveConfig.config );
console.log( config.option1 ); //'A new setting'
console.log( config.option2 ); //'my default setting'
}
}
}])
.provider( 'myDirectiveConfig', function() {
var self = this;
this.config = {};
this.$get = function() {
var extend = {};
extend.config = self.config;
return extend;
};
return this;
});

EmberJS Controller is undefined

So I started using EmberJS today.
// js/main.js
require.config({
baseUrl:'js/',
paths:{
ember: 'libs/emberjs/ember-0.9.8.1',
text: 'libs/require/text',
}
});
// Start the main app logic.
requirejs([
'ember',
'app/controller/users'
],
function(ember, UsersController) {
App = Em.Application.create();
console.log(UsersController); // undefined
}
);
// My Controller
// js/app/controller/users.js
define('app/controllers/users', [
'text!app/views/users/index.handlebar'
],
function( UsersIndexTemplate ) {
return Ember.Object.create({
indexView: Ember.View.create({
template: Ember.Handlebars.compile( UsersIndexTemplate )
}),
// Activates the views and other initializations
init: function() {
this.get( 'indexView' ).appendTo( '#content' );
}
});
});
My question here is, why is the Controller undefined? I built this while reading the TodoMVC example and don't udnerstand why this doesn't work the same way.
I found the answer, it's just too simple.
To invoke the main app logic I need to use require() NOT requirejs()

Categories