How Can I Use an External Layout Within a Module?

I have two modules side by side like this:
/modules_app/admin/
/modules_app/leads/

The leads module has an entryPoint of /admin/leads.

I’d like the leads module to utilize the default layout (Main.cfm) from the admin module. I know there’s a Coldbox setting called modulesExternalLocation (documentation) but I’m not sure how to have my ModuleConfig override this setting.

Here’s what I’ve tried putting in my configure() method:

layoutsExternalLocation = "/admin/layouts/";

I have also tried:

layoutSettings = {
    layoutsExternalLocation = "/admin/layouts/",
    defaultLayout = "Main.cfm"
};

However, no matter what combination I try, I keep seeing the root /layouts/Main.cfm layout appear instead of the admin module’s Main.cfm layout (/modules_app/admin/layouts/Main.cfm).

Thanks for your help!

– Edited Post - updated 8/27/2021

You can’t use the Coldbox setting layoutsExternalLocation from within a module. The Coldbox router will always look inside the current module for a layout.
Source: coldbox\system\web\Renderer.cfc line 713

One way to have the module leads default to the layout of module admin is to specify the layout at the handler level. You have a couple of different ways of implementing this solution, but here are some simple examples:

Example 1:
Within the leads module, create a baseHandler that all your other handlers in this module will extend. Then specify the default that should be used using one of the built-in implicit handler methods like aroundHandler().

// this method automatically executes around every handler method call
function aroundHandler( event, targetAction, eventArguments, rc, prc ) {
    
    // set the default layout to our desired module
    event.setLayout( 
        name="main",
        module="admin" 
    );

    // prepare arguments for action call
    var args = {
        event = arguments.event,
        rc    = arguments.rc,
        prc   = arguments.prc
    };

    structAppend( args, eventArguments );

    // execute the action now
    return arguments.targetAction( argumentCollection=args );

}

Example 2:
If you’re trying to build a “plug-in” architecture and you want to create a bunch of modules to extend the functionality of some type of core module, you could explicitly set the default layout in a base handler in the core module. You would then have each “plug-in” module’s handlers extend the base handler from the core. In my example, I specify the layout in the admin module and then the leads module handlers all extend from the admin base handler.

// BaseHandler.cfc in the 'admin' module
component 
    hint="I am the base handler for the core 'admin' module"
{

    // this method automatically executes around every handler method call
    function aroundHandler( event, targetAction, eventArguments, rc, prc ) {
        
        // set the default layout to this module
        event.setLayout( 
            name="main",
            module="admin" 
        );

        // prepare arguments for action call
        var args = {
            event = arguments.event,
            rc    = arguments.rc,
            prc   = arguments.prc
        };

        structAppend( args, eventArguments );

        // execute the action now
        return arguments.targetAction( argumentCollection=args );

    }

}

// Home.cfc in the in the 'leads' module
component 
    extends="admin.BaseHandler"
    hint="I handle lead requests. I extend the admin module's base handler"
{

    function index( event, rc, prc ) {
        // ... etc.
    }

}

Hopefully, this will help someone else out as well. I’m thinking I might write a little tutorial at some point on how to create a plug-in architecture for a Coldbox app. I could see it being extremely useful for someone that finds they always need a generic “admin” section for a bunch of different apps, but the specific functionality for each “admin” section varies from app to app.

1 Like