Backbone and RequireJS effective loading - javascript

I looked at many examples on the internet how to start develop BB applications with requireJS but I am a kind of lost.
I think AMD has a goal that it load files only if they really needed. Not sooner.
Why I am seeing examples only where the developer put almost every files as a dependency at the beginning of his/her main file?
Here is an example:
https://github.com/jcreamer898/RequireJS-Backbone-Starter/tree/master/js
This application instantly loads main.js which depends on app.js which loads routers/home.js which requires views/view.js which loads the view's template and models/model.js which ... and end.
I can't see how to extend this application for example with more views where views' dependencies (its models, templates, collections, third party APIs, etc) load only when the router calls and initialize them. Other way this would be nonsense to use AMD where you load all your files when initializing your app.
Similar example here:
http://backbonetutorials.com/organizing-backbone-using-modules/
see router.js file.Actually it loads 'views/projects/list' and 'views/users/list' dependencies while the router does not know yet whether the user will need them in the future or not.
Please advise, thanks in advance!

It's a bit hard to see in such a small sample app, because you have to load something on the initial route, and loading something in Backbone usually means a model, a collection, and a view. Since the sample you linked has only one of each, then yes you're loading almost everything.
Where you start to see the "on demand" feature is where you add additional routes/views/models/etc. Keep in mind, however, that on-demand loading is probably a secondary goal of AMD/RequireJS. The primary goal is modularity. They then give you lots of options for either loading things on demand, or bundling everything up via the optimizer
Also there is nothing which says you have to put all the require() at the beginning of the file. You can do them later (e.g. when initiating a route). Here is a modified version of home.js from your first linked example. If you're using Chrome dev tools you can look at the network tab when the "debugger;" statement pauses execution. Then continue execution and see how the rest of scripts get loaded.
define([
'jquery',
'backbone',
'underscore'
],
function($, Backbone, _){
var Router = Backbone.Router.extend({
initialize: function(){
Backbone.history.start();
},
routes: {
'': 'home'
},
'home': function(){
debugger;
require(['views/view'], function (mainView) {
mainView.render();
});
}
});
return Router;
});
See this person's article for more background and where you might go next with it.

Related

Loading multiple modules under same parent route in Angular

I' am looking for a way through which I can load multiple modules under same path in Angular. For example, consider I have three modules AModule, BModule and CModule each having its own RouterModule.forChild call. Now I want to mount all these modules under say site route.
One way I have achieved this thing is by wrapping routes in RouterModule.forChild call under site route as follows:
RouterModule.forChild([
{
path: 'site',
children: [{}] // children goes here
}
])
I don't know whether this approach is correct but it is working fine. The only issue which this approach is that I have to specify canActivate in every module I want to mount under site. While this is not a problem, I was looking for a cleaner solution.
I know there is a property loadChildren which could be used to load modules lazily. But I want to load modules eagerly.
I' am using AngularCLI which splits code of module I specify in loadChildren in a separate JavaScript file which is not I want.
I' am using AngularClI v1.2.0 and Angular v4.2.5.
Any help is highly appreciated.
Thanks
I'm not entirely clear on your goal and what you are ultimately trying to achieve, but here are a few thoughts.
You can use the loadChildren and "lazy loading" to load on demand OR eagerly. If you select eagerly loaded routes, as soon as your main route is loaded and your first view is displayed, the other modules marked for eager loading are immediately loaded asynchronously.
I have an example of that here: https://github.com/DeborahK/Angular-Routing in the APM-Final folder.
I'm not clear on why you don't want module splitting. It can significantly improve the startup performance (time to display of the first page) of your application.
In addition to canActivate there is also a canActivateChild so you can put this on the parent and not have to repeat it for each route. The docs for that are here: https://angular.io/api/router/CanActivateChild

I just added angularjs to my BackboneJS site using requireJS to use for rendering a few views and gradually transitioning. Should I expect problems?

