How can we handle starting/stopping modules between routes without explicitly telling the route's controller method to start/stop each module.
var AppRouterController = {
index: function() {
// Start only modules I need for this route here
// in this case, HomeApp only
App.module('HomeApp').start();
// Stop all modules that should not be running for this route
// The idea, being that not everyone has to come to the index route first
// They could have been to many other routes with many different modules starting at each route before here
App.module('Module1').stop();
App.module('ModuleInfinity').stop();
// ...
// ...
// This could get tedious, expensive, and there has to be a better way.
},
someOtherRouteMethod: function() {
// Do it all over again
}
}
I know I am doing something wrong here, hopefully not fundamentally, but please let me know if there is a better way. Module management is going to be key for this project due to that fact that it will be running primarily on tablet devices.
It seems like overkill that you are starting and stopping every module in each route. There isn't much built into Marionette to help you with juggling your modules like that.
What I would recommend if you really want to do that is to write a wrapper for your routes that takes a list of modules to start and I function to run after starting/stopping modules.
Something like this:
(function (App) {
var listOfAllModules = ["HomeApp", "Module1", ...];
window.moduleWrapper = function (neededModules, route) {
return function () {
_.each(_.without(listOfAllModules, neededModules), function (moduleName) {
App.module(moduleName).stop();
});
_.each(neededModules, function (moduleName) {
App.module(moduleName).start();
});
route.apply(this, arguments);
}
};
})(App);
Then, in your routers just wrap routes that need to juggle modules.
var AppRouterController = {
index: moduleWrapper(["HomeApp"], function() {
// Only routing logic left...
})
};
Related
I'm trying to create directives on the fly, actually I achived that, but seams pretty hacky.
This was my first approach:
function create(myDir) {
angular.module("app").directive(myDir.name, function() {
return {
template:myDir.template
};
});
}
It didn't work because you can't register directives after application started.
based on this post: http://weblogs.thinktecture.com/pawel/2014/07/angularjs-dynamic-directives.html
I found out that I could use compileProvider to do the work, but since compileProvider isn't available outside config block, you need to put it out, so I did:
var provider = {};
angular.module("app",[]);
angular.module('app')
.config(function ($compileProvider) {
//It feels hacky to me too.
angular.copy($compileProvider, provider);
});
....
function create(myDir) {
provider.directive.apply(null, [myDir.name, function () {
return { template: myDir.template } }]);
render(myDir); //This render a new instance of my new directive
}
Surprisingly it worked. But I can't feel like being hacking the compileProvider, because I'm using it not in the way it was suppose to be, I would really like to know if is it possible to use the compileProvider properly after the application has started.
There is a list of dependencies that can be injected to config blocks (these are built-in $provide, $injector and all service providers) and a list of dependencies that can be injected to everywhere else (service instances and good old $injector). As you can see all that constant does is adding the dependency to both lists.
A common recipe for using providers outside config is
app.config(function ($provide, $compileProvider) {
$provide.constant('$compileProvider', $compileProvider);
});
Doing my first backbone app and I'm using a structure somewhat like this tutorial
I'm wondering where the correct place for me to put my onload code, such as setting up onclick listeners etc would be?
I have:
A simple Bootstrap
require.config({
paths: {
jquery: 'libs/jquery/jquery',
underscore: 'libs/underscore/underscore',
backbone: 'libs/backbone/backbone'
}
});
require([
// Load our app module and pass it to our definition function
'app',
], function(App){
// The "app" dependency is passed in as "App"
App.initialize();
});
The App.js
define(['routers/search'], function(router){
var initialize = function(){
this.router = new router();
}
return { initialize: initialize};
});
And then a simple router that calls the relevenent function in the router also defined as a module that calls the relevent function on the router depending on the page.
My feeling is that this function in the router is where I should be putting my onload code. Is that correct?
One possibility is to use the RequireJS domReady plugin (it's available for download from their short plugins list): http://requirejs.org/docs/api.html#pageload
Here's the example they give:
require(['domReady'], function (domReady) {
domReady(function () {
//This function is called once the DOM is ready.
//It will be safe to query the DOM and manipulate
//DOM nodes in this function.
});
});
So then you can just incorporate it into your normal RequireJS structure, knowing that both the DOM is loaded plus any additional dependencies you might have listed alongside it.
I have a very complex Backbone application with many views/Models and collections.
at times when user clicks a button I need to update multiple collections and update certain views.
In order to manage the collections I've handled most events in my router. The sequence of events is as follows:
user clicks a link/button and view triggers an event:
this.model.trigger('someEvent');
the model listening to the event, updates itself if necessary and notifies the router through my eventbus
eventbus.trigger('globalEVent');
My router is listening to global events, fetches collections/models from a cached storage and updates the necessary collections and models.
This has worked really well so far but
I have just too many events and my router code is becoming hard to manage. My question is there a way to handle events outside of the router and still access methods inside the router? is my approach correct or is there a more elegant solution I haven't considered?
Update:
Here's how I do it now:
in Router I call this method in my initialize():
registerModules : function() {
var self = this;
Backbone.trigger(Events.RegisterModule, function(moduleRoutes) {
_.each(moduleRoutes, function(moduleRoute) {
self.routeDetails[moduleRoute.name] = {
buildPageUrl: moduleRoute.buildPageUrl,
fragment : moduleRoute.fragment
}
self.route(moduleRoute.fragment, moduleRoute.name, moduleRoute.action);
});
});
},
Then I have regular self-exec blocks for each module/page which self registers (simplified version):
(function() {
var module = {
loadAndDisplay: function() {},
saveAndContinue: function(model) {
Backbone.trigger(Events.ChangePage, model.get('nextPage'));
},
registerEvents: function() {},
_init: function() {
module.registerEvents();
var self = this,
routes = [{
fragment: Constants.FRAGMENT,
name: Constants.PAGE_NAME,
buildPageUrl: function() {
return Constants.FRAGMENT;
},
action: module.loadAndDisplay
}];
Backbone.on(Events.RegisterModule, function(registerCallback) {
registerCallback.call(registerCallback, routes);
});
}
};
module._init();
})();
Of course your Router script should load before your module code. It works great for my needs. With this design I have separated router / modules completely and they have no knowledge of each other either. Each will handle it's own events/data etc. Shared logic goes in router.
You should split you router functionality into different classes. In our Marionette based application we using a RegionManager to handle all the view related stuff, like change views in different areas, open overlays etc and a StateMachine. The router itself just trigger different events, like state:change or region:change where the manager classes listen to.
Doing it tis way, you can have a different manager classes that handle a special aspect of your app. Let the router to what he is build for: listen on location change events and notify the app about it. The router should not have other logic then this.
Ok, I think this is something simple, however I am being to stupid to see it. Here is my code in backbone using the backbone boilerplate method
require([
"app",
// Libs
"jquery",
"backbone",
// Modules
"modules/example"
],
function(app, $, Backbone, Example) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"": "index",
"item" : 'item'
},
index: function()
{
console.info('Index Function');
var tutorial = new Example.Views.Tutorial();
// Attach the tutorial to the DOM
tutorial.$el.appendTo("#main");
// Render the tutorial.
tutorial.render();
},
item: function()
{
console.info('Item View');
}
});
// Treat the jQuery ready function as the entry point to the application.
// Inside this function, kick-off all initialization, everything up to this
// point should be definitions.
$(function() {
// Define your master router on the application namespace and trigger all
// navigation from this instance.
app.router = new Router();
// Trigger the initial route and enable HTML5 History API support
Backbone.history.start({ pushState: true, root: '/reel' });
});
// All navigation that is relative should be passed through the navigate
// method, to be processed by the router. If the link has a data-bypass
// attribute, bypass the delegation completely.
$(document).on("click", "a:not([data-bypass])", function(evt) {
// Get the anchor href and protcol
var href = $(this).attr("href");
var protocol = this.protocol + "//";
// Ensure the protocol is not part of URL, meaning its relative.
if (href && href.slice(0, protocol.length) !== protocol &&
href.indexOf("javascript:") !== 0) {
// Stop the default event to ensure the link will not cause a page
// refresh.
evt.preventDefault();
// `Backbone.history.navigate` is sufficient for all Routers and will
// trigger the correct events. The Router's internal `navigate` method
// calls this anyways.
Backbone.history.navigate(href, true);
}
});
});
I am running this of a MAMP server and when i type Localhost:8888/reel , I get the example index page that comes with boilerplate. However when I type Localhost:8888/reel/item or Localhost:8888/reel/#item I either get, page can not be found or directed back to my index page.
My question is what am i doing wrong. Do I need to use htaccess? This doesnt seem right. Is there a way using backbone to sort this. Sorry if this is really simple, just cant get my head around it.
the problem may lie with the pushState flag.
With that on the request goes all the way to the server and it sees the full url and responds to it with whatever it would do ...
does it work if you have a
$(function (){
setTimeout(navMe, 2000);
});
function navMe() {
backbone.navigate("item");
}
that way 2 seconds after load it will navigate to item view and you know that its because of the request going to the server and not to backbone.
I'd like to have multiple routers living on a single page for modularity. I initialize the routers on $(document).ready() in different js files. When I had just one router that worked fine because I could call History.start() right after initializing the router, but now that I have multiple routers that could be initialized from different files, I'm not sure when to call History.start().
For example:
<script src="router1.js" type="text/javascript"></script>
<script src="router2.js" type="text/javascript"></script>
In router1.js:
$(document).ready(function(){
new Core.Routers.Router1()
});
and likewise for router2.
Is the best solution just to add a new $(document).ready() that calls History.start() at the end of the page? I don't think the doc ready calls are blocking, so doesn't that introduce a race condition where all the Routers may not have been initialized by the time History.start() has been called.
You only need to call Backbone.history.start() once in your app and the only criteria for when you call it is that at least one router has to be instantiated already.
So, you could easily do this:
$(function(){
new MyRouter();
Backbone.history.start();
});
$(function(){
new AnotherRouter();
});
$(function(){
new AndMoreRouters();
});
I do a similar thing with routers on a regular basis, and I often start up new routers long after the page has been loaded and the user is interacting with the page.
FWIW though, you might be interested in the idea of initializers that I have in my Backbone.Marionette plugin and documented as part of this blog post: http://lostechies.com/derickbailey/2011/12/16/composite-javascript-applications-with-backbone-and-backbone-marionette/
You also may be able to check Backbone.History.started...
var Router = Backbone.Router.extend({
routes: {
'': 'load'
},
initialize: function () {
},
load: function () {
}
});
$(function () {
new Router();
if (!Backbone.History.started) {
Backbone.history.start();
}
});
It was added recently in a pull request.
Be sure and check out Derick's Marionette plugin as well, it's pretty awesome.