In my application I need to load modules not at the initialization stage (by enumerating them in ./modules/modules), but on demand later based on some condition (e.g. user authorization results). Imagine I want to provide User A with calculator module, and User B with text editor module.
Let's take boilerplatejs example app for simplicity, and assume that sampleModule1 and sampleModule2 are to be loaded on demand.
So I remove the modules from initial loading sequence in src\modules\modules.js:
return [
require('./baseModule/module'),
/*require('./sampleModule1/module'),
require('./sampleModule2/module')*/
];
and add a control to summary page (src\modules\baseModule\landingPage\view.html) to load them on-demand:
<div>
Congratulations! You are one step closer to creating your next large scale Javascript application!
</div>
<div>
<select id="ModuleLoader">
<option value="">Select module to load...</option>
<option value="./modules/sampleModule1/module">sampleModule1</option>
<option value="./modules/sampleModule2/module">sampleModule2</option>
</select>
</div>
Then I patch src\modules\baseModule\module.js to pass context to the LandingPageComponent (for some reason it doesn't in the original source code):
controller.addRoutes({
"/" : new LandingPageComponent(context)
});
and finally add the loading code to src\modules\baseModule\landingPage\component.js:
$('#ModuleLoader').on('change', function(){
require([this.value], function(mod){
moduleContext.loadChildContexts([mod]);
alert('Module enabled. Use can now use it.');
});
});
This appears to be working, but is this the best way to do it?
Does it handle context loading properly?
How to protect against loading the same module twice?
Smart thinking here.. I was too working on improving BoilerplateJS to lazy-load modules as plugins last few days. I did something similar, and u can access the POC code at:
https://github.com/hasith/boilerplatejs
In the POC I did, I'm trying to achieve 'lazy loading' + 'configurable ui' at the same time.
Each of the screen (called an application) is a collection of components (plugins) placed on to a layout. These application definitions are just a JSON object either dynamically returned from server API or statically defined as JSON files (as it is in the POC). In the POC, application definitions are stored at "/server/application".
These applications can be called dynamically by convention now. For example "/##/shipping-app" will look for an application definition with the same name at "/server/application".
Application loading happens via a new component I have at '/modules/baseModule/appShell'. Anything staring with '/##/' will be routed to this component and then it will try to load the application definition by convention from "/server/application" folder.
When the 'appShell' receives the application definition (as a JSON), it will try to load the components (plugins) that are defined in it dynamically too. These pluggable components are placed in 'src/plugins' and will be loaded by convention too.
Sample application definition will look like below:
{
"application-id" : "2434",
"application-name" : "Order Application",
"application-layout" : "dummy-layout.css",
"components" : [
{
"comp-name" : "OrderDetails",
"layout-position" : "top-middle"
},
{
"comp-name" : "ShippingDetails",
"layout-position" : "bottom-middle"
}
]
}
The benefit of the approach are as follows:
Applications interfaces are component driven and configurable at runtime
It is possible that different application definitions are sent to the user depending on the user role, etc (logic in backend)
Applications can be created on-the-fly by combining already existing components
There is no static code changes needed in the 'framework' either to add new applications or components since loading is based on convention
These are very initial thoughts around. Appreciate any feedback !
You can protect against multiple loading of modules by using named functions for the change event, and unbinding the function after executing it.
Related
I am building a modular single page application which consumes multiple require config files from different sources. I would like a way in my application to be able to consume a list of all modules of a specific type. something like this:
define('module-type/an-implementation',...)
define('module-type/another-implementation',...)
require('module-type/*', function(modules){
$.each(modules,function(m){ m.doStuff(); });
})
This is similar patterns dependency injectors use with multiple dependency injection (eg. https://github.com/ninject/ninject/wiki/Multi-injection)
Is there a way to do this (or something similar) with require?
RequireJS doesn't know which modules exist until something requires them. Once a module is required / depended upon RequireJS will figure out where to request the module from based on module's name and RequireJS's configuration. Once the module is loaded it can be examined / executed to find out its dependencies and handle them in turn, until all dependencies are loaded and all module bodies are executed.
In order to be able to "consume a list of all modules of a specific type" something would need to be able to find all such modules. RequireJS doesn't have any means to know which modules exist, so it alone wouldn't be enough to implement "Multi Injection".
Speculation
Some kind of module registry could be created and populated with help of the build system: e.g. a file (say module-registry.js) could be generated each time a file in the source directory is added / removed or renamed, then multi inject could be possible like:
multiRequire('module-type/*', function(modules){
$.each(modules,function(m){ m.doStuff(); });
})
which in turn would call
require(findModules(pattern), function() {
callback(Array.prototype.slice.call(arguments, 0));
});
(where multiRequire and findModules are provided by the module registry).
I need a way to share information between modules - not only between components in the same module -.
I have common data to share between the different moduleContext.
-I tried using the application context (moduleContext.getParentContext().setSettings() or getSettings(), but each module context has a different moduleContext.getParentContext().
-I also tried creating a singleton object for the application, but even in this case the singleton data of the first module (landing page) are not available for the other modules.
-The third thing I tried is to pass data via notifications (notify / listen), with the same results.
Does anyone knows how to solve this problem?
Marcos
If you want to keep relationship between the module tree, it is necessary that you create your module hierarchy by calling:
parentContext.loadChildContexts(moduleContexts);
When this is done, that method ensures the 'event mediator' of child contexts is set to the exact same 'mediator' instance of the parent context. Then an event occurring at any of the modules will be notified to all the contexts in the same module tress.
It is the same for settings as well. When 'loadChildContexts' method is used, the settings of the parent context are 'copied' to child context settings.
If it still doesn't work for you, can you share your code to me? I may help you to find where the issue is.
In Mojito on top of Node.js, I followed the example on http://developer.yahoo.com/cocktails/mojito/docs/quickstart/
What I did was renaming controller.server.js to controller.server-foo.js, and created a new file controller.server.js to show "Hello World".
But when mojito is started, the old file controller.server-foo.js is being used and so the "Hello World" is not printed. How come Mojito will use the old file?
(I also tried renaming controller.server-foo.js to foo-controller.server.js and now the "Hello World" is printed, but why is controller.server-foo.js used?)
I found out that historically, the "affinity" of the controller can be two parts. The first part is common, server, or client, and the second part is optional, and it might be tests, or other words, so use other names such as controller-not-used-server.js to disable it.
#Charles, there are 3 registration processes in mojito (yes, it is confusing at first):
Affinity (server, client or common).
YUI.add when creating yui modules (controllers, models, binders, etc)
and the less common which is the registration by name (which includes soemthing that we call selectors)
In your case, by having two controllers, one of them with a custom selector named "foo", you are effectible putting in use the 3 registration at once. Here is what happen internally:
A controller is always detonated as "controller" filename from the mojit folder, which is part of the registration by name, and since you have "foo" selector for one of the controller, your mojit will have to modes, "default" and "foo". Which one of them will be use? depends on the application.json, where you can have some conditions to set the value of "selector", which by default is empty. If you set the value of selector to "foo" when, for example, device is an iphone, then that controller will be used when that condition matches.
Then the YUI.add plays an important role, it is the way we can identify which controller should be used, and its only requirement is that NO OTHER MODULE in the app can have the same YUI Module name, which means that your controllers can't be named the same when registering them thru YUI.add. And I'm sure this is what is happening in your case. If they both have the same name under YUI.add() one will always override the other, and you should probably see that in the logs as a warning, if not, feel free to open an issue thru github.
To summarize:
The names used when registering YUI modules have to be unique, in your case, you can use: YUI.add('MyMojit', function(){}) and YUI.add('MyMojitFoo', function(){}), for each controller.
Use the selector (e.g.: controller.server-mobile.js) to select which YUI module should be used for a particular request by setting selector to the proper value in application.json.
in boilerplatejs it looks like modules are pre-loaded
(Refer to code below)
return [
require('./baseModule/module'),
require('./sampleModule2/module'),
require('./customerModule/module'),
require('./orderSearchModule/module'),
require('./orderListModule/module'),
require('./mainMenuModule/module')
];
what is the impact on this when it comes to large scale web applications (module heavy web applications) . Is there any way to lazy load modules in boilerplatejs?
Java script has no reflection type of mechanism to load things. Any module that needs to be loaded has to be registered somewhere. This is why modules (sub contexts) are loaded as below:
appContext.loadChildContexts(moduleContexts);
(in src/application.js)
But code you have above is about requirejs AMD modules. This is basically the import of different JS scripts (each representing code for modules). With requireJS AMD, scripts (your source code) are not lazy loaded, but pre loaded. This make sense since your application source code should be available in the browser for execution. On the other hand, when you do JS optimization, we anyway create a single script containing all your source code. Then there is no meaning of having separate script files, or lazy loading of the source code.
But lazy loading should be applied to the behaviors (not to load code). This is why the UI components (ViewTemplate) are only created at the 'activate()' method. These are created only when users demands for it. That is a lazy loading of behavior, so that application rendering time is less.
this.activate = function(parent, params) {
// if panel is not created, lets create it and initiate bindings
if (!panel) {
panel = new Boiler.ViewTemplate(parent, template, nls);
...
}
vm.initialize(params.name);
panel.show();
}
I am facing an issue in declaring a fucation in block which I have added . I am calling a function by including an file which ia placed in the theme. I have also tried it by placing out of the theme folder. The function is alredy being user in front page. But when I am using the same function in that block. The screen gets blank and nothing displays. some part of my block coding is written below. Please help me.
<?php
global $base_url;
include($_SERVER['DOCUMENT_ROOT']."/travellar/geoiploc.php"); // indluded file
$ip = "203.189.25.0"; // Australia IP test
$country_code = getCountryFromIP($ip, "code");
I've had no problems loading functions from modules into custom blocks, but I've never tried loading one from a theme before. It's not clear to me whether or not theme functions are loaded before the page content is loaded.
You might have to create a custom module or include file to hold the function. Check out the module_load_include() function for how to load a specific include file.
A custom module would be a good approach as it is loaded before the theme layer and can be accessed from almost anywhere in Drupal except other modules with a lower weight than the custom module. It is also likely to come in handy for hooks and other overrides.
However, if you must have it in the theme layer, another option is adding it to template.php of your theme which should make it available within page.tpl.php and such, but not blocks I don't believe.
/sites/all/modules/mymodule/mymodule.info
name = My Module
package = !
description = It is MY module, not yours!
core = 6.x
The package "!" will make this module appear at the top of the modules page
/sites/all/modules/mymodule/mymodule.module
<?php
// Load mymodule.morePHP.inc
module_load_include('inc', 'mymodule', 'mymodule.morePHP');
// A custom function
function mymodule_my_custom_function($args) {
/* do custom stuff here */
return 'output';
}
/sites/all/modules/mymodule/mymodule.morePHP.inc
<?php
// An included custom function
function mymodule_other_custom_stuff() {
}