So, I've read in different places that Backbone and angular don't get along always, and that makes sense with respect to certain modules (like anchor link routing, since there can only be one top dog).
I went ahead and did the work to add angular anyway because it is clearly the better choice and there are way better plugins available for it, including a few table plugins that I am loving.
Now, I've yet to actually do this, but this has been my plan all along: use angular to generate and render a particular view or set of views, but always invoke it using Backbone.
For instance, I will need a "grid view" in the interior of one of my existing Backbone.Views. I am planning on booting angular with the root element provided by the client Backbone.View:
var ClientBBView = Backbone.View.extend({
...
render: function() {
... //do my normal rendering
require(['app/app.boot'],function(appBoot) { appBoot.boot($(targetElement)); });
}
...
}
Of course I would need to make the call specific to the view needed.
Anyway, if I do this, my intent is to demote angular so it doesn't try to take over the whole document. I noticed this line...
define([
'angular',
'app/js/app'
], function (angular, app) {
'use strict';
// as script is at the very bottom of the page no waiting for domReady
angular.bootstrap(document, ['app']); // <-- REPLACE DOCUMENT
});
so that's where the targetElement parameter will go. This way, I am thinking, it won't be able to mess with my routes at least.
Are there any gotchas I should know about with this sort of patchwork use of angular? Is this a common use case? It seems like it would work fine but I could use some advice.

Changing layouts depending on routes

I have a working Marionette app using modules together with RequireJS modules largely based on David Sulc's book.
When the server serves the index.html, which includes some base regions (menuRegion and mainRegion), my app.js starts the Marionette app. The app automatically loads the MenuApp module for the menu, and depending on the route, loads a certain module for the main content (i.e KanbanApp). Thus, I require all modules that define routes to be fully loaded before starting the app.
i.e
require(["apps/home/home_app", "apps/kanban/kanban_app"], function(){
Backbone.history.start();
});
At this point, the app is always using the regions defined in the index.html/app.js.
Now, I realize I need a landing page (login form, etc) to be included in the app which has a different layout. I have tried different approaches but I always get to a dead end. Which steps would you try to follow?
My main idea is that depending on the route, an "AppLayout" or "LandingLayout" is loaded. However, the fact that the routes are defined in submodules and I don't have a Main router makes this complicated. Additionally, I am unsure on where would the code for this logic go, maybe to a new extra layer between app.js and each submodule .js? I am unsure this would be the best solution because things get overcomplex. For example: Now when /route-from-the-real-app is called, a submodule route captures it and executes the controller. Until now the controller basically filled the mainRegion. But now, the controller also needs to call this extra layer to display the AppLayout and additionally, trigger an event for the MenuApp to be displayed in the menuRegion. This shouldn't be the original KanbanApp task.
This is how I am unsuccessfully trying to accomplish to do this, but any ideas working on my current code would be greatly appreciated:
I move the regions declared in the index.html/app.js into a new layout and leave it with a unique content region. LandingApp, displays its content here when "/" without problems.
I don't load MenuApp together with the app start anymore.
I created a new MainApp module, with the idea of it being the new extra layer. This module instantiates the LayoutView and adds it to the content region when an event is triggered.
I am unsure on how/when should I load both the MenuApp and the MainApp. As they have no defined routes, I don't require them with the app start, but I need them to register the event handlers.
You can view the full code (very simple) of the app before any of this changes had been applied here: https://github.com/mezod/multikanban
How would you approach this without using any advanced patterns or external libs?
If I understand the question right, you are asking for strategies involved with changing the "page" of your app?
I believe the general approach is to have one layout view instantiated in the app object which represents the container for the "page". That layout view could have a region called page. You could then switch views easily via that region. For example:
var App = Marionette.Application.extend({
changePage : function(view) {
this.rootView.page.show(view);
},
onStart : function() {
this.rootView = Marionette.LayoutView.extend({
el: 'body',
regions: {
'page' : '.page-container' // some div on the page <div class="page-container"></div>
}
});
});
});
Then to change the page (whether in a route method or somewhere else in the app) you can just go App.changePage(myNewPageView). That other page (myNewPageView) could also be a layout view which might contain a region for a sidebar, or a header, or whatever you want.

requirejs race condition - how to block until previous file retrieved

