Currently the application that I am building is a single page marionette application with a single entry point. When the user is at "/" I pass a very simple jade document:
body
header
section
div#main
script(src='/javascripts/lib/require.js', data-main='/javascripts/application.js')
The only javascript that I am loading to this is my require.js page, and once that's loaded I start things with Backbone.Marionette.Application() and thats the only app object I create for the whole app and that takes care of everything.
define([
'zepto', 'marionette', 'router', 'events'],
function ($, Marionette, router, Event) {
// set up the app instance
var MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
main: "#main"
});
MyApp.addInitializer(function(){
});
MyApp.on("initialize:after", function(){
var newRouter = new router(MyApp);
Backbone.history.start();
});
MyApp.start();
return MyApp;
});
If I have multiple entry points (in other words, multiple html pages created in the server side) for example one for "Classroom", one for "User Profile" one for "Discussion" , does that mean I need separate require.js documents to load for each page and separate Backbone.Marionette.Application() objects?
You don't have to otherwise it's too troublesome :) That's the job of Route.
At first, don't start app right away in the app definition. Remove this line
MyApp.start();
Then, put such command at the footer of your html page, and better after dom ready
$(function(){
MyApp.start();
});
The third is the most important. You need to define your routes in App or sub app(better). Here is the code "borrowed" from BBCloneMail
BBCloneMail.module("ContactApp", {
startWithParent: false,
define: function (ContactApp, App, Backbone, Marionette, $, _) {
var Router = Marionette.AppRouter.extend({
before: function() {
App.startSubApp("ContactApp", {});
},
appRoutes: {
"contacts": "showContacts"
}
});
In above case, when visitor enters your app from example.com/contacts, the method showContacts will be trigger and that's the start of your arranging page specific logic.
For more about appRouter:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.approuter.md
Related
I'm building a new application using Marionette and RequireJS, and I've got the following structure:
/main.js -- Main require() call that includes app.js and calls Application.start()
/app.js -- Application definition
/modules
/sub
/controller.js -- Defines a sub-application, requires app.js
...
I'm trying to keep dependencies at the top level of each file, as opposed to using require() inline, so that the r.js compiler can find them. The problem is, in my controller.js file, I am requiring app.js (in order to add initializers) and so I cannot require controller.js in app.js until after the Application has initialized, which means I can't put controller in the top-level define() array.
A simplified example of the currently working code:
// app.js
define(['marionette'], function(Marionette) {
var Application = new Marionette.Application();
Application.on("initialize:after", function() {
require(['modules/sub/controller'], function() {
Backbone.history.start();
});
});
});
// controller.js
define(['app'], function(Application) {
Application.module('SubApplication', function(SubApplication, Application, Backbone, Marionette, $, _) {
var router = Marionette.AppRouter.extend({
appRoutes: { "foo": "bar" }
});
var controller = { foo: function() {} };
Application.addInitializer(function() {
new router({ controller: controller });
});
});
});
I'm still fairly new to both Require and Marionette, so any suggestions would be welcome! I do know that I can include the files I want via the include option to r.js, but I thought this question was worth asking nonetheless.
The way I've chosen to do it in my book on Marionette and RequireJS is to require inline the modules that are only necessary for a subset of functionality. This simplifies development, and also means that modules won't be loaded unless that code path is triggered.
R.js will find inline dependencies just fine (provided they're defined as strings, i.e. not computed dynamically). In addition, they will also work with Almond.js (but don't forget to use the findNestedDependencies option in your build file).
Hope this helps!
It's my first project using Backbone/Underscore and RequireJS. I have a "parent view" named Home and a child view named Sidebar. I got that the parent view loaded with the content of the child templates (static content for now, without models), but the problem is that jquery and scripts.js (home made file that have all the calls to jquery functions) not loading properly. Seems like jquery loads at the bigining, but not reloads or not refresh when the Home view's rendered. I'm going to show the code to try to explaining it better.
The two files that I want to load are jquery and scripts.js. Jquery is the library and I think that it loads (I don't know how test it). The second one is the scripts.js, file where I call the functions of jquery (.animate(...), .css(...), etc.).
Home view (Home.js)
define(function (require) {
"use strict";
var $ = require('jquery'),
Scripts = require('scripts'),
Backbone = require('backbone.min'),
tpl = require('text!tpl/Home.html'),
Sidebar = require('app/views/Sidebar'),
template = _.template(tpl);
return Backbone.View.extend({
el: $("body"),
initialize : function() {
this.sidebar = new Sidebar(),
this.on('render', this.onRender);
},
render : function () {
this.$el.html(template());
return this.trigger('render');
},
onRender : function() {
this.sidebar.setElement('aside').render();
}
});
});
Sidebar View (Sidebar.js)
define(function (require) {
"use strict";
var $ = require('jquery'),
Scripts = require('scripts'),
_ = require('underscore.min'),
Backbone = require('backbone.min'),
tpl = require('text!tpl/Sidebar.html'),
template = _.template(tpl);
return Backbone.View.extend({
el : $("#sidebar"),
render: function () {
this.$el.html(template());
}
});
});
Finally, this is the code from my RequireJS configuration file (app.js). I've put that backbone.min depends on jquery, underscore and scripts to force to the view to load the scripts.js file.
app.js
require.config({
baseUrl: 'js/lib',
paths: {
app: '../app',
tpl: '../tpl'
},
map: {
'*': {
'app/app': 'app/app'
}
},
shim: {
'tablesorter.min' : ['jquery.min'],
'tablesorter.widgets.min' : ['jquery.min', 'tablesorter.min'],
'scripts' : ['jquery.min', 'tablesorter.min', 'tablesorter.widgets.min', 'json2'],
'backbone.min' : {
deps: ['underscore.min', 'jquery.min', 'scripts'],
exports: 'Backbone'
},
'underscore.min': {
exports: '_'
}
}
});
require([
"jquery.min",
"backbone.min",
"app/router",
"scripts"
], function(
$,
Backbone,
Router,
Scripts
){
var router = new Router();
Backbone.history.start();
});
EDIT:
I've a /tpl directory where I put all the template files of the views. To see the example I've upload a lite version of the project
Sry for the explanation... I've tried to explain the much better as possible :)
I don't know if it's the best solution... If somebody knows one better, I'd be glad to know about it.
Finnally, I put the jQuery code on a callback that calls after a render event of backbone. At the begining I had the jquery code inside the backbone view, but afterwards I created differents jquery functions depending of the backbone view that called it, so now I have a script.js file with all the jquery functions.
The disadvantatge, and here is where I'm not sure about the solution I've done, is that I've to call every jquery function from every view that needs that jquery complements.
I'm building a backbone.js + require.js application and I have run into the following issue.To structure my application I have an App.js file which as the following contents:
define(function(require) {
'use strict';
var _ = require('underscore'),
Backbone = require('backbone'),
Router = require('router'),
ModuleManager= require('moduleManager');
var App = function App() {
var base = {
router: new Router(),
moduleManager: new ModuleManager(),
start: function start(){
Backbone.history.start({pushState: true});
this.router.navigate('home', {trigger: true});
}
};
return _.extend(
base,
Backbone.events
);
};
return App;
});
The application is started with window.myApp = new App();, then myApp.start();.
The contents of router.js are as follows :
define(function(require) {
'use strict';
var _ = require('underscore'),
Backbone = require('backbone');
var Router = Backbone.Router.extend({
routes: {'home': 'home'},
home: function home() {
// MY ISSUE IS HERE
App.moduleManager.add('moduleName');
}
});
return Router;
});
The moduleManager is a utility function/object for :
Adding application modules via App.moduleManager.add('module') by requiring require.js files (backbone views + collections),
Doing some checks (e.g. ensuring the module doesn't already exist),
Centrally storing modules in App.moduleManager.modules
Everything is working fine except for the following point :
How can I call App.moduleManager from within App.router.home or any other route (App.router.xyz) ?
Within App.router.home, this can't refer to App (?)
Within router.js, I can't call App = require('app') because I would be making a circular dependency between App.js and Router.js
I'm not sure if I have a global application structure problem or if there is just a simple language construct which can work around this problem. Thanks for any help you can provide.
You could pass the object you need in the router's constructor.
But I would suggest using events. In the route, trigger an event, then listen for that event in the app. This leaves the router to do a single job, responding to route changes from the browser (back/forward clicks).
In router:
home: function() {
Backbone.trigger('home:show');
}
In app:
start: function(){
Backbone.history.start({pushState: true});
Backbone.on('home:show', this.showHome, this);
},
showHome: function(){
this.router.navigate('home', {trigger: false}); // just update, dont trigger route
this.moduleManager.add('moduleName');
}
Now if you want to change what the app is showing from your code, you can just trigger this event, instead of calling navigate on the router.
Some other code, maybe a menu view:
homeClicked: function(){
Backbone.trigger('home:show');
}
This would show your home view, and also update the history.
I've started a JavaScript application using Backbone.js and Require.js. The application displays different top-level views - searching items, editing different aspects of items, connecting items to each other. Each view is displayed exclusively.
The file for the router module looks like this:
define([
'backbone',
'myapp'
'views/search',
'views/edit1',
'views/edit2',
'views/connect'],
function(Backbone, App, SearchView, EditView1, Editview2, ConnectView) {
return Backbone.Router.extend({
routes: {
"search": "doSearch",
"edit1": "doEdit1",
// more routes here
},
doSearch: function() {
App.main.show(new SearchView()); // Marionette.js regions
},
doEditView1: function() {
App.main.show(new EditView1());
},
// etc.
});
});
In my code there are much more views. Is there a way to cut down the long require list of views to one object? Maybe through another architecture or some require.js trick?
Maybe I'm too influenced by the Symfony 2 concept of what a "router" is?
I've been thinking about this problem myself.
One simple solution would be to define a module with all your views in it, and then just include that as a dependency:
views/all.js
define([
'views/search',
'views/edit1',
'views/edit2',
'views/connect'],
function(SearchView, EditView1, EditView2, ConnectView) {
return {
"EditView1": EditView1,
"EditView2": EditView2,
"ConnectView": ConnectView
"SearchView": SearchView
};
});
Then in your router module you can include views/all as a dependency assigned to a variable Views, and call any view as Views.EditView1, Views.EditView2, etc.:
define([
'backbone',
'myapp',
'views/all'],
function(Backbone, App, Views) {
...
doSearch: function() {
App.main.show(new Views.SearchView());
},
...
});
I've never actually tried this but I think it would work.
On a syntactical level, Require.js also supports the 'simplified CommonJS wrapping'. Obviously this cannot help you avoid long lists of dependencies (as #shioyama's suggestion does) but will minimise the risk of mismatched dependency names with named function arguments and aid in keeping things tidy(er).
I have two backbone Views. One is the main view and the other is a view that will be used within the main view. I am making individual modules for my various views. I have the main view being loaded and run fine from the router. Here are my two views:
MAIN VIEW:
define([
'jquery',
'backbone',
'metros/docsMetro'
],
function($, Backbone, docsMetro) {
//Main Dashboard View
var Dashboard = Backbone.View.extend({
el: $('#mainContent'),
events: {
},
initialize: function() {
console.log('test');
//Validate User Here most likely. Each of the main view's for each app should probably call the same validation function
},
render: function (){
console.log('testing render');
}
});
// Return the Main Dashboard View
return new Dashboard;
The 'metros/docsMetro' file that is being loaded with require there in the top. I can see that this view is loading and is running through the init.
define([
'jquery',
'backbone'
],
function($, Backbone) {
//Docs Metro View
var docsMetro = Backbone.View.extend({
el: $('.docs'),
events: {},
initialize: function() {
console.log('docs Metro');
},
render: function (){
console.log('redering docs');
}
});
// Return the View
return new docsMetro;
});
My issue is in the Main View the 'docsMetro' variable that should be the returned docsMetro view is coming back null.
What am I missing, seems like it is all setup correctly?
can you try setting paths to modules to be relative?
instead of doing 'metros/docsMetro' do './metros/docsMetro' (assuming metros folder is on the same level as the module file from which you require metros/docsMetro)
And few other tips and fixes for your code.
Modules should return constructors rather then instances and you should instantiate them in the initializers for views that are requiring them.
jQuery and Backbone aren't going to drop support for global registering of their namespace so you don't need to pass them every time to the dependencies array. It's enough if you require them once in the boostrap before you load your router and main view which will take care of loading the rest of the app