ColdBox modules have revolutionized the way that CFML developers can reuse and organize code.  Modules follow an HMVC, or Hierarchical MVC pattern that allows you to not only break apart your CFML app into smaller chunks, but to stack those pieces into a hierarchy that really makes sense of your code.  We also call this module inceptions-- the act of nesting modules inside of each other.  

So, a question came across the ColdBox Google group today asking about how to access events from within nested modules and how that manifests itself in the URL.  Before I responded, I spun up a quick site in CommandBox to test and I found to my dismay that the answer was very difficult to find in our docs.  As such, I figured a quick blog post was in order since it's fairly easy to set up if you know what to do.

Module Routing

First, a quick review of how module routes work.  Let's create a quick site in CommandBox to test with (feel free to follow along locally).

CommandBox> mkdir moduleRouting --cd
CommandBox> coldbox create app
CommandBox> coldbox create module admin
CommandBox> server start --rewritesEnable

Ok, in a few seconds a browser should pop up with your fresh ColdBox site.  We've created a simple module called "admin".  There's two ways you can hit the admin module (Add in the port for your server):

  • http://localhost/index.cfm?event=admin:home.index
  • http://localhost/admin/home/index

The latter is what we're going to focus on.  It uses SES URLs and the URL rewriting inherent in CommandBox to produce a nice, clean URL.  In fact, you can shorten that last URL down to http://localhost/admin which uses the conventions and defaults inside the framework to default the home an index part.  That default route of "/admin" comes from the admin module's ModuleConfig.cfc where it says 

this.entryPoint = "admin";

This can really be anything, but it default to the name of the module.

Nested Modules

Ok, let's throw an "orders" module inside of our "admin" module.  We'll assume the administrator portion of your site has a subsection to handle orders.

CommandBox> cd modules_app/admin
CommandBox> coldbox create module name=orders directory=modules
CommandBox> cd ../../
CommandBox> coldbox reinit

Ok, so now we have this setup:

Your ColdBox:
 +- Admin module
    +- Orders module

So, how do we access the default event from our nested "orders" module in the url?  Well, by default the nested module's entry point is simply going to be "orders" which means the default URLs look like this:

http://localhost/orders

But what if we want to clean that up and show the same nesting in the URL that matches our module hierarchy?  There's two ways to approach this.

Override the entry point for the nested module

Ok, so let's open up the ModuleConfig.cfc from our nested orders module in your default editor:

CommandBox> edit modules_app/admin/modules/orders/ModuleConfig.cfc

Find the entry point defined before the configure method and change it like so:

// Module Entry Point
this.entryPoint = "admin/orders";

Now, issue another ColdBox reinit, and the following URL should take you to your orders module homepage:

http://localhost/admin/orders

Add module routing in the parent

Ok, the above method works great if you have control over all the code.  What if the "orders" module was something installed by CommandBox as a 3rd party dependency and you don't want to or simply cannot edit the code. No worries, our "admin" module can declare a passthrough route that tells ColdBox that we want to point to the nested module under a certain routing namespace.  Let's open up the parent admin module's config:

CommandBox> edit modules_app/admin/ModuleConfig.cfc

Find the array of routing info and add a new line to it:

// SES Routes
routes = [
    // Module Entry Point
    { pattern="/", handler="home", action="index" },
    // Passthrough route for nested module
    { pattern="/orders, moduleRouting="orders" },
    // Convention Route
    { pattern="/:handler/:action?" }
];

Ok, so we added the moduleRouting bit.  The pattern is whatever URL placeholder you want to use and is automatically appended to the entry point of the admin module.  The moduleRouting key points to the real name of the nested module that we want to route the pattern to. This gives us the same result as the previous method, but we never have to touch the nested module's code.

http://localhost/admin/orders

And in case you're wondering-- the events inside the nested module just append to the URL like you would expect.  Basically, any convention routes, or custom routes in your inner module just append themselves onto the end of the pattern above.

http://localhost/admin/orders/home
http://localhost/admin/orders/home/index

Clean up, aisle 7

OK, our work here is done.  If you followed along at home, congrats.  Let's pretend this never happened and get back to work!

CommandBox> server stop --forget
CommandBox> cd ../ && rm moduleRouting --recurse --force