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.
Related
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.
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
});
I scaffolded a simple Angular app using Yeoman, and I've been playing with it ever since.
Inside the file app.js, which is the first one listed as a <script> inside index.html, I define the main module as:
angular.module('myMod', [])
.config(...)
Note the empty array of dependencies.
Now, when I want to, say, add a filter to this module, I create a myFilter.js file (which I load after app.js inside index.html); myFilter.js consists of:
angular.module('myMod').filter(...)
Note there's just one parameter to the module() function. If I pass the empty array of dependencies as a parameter to this module() function, really nothing appears on screen.
I've been playing with a bunch of other files which extended myMod with controllers, and passing [] as a parameter to the angular.module() function breaks my app every time.
It seems to me like I can only call angular.module() once using the second parameter, which may have some sense (how many times do I want to list my dependencies? What about consistency?). Is it that way?
If it is, is there some standard place where to list dependencies for a module?
angular.module("myModule",["dependencyA"]) will create a new module (this will crash if module allready exists.)
angular.module("myModule") uses an already known module.
This will also affect how you need to load the scripts in the index.html
You should declare your dependencies only once. In addition, it's best practice to keep all of your filters as a separate module that your app depends on, for example:
var myApp = angular.module('myApp', ['myApp.filters', 'myApp.directives', 'myApp.someOtherDependency']);
then, you would define your filters as a module that your app is dependant on:
angular.module('myApp.filters', []).filter(...)
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.
I just decided to try require.js for the first time and for the most part, it works fine - until I get to the point where I try to handle template loading dynamically.
basically, i have a requirement that the app should not proceed until the templates have all been loaded and made available.
to that effect, a module 'templating' has been created. it imports a definition of an array of templates (already available) that it needs to load - require - before it returns.
I am pretty sure this is probably an anti-pattern so how would you fix it?
app -> requires "templating"
define templating ->
- loop through an array of templates and dynamically create a list
- define all templates (via text!) so later we can require("template-name")
- also tried, require all templates
What I observe is that the templating module loads and becomes available to the app before all the templates are loaded.
The XHR fetching the templates follows afterwards.
How do i prevent the module from returning before all the texts load and compile? pseudo code or links to examples would be fine.
We actually had one solution early on in a project and then moved to another one.
[1] Synchronization - Because we're using jQuery in our app we have the jQuery Deferred object available for use throughout the app. I created one instance of Deferred which only had its .resolved() called when all of my templates were loaded. Then each time I tried to use a template I had it wrapped in code like this:
$.when(cache.templatesLoadedPromise).done(
function () {
// Render the view.
$("...").html($.tmpl(cache.template, jsonData));
});
Then none of the renders would happen until the templates were available.
[2] Per module loading and registration - Nobody was that crazy about solution [1] and we wanted each module to load only the templates needed for that particular module. So we started just listing them as "text!something.tmpl" requirements and then doing a registration of that particular template as the first line within the module that had that as a requirement. In our case, we use Handlebars as the template engine together with ICanHandlebarz (similar to ICanHaz for Mustache). So I have a call to ich.addTemplate("something", something); up at the beginning of the module code.
In a few cases, multiple modules used the same templates so I had to test to see if the template was already registered before doing the registration.
I hope one of those would help you with your problem.