Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Heather Metcalf and Faith Chouteau
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Heather Metcalf Faith Chouteau

Collocating My ColdFusion, CSS, And JavaScript Files

By
Published in , , Comments (2)

When building a ColdFusion multi-page application (MPA), I've never been satisfied with how files are organized. Client-side files (CSS and JS) that are tightly coupled to server-side files (CFML) are often located in completely different parts of the application's folder structure. This adds friction to the maintenance of the ColdFusion application. In an effort to experiment with a more cohesive file strategy, I want to try putting CSS and JavaScript files right next to their CFML counterparts.

What this means is that each ColdFusion interface may have four different files working together to create the user experience (UX). A login form, for example, may have the following files:

  • form.cfm - the "controller" template.
  • form.view.cfm - the "view" template.
  • form.view.less - the CSS file for the view.
  • form.view.js - the JavaScript file for the view.

This may feel verbose; but, it's really no different than the number of files one might normally have; only, the files are right next to each other rather than spread across various server and client focused folders.

If you're coming from the Angular world, this should feel very familiar as Angular components use this same multi-file collocation technique. For example, a form component might entail the following files:

  • form.component.ts - the "controller" code (which, in Angular, also acts as the JavaScript file for the view).
  • form.component.html - the "view" template.
  • form.component.less - the CSS file for the view.

Essentially, I'm trying to get my ColdFusion multi-page application (MPA) file organization to look more like my Angular single-page application (SPA) file organization. The collocation of files has proven to be very powerful in the SPA world; and, I have no doubt that it will be equally powerful in the MPA world.

Of course, in the MPA world you don't have all of the same developer ergonomics that you have in the SPA world. Which means, I'm going to be manually minding the gap in this exploration. Specifically, instead of getting my emulated CSS scoping from the JavaScript compiler, I'm going to be manually adding some random attributes to my CFML markup that my Less CSS can hook into.

I don't love the idea of doing this manually; but, I also don't want to let the perfect become the enemy of the good. I'd rather explore the space with some manual labor than waste time trying to figure out how to programmatically add CSS scoping.

To this end, I created a random slug generator utility that allows me to randomly generate a 6-character token. This token will then act as a namespace in two ways:

  1. It will be added as an attribute to all HTML elements that also need to have a scoped CSS class.

  2. It will be added to the global JavaScript scope as an object in which to define Alpine.js controllers.

Take the slug, 784nz3, as an example. In my CFML file, I'll use this to both define x-data attributes and to scope CSS classes:

<form
	x-data="m784nz3.FormController"
	m-784nz3
	class="form">

	<button type="submit" m-784nz3 class="submit">
		Submit Form
	</button>
</form>

Note: I'm prefixing the slug with m to ensure that it always starts with a letter (in order to ensure a proper JavaScript variable name). I probably should just make that a constraint of the slug generator.

My collocated Less CSS file will then define a block for this attribute, [m-784nz3]; and all CSS classes will be scoped under it using the &. operator:

[m-784nz3] {
	&.form {
		// ... scoped form styles ...
	}

	&.submit {
		// ... scoped button styles ...
	}
}

This gets compiled down into CSS that looks like [m-784nz3].form and [m-784nz3].submit, ensuring that these CSS classes don't affect any other user interface elements accidentally.

My collocated JavaScript file then performs two jobs: it includes the .less file (a feature of the Parcel JS bundler that I'm using); and, it defines the Apline.js component(s) for the view:

// Import and emit the collocated Less CSS file.
import "./form.view.less";

// Make Alpine JS component available on global scope under unique slug.
window.m784nz3 = {
	FormController
};

function FormController() {

	if ( window.location.hash ) {

		document.querySelector( "input[ name = 'redirectTo' ]" )
			.value += window.location.hash
		;

	}

}

The unique and random slug, 784nz3, is now effectively scoping both the CSS and the JavaScript for the collocated set of files that holistically represent a view within my ColdFusion application.

I then need a root JavaScript file to pull the whole system together into a compiled, bundled set of resources. This is what my main.js file looks like—it globs (**/*) over the folder structure looking for all *.view.js files:

// Import vendor modules.
import Alpine from "alpinejs";

// Import app modules.
import "../cfml-main/views/**/*.view.js";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

window.Alpine = Alpine;

Alpine.start();

For every .view.js file that gets included by the above import, the relevant .less is also included (since it gets included as a module by each view-specific JavaScript file). This ultimately traverses the whole ColdFusion user-facing folder structure and compiles all of the Less CSS and JavaScript files into a .css and .js file, respectively, that I can then include in my layout rendering.

Using this approach, if I have to modify the look and feel or behavior of the form, all of the relevant files are right there—there's no confusion about whether or not I'm looking in the right place; or, if the relevant files already exist. And, if I need to delete the form from the application, I can easily delete all four collocated files and have no concern about any crufty, left-over CSS or JavaScript files being left accidentally in the compiled code.

This makes the code easy to find and easy to delete. Which are two foundational principles underpinning my theories on clean code architecture.

Again, I don't love the idea of having to manually generate tokens and apply them to my CFML markup. But, I'm not going to let a little extra work block this experiment—an experiment that has already made my development life easier.

You can see this in action in my Feature Flags playground app. If you look at the "feature targeting" view, for example, you'll see four files that work together to create the user experience.

So far, this is working well in my feature flags app. And, if this continues to feel right, I'll try to retrofit this approach into my blog as well (where I do much of my "production ready" experimentation). And, if anyone has any suggestions, I'm all ears. It would seem that this is a "solved" problem; but, it also seems that most people only have solid solutions for SPA architectures; and that MPA architectures are still a little "wild West" in terms of file organization and scoping.

Want to use code from this post? Check out the license.

Reader Comments

19 Comments

This is either going to make people laugh or tick them off. Either way, I'm going for it.

Ahem ...

Hey! Aren't those called "fuses"?

15,825 Comments

@JC,

Ha ha, I mean, when you boil it down, all apps have to render views. Each framework is going to call it differently. Way way way back when I was learning about Fusebox, I'm sure that we even had a concept of JavaScript / Less CSS build systems. So, yes, in a sense I'm trying to figure out how to take the simplicity of that kind of an app but modernize it a bit for ease of full-stack development.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel