I am building a Backbone app using require.js for modular loading and Marionette to help with my application structuring and functionality. I have set up a require module for the event aggregator like this:-
define(['backbone', 'marionette'],function(Backbone, Marionette){
var ea = new Backbone.Wreqr.EventAggregator();
ea.on('all', function (e) { console.log("[EventAggregator] event: "+e);});
return ea;
});
I was hoping to pass it into my other require modules and have it function as a central event handling and messaging component and I am getting some success with this. I can pass the vent as a dependency into other modules without problem like so:-
define(['marionette', 'text!templates/nav.html', 'shell/vent'], function (Marionette, text, vent) {
return SplashView = Marionette.ItemView.extend({
template : text,
events : {
'click #splashContinueButton': 'onButtonClick'
},
onButtonClick : function(evt) {
vent.trigger('onSplashContinueClick');
}
});
});
The problem I am having is that although all the events are getting triggered across the different places in my app (which I can see in the console log), I am not able to listen to them in some parts of my app. For instance I have a Marionette module (loaded at runtime as a require module) which is trying to pick up some events like this:-
var SplashModule = shellApp.module("SplashModule");
SplashModule.addInitializer(function(){
vent.on("onSplashModelLoaded", this.onSplashModelLoaded);
vent.on("onSplashContinueClick", this.onSplashContinueClick);
}
I get nothing, even though if I log the vent from this place I can see it as an object. In the log, it contains an array of events that actually only contain the events being listened to by the root level application, not any other events that other parts of the app are listening for. And this is where my understanding falls apart: I thought I could use the event aggregator as a global communication and messaging system across my application structure. Can anyone please shed any insight into what might be going on?
Much thanks,
Sam
* UPDATE/EDIT/SOLUTION *
Hello, well, I have it working now (only 5 minutes after posting the above - doh!). Basically, adding my listeners in the initializer event of the module was too early (as far as I can tell). I moved them further along the chain of functions and now everything is behaving as expected.
The change I had to make to get it working was that I had to remove the vent listener "onSplashContinueClick" within the module further along. Before this change, it was in the initializer function but now it is further along:-
define(["backbone", "marionette", "shell/vent", "shell/shellapp", "shell/splash/splashmodel", "shell/splash/splashview"], function(Backbone, Marionette, vent, shellApp, SplashModel, SplashView){
var SplashModule = shellApp.module("SplashModule");
SplashModule.addInitializer(function(){
trace("SplashModule.addInitializer()");
SplashModule.model = SplashModel;
SplashModule.model.fetch({
success:function(){
//trace("splashModel.fetch success")
SplashModule.onSplashModelLoaded();
},
error:function() {
//trace("splashModel.fetch error")
}
});
});
SplashModule.addFinalizer(function(){
});
SplashModule.initView = function () {
//trace("SplashModule.initView()");
SplashModule.mainView = new SplashView({model: SplashModel});
shellApp.mainRegion.show(SplashModule.mainView);
vent.on("onSplashContinueClick", this.onSplashContinueClick);
};
SplashModule.end = function () {
trace("SplashModule.end()");
shellApp.mainRegion.close();
vent.trigger("onSplashModuleComplete");
};
// events
SplashModule.onSplashModelLoaded = function () {
trace("SplashModule.onSplashModelLoaded");
SplashModule.initView();
};
SplashModule.onSplashContinueClick = function () {
trace("SplashModule.onSplashContinueClick()");
SplashModule.end();
};
return SplashModule;
});
I am guessing the problem has to do with the order of when dependencies are available and/or ready. I believe the vent was not ready for the listener during the initializer method. This may well be tied up to my usage of Marionette modules within require modules.
Using RequireJS also involves some clean modules...
Backbone.Wreqr.EventAggregator is a module that is part of Marionette.js (for the record, Derrick Bailey just put this module that is made by someone else inside his library, same thing for Backbone.BabySitter)
using RequireJS, you are limited to see what is exported by the library, and in this case Marionette
I think the best way is to split marionette into the 3 modules it actually contains backbone.babysitter, backbone.wreqr, and marionette.
Then you have to create a shim for each module
I used this
require.config({
baseUrl: "/Scripts/",
paths: {
"json2": "vendor/JSON2",
"backbone": "vendor/backbone/backbone.1.1.0",
"localStorage": "vendor/backbone/backbone.localStorage.1.1.9",
"marionette": "vendor/backbone/backbone.marionette.1.8.6",
"bootstrap": "vendor/bootstrap/bootstrap.3.1.1",
"jquery": "vendor/jquery/jquery.1.8.3",
"text": "vendor/Require/text.0.27.0",
"underscore": "vendor/underscore/underscore.1.5.2",
"wreqr": "vendor/backbone/backbone.wreqr",
"babysitter": "vendor/backbone/backbone.babysitter",
},
shim: {
"json2": {
exports: "JSON"
},
"jquery": {
exports: "$"
},
"underscore": {
exports: "_"
},
"bootstrap": {
deps: ["jquery"]
},
"backbone": {
deps: ["underscore", "jquery"],
exports: "Backbone"
},
"validation": {
deps: ["backbone"],
exports: "Backbone.Validation"
},
"wreqr": {
deps: ["backbone", "underscore"],
exports: "Backbone.Wreqr"
},
"marionette": {
deps: ["backbone", "babysitter", "wreqr"],
exports: "Backbone.Marionette"
},
"localStorage": {
deps: ["backbone"],
exports: "Backbone.LocalStorage"
}
}
});
once you have this, you will be able to use wreqr
There's another trick in your script, the fact you write
define(['backbone', 'marionette'],function(Backbone, Marionette){
is a bit disturbing, because you will never know if the use of Backbone or Marionette in your implementation is made on purpose or not. I mean, the namespaces related to backbone and marionette are Backbone and Marionette; I suggest you alias Backbone as backbone and Marionette as marionette like this:
define(['backbone', 'marionette'],function(backbone, marionette){. Doing such , you will be able to check if your module has been downloaded on demand by RequireJS or not.
Then once the shim has been created, your first block code should look like this
define(["wreqr"],function(wreqr){
var ea = new wreqr.EventAggregator();
ea.on('all', function (e) { console.log("[EventAggregator] event: "+e);});
return ea;
});
Related
I'm using raphael.js in a backbone.js project. Importing raphael works like a charm (this version is AMD compliant). Everything is working as expected. "app" here is a predefined global object defined in another app.js file.
define(['jquery', 'underscore', 'app', 'backbone','raphael' ],
function($, _, app, Backbone, Raphael) {
var AppView = Backbone.View.extend({
initialize: function() {
app.paper = Raphael(0, 0, app.w, app.h);
}
...
}) })
Now my app.paper has all Raphael methods. Awesome!
I just discovered in the Raphael API that I can add my own predefined methods using Raphael.el and Raphael.fn
initialize: function() {
app.paper = Raphael(0, 0, app.w, app.h);
Raphael.el.myfill = function(){
this.attr('fill', '90-#fff-#000');
}
app.paper.circle(x,y,r).myfill(); //it works! (Brilliant!)
}
}
My question is, how can I put the Raphael.el.myfill definition along with other Raphael.fn.mydefinedmethods into another javascript file and bring it into the AppView above?
I don't want to clog up my AppView file with lengthy definitions, and I also would like to provide variability as to which Raphael.[el|fn] definitions I use in different views. But since these object constructors are already part of the Raphael.js object that I've already pulled in as a dependency, I'm not sure how to separate the Raphael.el and Raphael.fn definitions out using the require.js protocol. Before require.js I would have simply put such definitions in another myRaphaelDefs.js file, added another "script" tag to my html and they'd all be available always, but this is the 2015 and I've jumped on the modular js bandwagon.
I'm using RedRaphael branch which is AMD compliant, so I have no "define" wrapper on the Raphael.js itself. If this library did come with such a wrapper I might try adding the outsourced definitions directly into the Raphael.js as dependencies. (not an option) RedRaphael works with require.js right out of the box, so there's no "define" wrapper there.
What we do is add a wrapper around our libraries and route it with the map option in require.config, so the wrapper gets the original library, but everything else goes throw the wrapper:
raphael-wrapper.js:
define(['raphael'], function (Raphael) {
Raphael.el.myfill = function(){
this.attr('fill', '90-#fff-#000');
};
return Raphael;
});
require.config:
{
paths: {
'raphael': '/path/to/rahpael'
},
map: {
'*': {
'raphael': '/path/to/rahpael-wrapper'
},
'/path/to/rahpael-wrapper': {
'raphael': 'raphael'
}
}
}
We use RequireJS to add modularity to our Backbone.js site. I found myself with the need to override the Backbone.Collection class to add an advance filtering routine.
My questions is, say I have the following 'override',
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
and our site structure looks like the following:
where, main.js sits at the top level and beneath it is app.js; Where would I add this override, such that I don't have to add a new module to our RequireJS definition for every class? More generally, where are overrides to Backbone usually recommended?
Create a file (say Overrides.js in modules folder)
define(function(require){
var app = require('app');
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
// other overrides can also be added here in this file like
_.extend(Backbone.View.prototype,{}, {
// adding functions or overriding something
})
});
Now, require this file in main.js like
require([
'backbone',
'App',
'modules/Overrides',
'globalize',
.
.
.
],
function ( Backbone, App, ..... ) {
});
There you go!
Say, I want to add some function to the view or override some function such as render, initialize, remove,... universally in the application. You could do something like this:
_.extend(Backbone.View.prototype,{}, {
remove: function() {
alert("View removed");
this.$el.remove();
this.stopListening();
return this;
}
});
One easy option if using requirejs, in your require config add an init statement. eg,
require.config({
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone',
init: function (_) {
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
}
}
}
});
Alternatively you can use a map config call to amp all backbone calls to your overridden backbone,
require.config({
map: {
'*': { 'backbone': 'backbone-custom' },
'backbone-custom': { 'backbone': 'backbone' }
}
});
// backbone-custom.js file:
define(['backbone'], function (Backbone) {
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
return Backbone;
});
Either case will load the override into the backbone object before it is used anywhere.
In the following code, I am trying to trigger an event using dynamic require. For some reason I am not able to access app object in the eventRouter method. I am getting "TypeError: app is undefined" error. I have implemented listener on show event in respective controller files.
My question is similar to this post except my listeners are in different controller files and I am not able to access app object as suggested in the post.
Help appreciated !!!!
define(["app",
"tpl!templates/nav/nav.tpl",
"tpl!templates/nav/navMenuItem.tpl",
"entities/navEntity"
],
function(app, navTpl, navMenuItem, navEntity){
navMenuItem = Backbone.Marionette.ItemView.extend({
template: navMenuItem,
events: {
"click a": "eventRouter"
},
eventRouter:function(ev)
{
var that = this;
var moduleName = $(ev.currentTarget).text().toLowerCase();
require(['controllers/' + moduleName + 'Controller'], function(controller){
app.trigger(moduleName + ':show');
});
}
});
navMenu = Backbone.Marionette.CollectionView.extend({
tagName: 'ul',
itemView: navMenuItem,
collection: navEntity.navItems,
});
return {
navMenu: navMenu,
navMenuItem: navMenuItem
}
});
To overcome Circular dependencies you can check the Following :
https://stackoverflow.com/a/4881496/2303999
Manage your modules accordingly and avoid dependencies. Make common js file for functions you use use now and then. You can even use Marionette Vent object to pass events and do according on that event.
In this simple Require/Backbone app
https://github.com/thisishardcoded/require-prob
Why does app.js see Router but TestView.js not?
Here is the first line of app.js
define(['router'],function (Router) {
and here is the first line of TestView.js
define(['backbone','router'],function(Backbone,Router){
Check out the repo for full details, download, run and check console log if you feel so inclined
Thanks!
Jim
More: Ok, the answer is - because of the order in which it is loaded and even if that were altered I have a circular dependency don't I? TestView needs Router, Router needs TestView.
In which case the solution might be
var r=require('router);
r.navigate or whatever
but, that seems like a shame that Router is not directly accessible everywhere and, is the above method good practice anyway?
Surely it happens because of circular dependency. To resolve it, you either pass router to view's constructor and remove router dependency from view's module, or use require('router') in your view.
1st option, router.js:
app_router.on('route:test', function () {
var testView = new TestView({router: app_router});
testView.render();
})
1st option, view.js:
define(['backbone'], function(Backbone){
var TestView=Backbone.View.extend({
el: '#test',
initialize: function() {
// get router from constructior options
this.router = this.options.router
},
render: function(){
this.$el.html('<p>Foo!</p>');
console.log("TestView.js does not find 'Router',", this.router);
}
});
return TestView;
});
2nd option, view.js:
define(['backbone','router'], function(Backbone, router){
// at this point router module is not loaded yet so router is undefined
var TestView=Backbone.View.extend({
el: '#test',
initialize: function() {
// at this point router module is loaded and you may access it with require call
this.router = require('router');
},
render: function(){
this.$el.html('<p>Foo!</p>');
console.log("TestView.js does not find 'Router',", this.router);
}
});
return TestView;
});
2nd option is also described here: http://requirejs.org/docs/api.html#circular
You should define baseUrl property in your main.js file that contains RequireJS config.
In this way all paths to modules in your application will be relative to that baseUrl.
See:
http://requirejs.org/docs/api.html#jsfiles
http://requirejs.org/docs/api.html#config-baseUrl
I downloaded and inspected your code. Following could be the issues:
require.js only works with AMDs. Since backbone no longer supports AMD. You will need to use AMD enabled version of Backbone. You can get it here
TestView is the dependency in you Router. So it loads before the Router is loaded.
You might want to improve the coding pattern. Here is my suggestion:
App.js
define([
'backbone',
'router',
], function(Backbone, MainRouter){
'use strict';
var AppView = Backbone.View.extend({
initialize: function(){
App.router = new MainRouter();
Backbone.history.start();
}
});
return AppView;
});
Router.js
define([
'backbone',
'view/TestView'
], function(Backbone, TestView){
var Main = Backbone.Router.extend({
routes: {
'test': 'test'
},
test: function(){
new TestView({
// pass model or collection to the view
// model: new TestModel // remember to require
});
}
});
return Main;
});
EDIT
Listening to events:
// in main.js
var window.Vent = {};
_.extend(window.Vent, Backbone.Events);
// now in any view you can trigger a event
$('something').on('click', function(){
window.Vent.trigger('somethinghappened', this);
// this is reference to current object
});
// now in other view you can do
window.Vent.on('somethinghappened', this.run, this);
// this in the end is the reference we passed when event was triggered
run: function(obj){
//this function will run when the event is triggered
// obj is the object who triggered the event
}
PS: why do you want to use router in view?? I have built quite a few backbone apps. Never needed to do so.
You can use available Backbone.history.navigate to achieve your goal easier, because Router.navigate is a simple wrapper for it. Consider this part of Backbone source:
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
return this;
},
I'm in the process of creating a Backbone.js app using Require.js. Each view file corresponds to one resource (e.g. 'News'). Within each view file, I declare a backbone
view for each action ('index', 'new', etc). At the bottom of the view file I receive
the necessary info from the router and then decide which view to instantiate (based on the info passed in from the router).
This all works well, but it requires lots of code and doesn't seem to be the 'backbone.js way'. For one thing, I'm rellying on the url to manage state. For another, I'm not using _.bind which pops up in a lot of backbone.js examples. In other words, I don't think I'm doing it right, and my code base smells... Any thoughts on how to structure my app better?
router.js
define([
'jquery',
'underscore',
'backbone',
'views/news'],
function($, _, Backbone, newsView){
var AppRouter = Backbone.Router.extend({
routes:{
'news':'news',
'news/:action':'news',
'news/:action/:id':'news'
},
news: function(action, id){
newsView(this, action, id).render();
}
});
var intialize = function(){
new AppRouter;
Backbone.history.start()
};
return{
initialize: initialize;
};
}
news.js ('views/news')
define([
'jquery',
'underscore',
'backbone',
'collections/news',
'text!templates/news/index.html',
'text!templates/news/form.html'
], function($, _, Backbone, newsCollection, newsIndexTemplate, newsFormTemplate){
var indexNewsView = Backbone.View.extend({
el: $("#content"),
initialize: function(router){
...
},
render: function(){
...
}
});
var newNewsView = Backbone.View.extend({
el: $("#modal"),
render: function(){
...
}
});
...
/*
* SUB ROUTER ACTIONS
*/
var defaultAction = function(router){
return new newsIndexView(router);
}
var subRouter = {
undefined: function(router){return defaultAction(router);},
'index': function(router){ return defaultAction(router);},
'new': function(){
return new newNewsView()
},
'create': function(router){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new createNewsView(router, unsavedModel);
},
'edit': function(router, id){
return new editNewsView(router, id);
},
'update': function(router, id){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new updateNewsView(router, id, unsavedModel);
},
}
return function(router, action, id){
var re = /^(index)$|^(edit)$|^(update)$|^(new)$|^(create)$/
if(action != undefined && !re.test(action)){
router.navigate('/news',true);
}
return subRouter[action](router, id);
}
});
While I feel like it's important to emphasize that there isn't really a "Backbone.js way", it does seem like you're replicating work Backbone should be doing for you.
I agree that it makes sense to have a specialized Router for each independent section of your application. But it looks at first glance like what you're doing in your "sub-router" section is just recreating the Backbone.Router functionality. Your AppRouter doesn't need to deal with /news URLs at all; you can just initialize a NewsRouter with news-specific routes, and it will deal with news-related URLs:
var NewsRouter = Backbone.Router.extend({
routes:{
'news': 'index',
'news/create': 'create',
'news/update/:id': 'update',
'news/edit/:id': 'edit'
},
index: function() { ... },
create: function() { ... },
// etc
});
As long as this is initialized before you call Backbone.history.start(), it will capture URL requests for its routes, and you never have to deal with the AppRouter. You also don't need to deal with the ugly bit of code at the bottom of your view - that's basically just doing what the core Backbone.Router does for you.
I'm using require.js and backbone as well I think the main difference that i'd suggest is that each file should return just one view, model, router or collection.
so my main html page requires my main router. That router is a module that requires a few views based on each of it's routes, and a bootstrapped model. Each router method passes the relevant bootstrapped model piece to the relevant view.
From there it stays really clean as long as each file is just 1 backbone thing (model, collection, view, router) and requires just the elements it uses. This makes for a lot of js files (I have about 100 for my current project) but that's where require.js optimization comes into play.
I hope that helps.
Why don't you structure your routes like this:
routes:{
'news':'news',
'news/edit/:id':'editNews',
'news/new':'newNews',
...
}