common_tempaltes file is not a requirejs file - rather a file that defines a global variable.
common_templates needs hogan. they both are requested more or less at the same time, but race condition is in affect. common_templates sometimes wins so the code fails with "hogan not loaded yet".
require(['module1', 'hogan', 'common_templates'], function(Module){
Module.do_stuff() // this module also requires hogan and common_templates to be loaded
});
other than nested require, is there a built in way to tell require to block until hogan is fully downloaded?
nested:
require(['hogan'], function(Fn, Ui){
require(['common_templates'], function(){
require(['module1'], function(Module){
Module.do_stuff();
});
});
});
This approach seems a bit hacky. is there a built in way to work around these race conditions?
If common_templates is not an AMD module (doesn't contain a define([deps]) call), then you need to configure it as a shim:
require.config({
shims: {
common_templates: {
deps: ["hogan"]
}
}
});
Now, require(['module1', 'hogan', 'common_templates']) and require(['module1', 'common_templates']) should work.
I had this exact same issue. I needed to essentially 'block' my program flow to ensure that an initial list of dependencies were loaded before a second list of dependencies, and then finally my main application code. Here is how I solved it. This is the Require.js call I made in my index.html file:
<script src="/js/lib/require_2.1.22.js"></script>
<script>
//debugger;
//Load common code that includes config, then load the app
//logic for this page. Do the requirejs calls here instead of
//a separate file so after a build there are only 2 HTTP
//requests instead of three.
requirejs(['/js/common_libs.js'], function (common) {
//debugger;
//Ensure the the AdminLTE code and its dependencies get loaded prior to loading the Backbone App.
requirejs(['/js/lib/adminlte.js'], function (common) {
//debugger;
//The main file for the Backbone.js application.
requirejs(['/js/app/main_app.js']);
});
});
</script>
The common_libs.js file contains the requirejs.config({shim:...}) stuff. The adminlte.js library does it's own dependency checking and will complain on the console if it does not detect it's dependencies. My problem was that it was getting loaded asynchronously with its dependencies and was causing a race condition.
I was able to wrap the existing code in adminlte.js like this:
//My experiments with Require.js to prevent race conditions.
define([
'jQuery-2.1.4.min',
'bootstrap.3.3.6',
'jquery.slimscroll.min'
], function($, Bootstrap, SlimScroll ) {
//Existing code goes here
...
//I knew to return this value by looking at the AdminLTE code. Your mileage may vary.
return $.AdminLTE;
});
That allowed me to load this library separately with its dependencies. The code in adminlte.js is only execute after its dependencies are loaded. Then, and only after that is complete, will main_app.js be loaded, along with its dependencies.
Structuring your code this way allows you to explicitly load your dependencies in batches.
Editing for clarity:
The dependencies are loaded in stages. To clarify the example above:
In the first stage jQuery, Boostrap, and the SlimScroll library are loaded, then the adminlte.js file is execute.
In the second stage, all other dependencies are loaded, then the main_app.js file is executed. main_app.js will have its own defined([], ...) function that calls out the rest of the dependencies it will need loaded.
This is much more efficient than writing a nested require() call for each dependency. And, as far as I can tell from the requirejs.org website, this is the 'proper' way to load serial dependencies.
Coding Update:
I also had to wrap the code in the jquery.slimscroll library inside a define() statement in order to explicitly call out the that this library depends on jQuery. Otherwise it was setting up another chance at a race condition.
This does not make sense, you said "common_templates requires hogan" but if common_templates requires hogan then hogan will already be loaded when the common_templates code starts.
Make sure common_templates.js is defined like this:
define(['hogan'], function(){
//common_templates suff here
});

Require dependency only when backbone view is loaded

I am using Backbone with Layout Manager and RequireJS.
View1 depends on 2 dependencies as can be seen below.
The application also has a similar view
named View2, which depends only on 'jquery.fileupload', unlike View1, which has 2 deps.
define(['jquery.fileupload', 'jquery.fileupload-ui'], function (dep1, dep2) {
var View1 = Backbone.View.extend({
...
});
return View1;
});
The problem is that 'jquery.fileupload-ui' (second dependency) seems to be loaded/evaluated by requireJS even if I don't visit a view that depends on it and that causes some plugin errors (I am using basic fileupload plugin in one view, and extended fileupload plugin in another view). It looks like define() pre-loads modules right away.
How can I avoid loading the second dependency at app initialization and load it only inside my view?
I think I could nest require() call into define for View1,
but I am not sure how then I can return a value if calls are nested.
Any time you module load the view1 module, 'jquery.fileupload-ui' will be loaded. If you only need this module in certain cases when you module load view1, you could have view1 require() in 'jquery.fileupload-ui' only if a certain code path is reached.
You can do this by adding a require(['jquery.fileupload-ui'], function(jqui){...}); in the specific method within view1 where you need the library.
I am not sure if this answers your question, but I think it may.

Categories