Modules
ColdBox Modules
Introduction
ColdBox Modules are self-contained subsets of a ColdBox application that can be dropped in to any ColdBox application and become alive as part of the host application. They will bring re-usability and extensibility to any ColdBox application, as now you can break them down further into a collection of modules. This concept has been around in software design for a long time as it is always essential to partition a system into manageable modules or parts.
"In structured design and data-driven design, a module is a generic term used to describe a named and addressable group of program statements" by Craig Borysowich (Chief Technology Tactician)
The key to the statement is that these modules are named and addressable, which is exactly what ColdBox Modules will offer to your application. So by building an application with a methodology of modules, you will inherently achieve:
- Manageability (i.e., small and simple parts that can be easily understood and worked on)
- Independence (i.e., a module can live on its own if necessary and tested outside of its environments, produces very nice low coupling between core and modules)
- Isolation (i.e., some modules can be completely isolated and decoupled)
- Extensibility (i.e., you can easily extend ANY application by just building on top of the modular architecture)
- Reusability (i.e., modules have independence and can therefore be shared and reused)
ColdBox Modules will change the way you approach application development as you can now have a foundation architecture that can scale easily and provide you enough manageability to reduce maintenance and increase development. Such a design means that development, testing and maintenance becomes easier, faster and with a much lower cost. Welcome to a brave new world!
Features
- Ability to build applications based on smaller manageable parts
- Ability to reload/unload all modules at runtime
- Ability to target specific modules to be loaded or unloaded from an application
- Ability to register or drop new modules at runtime
- New debugger panel to interact with modules
- A module can be complex as a full ColdBox application or as simple as one CFC
- Each module can have its own domain model, settings, and even SES routing
- Feature rich event model that can announce when modules are loaded and unloaded
- Ability to override module layouts and views from the host application
- Ability to switch the layout/view override flags, so either modules can take precedence or host application
- Built-in interceptors into the Module Configuration object
Configuration
There are a few settings or directives when dealing with modules and they are used in either the new programmatic ColdBox CFC or the coldbox.xml.cfm. In the programmatic approach, you must create a modules structure with some configuration settings. In the XML approach, you will create a <Modules> element with some configuration settings. Let's explore these settings:
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
| AutoReload | Boolean | false | false | Will auto reload the modules in each request. Great for development |
| Include | Array | false | empty array | An array or list of module names that should be loaded when the application starts up. If this setting is empty, it means that the framework will load ALL modules |
| Exclude | Array | false | empty array | An array or list of module names that should be EXCLUDED when the application starts up. If this setting is empty, it means that the framework will load ALL modules |
Programmatic
modules = {
autoReload = true,
include = [],
exclude = ["paidModule1","paidModule2"]
};
XML
<Modules> <AutoReload>true</AutoReload> <Include></Include> <Exclude>paidModule1,paidModule2</Exclude> </Modules>
Note : Please note that the Include and Exclude elements in the XML approach are simple lists instead of array notation in the programmatic configuration.
External Locations
You can also tell ColdBox to not only look in the conventions folder for your modules but anywhere in the server you like with the ColdBox directive: ModulesExternalLocation. This setting is an array of locations you want to tell ColdBox to look for modules. Each array element is the instantiation location which can use ColdFusion mappings or an absolute reference from the root of your application. Internally each of those entries will be expanded for you, so please be aware of this.
coldbox = {
.. all settings above
modulesExternalLocation = ["/shared/modules", "/codedepot/modules/customer1"]
};
In the example above, ColdBox will first register all the modules in the local application conventions (modules) and then look in order of declaration of the external locations. You might be asking yourself, what happens if there is another module in the external location with the same name as in another location? Or even in the conventions? Well, the first one found takes precedence. So if we have a module called funky in our conventions folder and also in our external locations, only the one in the conventions will load as it is discovered first.
With this approach you can very nicely create a lookup mechanism where you can put core modules or basic modules in the external locations and then very concrete or specific modules in the conventions.
Conventions
As we mentioned in our introduction, ColdBox Modules can be a full or subset of a ColdBox application. So lets investigate how to build them.
There is a new convention on your application folder structure called modules. This is the directory where you will drop or create named modules to be loaded by the application. This folder must exist within your application.
Changing The Conventions
Since the keyword modules is a convention of the host application, you can easily also rename it to whatever you like using the same approach as the other application conventions. Please refer to the configuration sections to learn how to customize the ColdBox Conventions.
Module Layout
So in order to create a module, you must first create a nicely named directory within the modules conventions directory. For example, let's build a simple hello world module. Create a folder called helloworld in the modules directory. This unique name is used in order to store the module settings and the basic entry points to the module using NON-SES URLs. So please remember that our preference and best practice is to ALWAYS use SES or URL Mappings in your applications.
The layout of a ColdBox Module can be almost all of it be optional except for one file: ModuleConfig.cfc. This is a simple CFC that boots up your module and tells the host application how your module is loaded, unloaded and behaves. Below are all the possible combinations of a module layout, you will notice that it is EXACTLY the same as a ColdBox application.
+Modules
+ {ModuleName - Unique}
+ ModuleConfig.cfc (The module configuration object Mandatory)
+ handlers (optional)
+ layouts (optional)
+ views (optional)
+ plugins (optional)
+ interceptors (optional)
+ model (optional)
As you can see, the only mandatory resources for a module is the directory name in which it lives and a ModuleConfig.cfc. The module developer can choose to implement a simple module or a very complex module. All folders are optional and only what is used will be loaded, but they must follow the ColdBox directory conventions. Not only are modules reusable and extensible, but you can easily create a module with dual functionality: A standalone application or a module. This is true reusability and flexibility. I don't know about you, but this is really exciting (Geek Alert!).
Changing The Module Layout
If you are picky and you would like to change the folder layout for your module, you can. This is achieved from within the ModuleConfig.cfc by adding a conventions structure that will explain to the ColdBox Module engine how to locate and configure your module. Below is the structure:
// Module Conventions conventions = { handlersLocation = "handlers", viewsLocation = "views", layoutsLocation = "layouts", pluginsLocation = "plugins", modelsLocation = "model" };
As you can see you can choose to rename the conventions for the module layout for handlers, plugins, views and layouts.
The Module Configuration Object
The module configuration object: ModuleConfig.cfc is the boot code of your module and where you can tell the host application how this module will behave. This component is a simple CFC, no inheritance needed, because it is decorated with methods and variables by ColdBox at runtime. The only requirement is that this object MUST exist in the root of the module folder. This is the way modules are discovered, so if there is no ModuleConfig.cfc in the root of that folder inside of the modules folder, the framework skips it. So let's investigate what we need or can do in this component.
Public Module Properties/Directives
First of all, we must define some public properties that can easily identify or provide directives for the module:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | true | --- | The title of the module |
| author | string | true | --- | The author of the module |
| webURL | string | true | --- | The web URL associated with this module. Maybe for documentation, blog, links, etc. |
| description | string | true | --- | A short description about the module |
| version | string | true | --- | The version of the module |
| viewParentLookup | boolean | false | true | If true, coldbox checks for views in the parent overrides first, then in the module. If false, coldbox checks for views in the module first, then the parent. |
| layoutParentLookup | boolean | false | true | If true, coldbox checks for layouts in the parent overrides first, then in the module. If false, coldbox checks for layouts in the module first, then the parent. |
| entryPoint | event or route | false | --- | This is the default event (ex: forgebox:manager.index) or the default route (ex:/forgebox) that ColdBox will use to create an entry link into the module. Similar to the default event setting. |
Below you can see an example of declaration for the configuration object:
component{
// Module Properties
this.title = "My Test Module";
this.author = "Luis Majano";
this.webURL = "http://www.coldbox.org";
this.description = "A funky test module";
this.version = "1.0";
// If true, looks for views in the parent first, if not found, then in the module. Else vice-versa
this.viewParentLookup = true;
// If true, looks for layouts in the parent first, if not found, then in module. Else vice-versa
this.layoutParentLookup = true;
}
The Decorated Variables
At runtime, the configuration object will be created by ColdBox and decorated with the following private properties (available in the variables scope):
- controller : A reference to the current ColdBox application controller
- appMapping : The appMapping setting of the current host application
- moduleMapping : The moduleMapping settig of the current module. This is the path needed in order to instantiate CFCs in the module. Similar to the appMapping setting.
- modulePath : The absolute path to the current loading module
- log : A pre-configured LogBox logger object for the configuration object
You can use any of these private variables to create module settings, load CFCs, etc.
The configure() method
Once the public properties are set, we are now ready to configure our module. You will do this by creating a simple method called configure() and adding variables to the following configuration structures:
- parentSettings : A structure of settings that will append and override the host application settings
- settings : A structure of settings that will only be available to the module. Please see the retrieving settings section
- conventions : A structure that explains the layout of the handlers, plugins, layouts and views of this module.
- datasources : A structure of datasource metadata that will append and override the host application datasources
- webservices : A structure of webservices metadata that will append and override the host application datasources
- interceptorSettings : A structure of settings for interceptor interactivity which includes the following sub-keys
- customInterceptionPoints : A string list of custom interception points to add to the application wide interceptor service
- interceptors : An array of declared interceptors that should be loaded in the entire application
- routes : An array of declared URL routes for this module. The keys of the structure are the same as the addRoute() method of the SES interceptor
- modelMappings : A structure of model mappings this module will append into the host application bean factory. Allowed keys are the alias and path, same as normal model mappings
It is important to note that there is only ONE running application and the modules basically leach on to it. So the following structures will basically append their contents into the running application settings: parentSettings, datasources, webservices, customInterceptionPoints, interceptors, model Mappings.
All of the configuration settings that are declared in your configuration object will be added to a key in the host application settings called modules. Inside of this structure all module configurations will be stored according to their module name and remember that the module name comes from the name of the directory on disk. So if you have a module called helloworld then the settings will be stored in the following location: ConfigSettings.modules.helloworld
Important: URL Mapping routes need to also be added at the host application level as order of entry is extremely important in SES URLs, so please refer to the configuring for SES below.
Below is an example of some settings:
function configure(){ // parent settings parentSettings = { woot = "Module set it!" }; // module settings - stored in the main configuration settings struct as modules.{moduleName}.settings settings = { display = "core" }; // Module Conventions conventions = { handlersLocation = "handlers", viewsLocation = "views", layoutsLocation = "layouts", pluginsLocation = "plugins", modelsLocation = "model" }; // datasources datasources = { mysite = {name="mySite", dbType="mysql", username="root", password="root"} }; // web services webservices = { google = "http://news.google.com/news?pz=1&cf=all&ned=us&hl=en&topic=h&num=3&output=rss" }; // SES Routes routes = [ {pattern="/api-docs", handler="api",action="index"} ]; // Interceptor Config interceptorSettings = { customInterceptionPoints = "onModuleError" }; interceptors = [ {name="modulesecurity",class="#moduleMapping#.interceptors.ModuleSecurity", properties={ URLMatch = '/api-docs', loginURL = '/api-docs/login' } ]; }
Interceptor Methods: onLoad(), onUnLoad(), etc
The module configuration object is also treated as a ColdBox Interceptor once it is created and configured. This means that the object itself can be registered on ALL of the framework's interception points by just creating the appropriate methods. Also, you have two special methods that you can create on the configuration object and they will come alive, much like Application.cfc event methods. These methods are:
- onLoad() : Called when the module is loaded and activated
- onUnLoad() : Called when the module is unloaded from memory
This gives you great hooks for you to do bootup and shutdown commands for this specific module. You can build a Drupal or Wordpress like application very easily all built in to the developing platform.
function onLoad(){ // Register some tables in my database and activate some features controller.getModel('MyModuleService').activate(); log.info('Module #this.title# loaded correctly'); } function onUnLoad(){ // Cleanup some stuff and logit controller.getModel('MyModuleService').shutdown(); // Log we unloaded log.info('My module unloaded successfully!); }
Also, remember that the configuration object itself is an interceptor so you can declare all of the framework's interception points in the configuration object and they will be registered as interceptors.
function preProcess(event, interceptData){ // I just intercepted ALL incoming requests to the application log.info('The event executed is #arguments.event.getCurrentEvent()"); }
This is a powerful feature, you can create an entire module based on a single CFC and just treat it as an interceptor. So get your brain into gear and let the gas run, you are going for a ride baby!
Module Event Executions
Module event executions are done almost exactly the same way we are used to in our ColdBox applications using event syntax patterns. By now we understand that an event comes in via the URL/FORM or Remotely to the framework and then the framework decides what event to execute. We also know that we can abstract our incoming events by using the ColdBox URL Mappings and SES support, but at the end of the day, we will always have an event variable in the request collection that tells the framework what event to execute. The typical event syntax pattern we have learned is:
// Pattern event=[package.]handler[.action] // Examples: event=blog.posts event=home.index event=admin.dashboard.index
This typical approach maps the event to a package, handler and method combination. With the addition of modules our event syntax pattern now morphs to the following:
// Pattern event=[module:][package.]handler[.action] // Examples: event=blog:posts.index event=cms:page.show event=admin:dashboard
As you can see, we can prefix the event syntax with a module name and then followed by a colon ({module}:). This is how ColdBox can know to what module or section to redirect the execution to. Please remember that this is great for securing your applications as the event patterns you can match against with regular expressions will help you tremendously, as you can pinpoint modules directly. You can even execute the runEvent() methods and target modules:
// Execute a viewlet in a module called viewlets: #runEvent('viewlets:users.dashboard')#
In summary, the event syntax has been updated to support module executions via the {module:} prefix. However, please note that our preference is to abstract URLs and incoming event variables (via FORM/URL) by using ColdBox URL Mappings. In the next section we will revise how to make module URL Mappings work.
SES-URL Mappings
SES Routing is the default routing and execution mechanisms for ColdBox. Creating URL Mappings has great benefits and incredible extensibility. If you are not familiar with them, please review our URL Mappings & SES section. We reviewed that you can have custom routes for each module, and that is fantastic. However, in order for the routes to become active you must add an entry point for them in the host application's SES Routing file: Routes.cfm. Why? Well, remember that routing is all about order, we must define our routes in order so they can be discovered. Thus, we need to manually go into our host application's Routes.cfm template and insert the module routes where we see fit. We do this by using a new method called addModuleRoutes().
- addModuleRoutes(pattern, module) : Insert the module routes at this location in the configuration file with the applied pattern.
The pattern is the normal URL Mappings pattern we are used to and the module is the name of the module you want to target. What this does is create an entry point pattern that will identify the module's routing capabilities. For example, we create the following:
addModuleRoutes(pattern="/blog",module="simpleblog"); addModuleRoutes(pattern="/admin",module="admin");
What the previous method calls do is bind a static URL entry pattern to a module. So if the framework detects an incoming URL with the starting point to be /blog, it will then match the simpleblog routes. Once matched, it will now try to match the rest of the incoming URL with the module's custom routes. Let's do a full example, below are some custom routes for my blog module in its ModuleConfig.cfc:
// In my ModuleConfig.cfc routes = [ {pattern="/", handler="blog", action="showPosts"}, {pattern="/:year-numeric/:month-numeric?", handler="blog", action="showPosts"} {pattern="/comment/:action", handler="comments"} ]
Now, let's say the URL that is incoming is:
http://mysite.com/blog
Then this will resolve to the /blog pattern in the host that says, now look in the module simpleblog for routes. The left over part of the URL is nothing or / so the pattern that matches is my first declared pattern:
{pattern="/", handler="blog", action="showPosts"}
This means, that we will execute the modules' blog handler and the showPosts method. Now, let's say the next URL that comes is:
http://mysite.com/blog/2009/09
Then this will match the simpleblog module via the static /blog entry point and then it tries to find a match for /2009/09 in the modules' routes and it does! So in conclusion, to enable module SES or URL Mappings you must do two things:
- . Define your routes in the ModuleConfig configuration object
- . Add the entry point for the module routes wherever you see fit in the host application's Routes.cfm configuration file by using the addModuleRoutes() method.
Retrieving & Interacting With Module Settings
We now understand that the modules configurations are stored in the host parent configuration structure under the modules key. You can do a very hefty dot notation to retrieve module settings but we have created a shorthand method that is available in the framework supertype and thus available in all handlers, plugins, interceptors, layouts and views:
- getModuleSettings( module ) : Returns the structure of module settings by the module name.
That's it, one simple method to help you shortcut your way to module settings.
Request Context Module Methods
The request context object (event parameter received in handlers/layouts/views) has also been expanded to have the following module methods:
- getCurrentModule() : Returns the module name of the currently executing module event.
- getModuleRoot() : Returns the web accessible root to the modules root directory.
The last method is what is really interesting when building visual modules that need assets from within the module itself. You can easily target the web-accessible path to the module by using the getModuleRoot() method. Below are some examples:
<head> <meta name="author" content="Luis Majano" /> <meta http-equiv="content-type" content="text/html;charset=utf-8" /> <---< Base HREF if using SES ---> <cfif event.isSES()> <base href="#getSetting('htmlBaseURL')#"> </cfif> <---<Module CSS ---> <link rel="stylesheet" href="#event.getModuleRoot()#/includes/css/style.css" type="text/css" /> <link rel="stylesheet" href="#event.getModuleRoot()#/includes/js/ratings/jquery.ratings.css" type="text/css" /> <---< Module Javascript ---> <script type="text/javascript" src="#event.getModuleRoot()#/includes/js/jquery-latest.pack.js"></script> <script type="text/javascript" src="#event.getModuleRoot()#/includes/js/forgebox.js"></script> <script type="text/javascript" src="#event.getModuleRoot()#/includes/js/jquery.simplemodal-latest.min.js"></script> <script type="text/javascript" src="#event.getModuleRoot()#/includes/js/jquery.uidivfilter.js"></script> <script type="text/javascript" src="#event.getModuleRoot()#/includes/js/ratings/jquery.ratings.pack.js"></script> <title>The Awesome ForgeBox Module!</title> </head>
As you can see, this is essential when building module UIs or layouts.
Layout and View Renderings
The ColdBox rendering engine has been adapted to support module renderings and also to support multiple discovery algorithms when rendering module layouts and views. When you declare a module you can declare two of its public properties to determine how rendering overrides occur. These properties are:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| viewParentLookup | boolean | false | true | If true, coldbox checks for views in the parent overrides first, then in the module. If false, coldbox checks for views in the module first, then the parent. |
| layoutParentLookup | boolean | false | true | If true, coldbox checks for layouts in the parent overrides first, then in the module. If false, coldbox checks for layouts in the module first, then the parent. |
The default order of overrides ColdBox offers is both viewParentLookup & layoutParentLookup to true. This means that if the layout or view requested to be rendered by a module exists in the overrides section of the host application, then the host application's layout or view will be rendered instead. Let's do some real examples, I am building a simple module with 1 layout and 1 view. Here is my directory structure for them:
/simpleModule
+ layouts
+ main.cfm
+ views
+ simple
+ index.cfm
Now, in my handler code I just want to render the view by using our typical event.setView() method calls or implicit views.
// In a handler called simple.cfc component{ function index(event){ // DO SOME CODE HERE // Set the view to render with a layout event.setView(view='simple/index',layout="main"); } }
Overriding Views
This tells the framework to render the simple/index.cfm view in the module simpleModule. However, let's override the view first. Go to the host application's views folder and create a folder called modules and then a folder according to the module name, in our case simpleModule:
/MyApp
+ views
+ modules
+ simpleModule
What this does, is create the override structure that you need. So now, you can map 1-1 directly from this overrides folder to the module's views folder. So whatever view you place here that matches 1-1 to the module, the parent will take precedence. Now remember this is because our viewParentLookup property is set to true. If we disable it or set it to false, then the module takes precedence first. If not found in the module, then it goes to the parent or host application and tries to locate the view. That's it, so if I place a simple/index.cfm view in my parent structure, the that one will take precedence.
/MyApp
+ views
+ modules
+ simpleModule
+ simple
+ index.cfm
Overriding Layouts
Now, let's say you want to override the layout for a module. Go to the host application's layouts folder and create a folder called modules and then a folder according to the module name, in our case simpleModule:
/MyApp
+ layouts
+ modules
+ simpleModule
What this does, is create the override structure that you need. So now, you can map 1-1 directly from this overrides folder to the module's layouts folder. So whatever layout you place here that matches 1-1 to the module, the parent will take precedence. Now remember this is because our layouParentLookup property is set to true. If we disable it or set it to false, then the module takes precedence first. If not found in the module, then it goes to the parent or host application and tries to locate the layout. That's it, so if I place a main.cfm layout in my parent structure, the that one will take precedence.
/MyApp
+ layouts
+ modules
+ simpleModule
+ main.cfm
These features are great for skinning modules or just overriding view capabilities a-la-carte. So please take note of them as it is very powerful.
Explicit Module Renderings
You can also explicitly render layouts or views directly from a module via the Renderer plugin's renderLayout() and renderView() methods. These methods now can take an extra argument called module.
- renderLayout(layout, view, module)
- renderView(view, cache, cacheTimeout,cacheLastAccessTimeout,cacheSuffix, module)
So you can easily pinpoint renderings if needed.
Important: Please note that whenever these methods are called, the override algorithms ALSO are in effect. So please refer back to the view and layout parent lookup properties in your modules' configuration.
Model Integration
When you declare a module and you define a model folder then the framework will register that folder as an external location to the main parent's model integration bean factory. This means, that whatever module object you place in the module's model folder will be available application wide. Not only that, but you can specifically create model mappings for module by using the modelMappings structure in your configuration object. We suggest trying to namespace your mappings or having unique names so when you wire them up you can easily distinguish where they are coming from. Below are our forgebox module's model mappings:
modelMappings = {
"forgeService@forgebox" = {
path = "ForgeService"
}
};
- Each key is the alias for a model object. The contents of the key is another structure that can hold (for now) the following keys:
- path : The path from the modules' model folder of where the CFC is located
- alias : An optional list of further aliases you can give this CFC
(* More keys will be added as BlenderBox is finalized)
Here is a sample of autowiring my model objects from my module in my module's handlers:
<--- dependencies ---> <cfproperty name="forgeService" inject="model:forgeService@forgeBox">
This tells the beanfactory to create a model object named forgeService@forgeBox. The bean factory then tries to resolve that alias to a CFC, create it, wire it and bring it back.
Module Plugins
Module plugins are great to build. Not only that but you can easily wire them anywhere in your applications by using the enhanced DSL for plugin autowiring:
- coldbox:myplugin:{name}{@module}
This tells the autowiring DSL to go get a custom plugin with the {name} but located (@) the {module}. Of course, this DSL is only for autowiring or wiring declarations. You can also explicitly get a module plugin by using our favorite plugin method: getMyPlugin() or getPlugin():
- getMyPlugin(name, newInstance, module) : Retrieve a custom plugin.
Note : Please note that all getMyPlugin() and getPlugin() methods on the ColdBox Factory and ColdBox Remote Proxy have been updated also to reflect the module parameter.
// Wire in a plugin from the pluginslib module property name="fileUtils" inject="coldbox:myPlugin:fileUtils@pluginLib"; // Use the fileUtils from the pluginLib module to read a file contents = getMyPlugin(name="fileUtils",module="pluginLib").readFile( filePath );
Module Service
The beauty of ColdBox Modules is that you have an internal module service that you can tap to in order to dynamically interact with the ColdBox Modules. This service is available by talking to the main ColdBox controller and calling its getModuleService() method:
// get module service from handlers, plugins, layouts, interceptors or views. ms = controller.getModuleService(); // You can also inject it via our autowire DSL property name="moduleService" inject="coldbox:moduleService";
However, before we start reviewing the module service methods let's review how modules get loaded in a ColdBox application. Below is a simple bullet point of what happens in your application when it starts up:
- ColdBox main application and configuration loads
- ColdBox Cache, Logging and Bean Factories are created
- Module Service calls on registerAllModules() to read all the modules in the modules location and start reading their configurations one by one
- All main application interceptors are loaded and configured
- afterConfigurationLoad interceptors are fired
- ColdBox aspects such as i18n, javaloader, ColdSpring/LightWire factories are loaded
- Module service calls on activateAllModules() so it begins activating the modules one by one. This registers the module's SES URL Mappings, model mappings, etc
- afterAspectsLoad interceptors are fired
The most common methods that you can use to control the modules in your application are the following:
- reloadAll() : Reload all modules in the application. This clears out all module settings, re-registers from disk, re-configures them and activates them
- reload(module) : Target a module reload by name
- unloadAll() : Unload all modules
- unload(module) : Target a module unload by name
- registerAllModules() : Registers all module configurations
- registerModule(module) : Target a module configuration registration
- activateAllModules() : Activate all registered modules
- activateModule(module) : Target activate a module that has been registered already
- getLoadedModules() : Get an array of loaded module names
With these methods you can get creative and target the reloading, unloading or loading of specific modules. These methods really open the opportunity to build an abstraction API where you can install modules in your application on the fly and then register and activate them. You can also do the inverse and deactivate modules in a running application.
Manithan said
at 10:44:47 PM 24-Feb-2010
Luis Majano said
at 10:56:10 AM 25-Feb-2010
Glyn Jackson said
at 05:18:45 PM 04-Apr-2010
Daniel Schmid said
at 03:39:33 AM 05-Apr-2010
SideBar
User Login
Comments (