Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Collecting HTML Class Name Attributes In Template Rendering In Lucee CFML 5.3.7.47

By Ben Nadel on
Tags: ColdFusion

The other day, I was updating a CFML template to include some conditional CSS class names in an ordered list. And, by the time I was done, the CFML looked a hot mess with several ternary operators all being interpolated into one class="" attribute. And, as I sat there, wallowing in the shame of such ugly looking code, it hit me like a bolt of lightening: Angular already solved this problem so elegantly with the NgClass directive that applies dynamic class names based on a set of conditionals. The same exactly thing should be quite doable in Lucee CFML 5.3.7.47.

To give you a sense of the shame that I was feeling, I ended up with an LI element that looked something like this:

<cfloop index="local.scheduleDay" array="#rc.scheduleDays#">
	<li class="m-schedule__item #( scheduleDay.isActiveDeployment ? 'm-schedule__item--deployment' : '' )# #( scheduleDay.isToday ? 'm-schedule__item--today' : '' )# #( scheduleDay.isFuture ? 'm-schedule__item--future' : '' )# #( scheduleDay.isBlocked ? 'm-schedule__item--blocked' : '' )#">

		<!--- truncated --->

	</li>
</cfloop>

As you can see, the class attribute on my list-item has one base class name and then a number of other class names that are applied conditionally. Well, I say "as you can see", but the reality is, this is terribly unreadable!

Now, on the Angular side of the world, they solved this with problem a decade ago with the NgClass directive which accepts (among other things) an Object in which the keys represent class names and the values determine which class names are included. So, the above CFML mess could be represented in an Angular app as follows:

<li [ngClass]="{
		'm-schedule__item': true,
		'm-schedule__item--deployment': scheduleDay.isActiveDeployment,
		'm-schedule__item--today': scheduleDay.isToday,
		'm-schedule__item--future': scheduleDay.isFuture,
		'm-schedule__item--blocked': scheduleDay.isBlocked
	}">
	<!-- truncated -->
</li>

This is 1000-times more readable than the soup of ternaries that I have in my ColdFusion. And, it's totally something that we can do in ColdFusion as well. All we have to do is create a function that takes a Struct and returns a list of keys whose values are "truthy". Here's what that might look like in Lucee - I'm calling the function encodeClassAttribute():

<cfscript>

	contacts = [
		{ id: 1, name: "Joe", isBFF: true, isFriend: true },
		{ id: 2, name: "Sam", isBFF: false, isFriend: true },
		{ id: 3, name: "Kit", isBFF: false, isFriend: false },
		{ id: 4, name: "Ryan", isBFF: false, isFriend: false },
		{ id: 5, name: "Alex", isBFF: true, isFriend: true },
		{ id: 6, name: "Jordan", isBFF: false, isFriend: true }
	];

	```
	<cfoutput>
		<ul>
			<cfloop value="contact" array="#contacts#">

				<li class="#encodeClassAttribute([
						contact: true,
						'contact--friend': contact.isFriend,
						'contact--bff': contact.isBFF
					])#">
					#encodeForHtml( contact.name )#
				</li>

			</cfloop>
		</ul>
	</cfoutput>
	```

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

	/**
	* I collect the list of CSS class-names to apply to a given context. The list will
	* include every key whose value is a Truthy.
	* 
	* @conditions I am the key/value pairs being inspected.
	*/
	public string function encodeClassAttribute( required struct conditions ) {

		var classNames = [];

		loop
			key = "local.className"
			value = "local.condition"
			struct = conditions
			{

			if ( isTruthy( condition ?: false ) ) {

				classNames.append( className );

			}

		}

		return( classNames.toList( " " ) );

	}


	/**
	* I determine if the given value is a Truthy.
	* 
	* @value I am the value being inspected.
	*/
	public boolean function isTruthy( any value ) {

		return( ! isFalsy( value ) );

	}


	/**
	* I determine if the given value is a Falsy.
	* 
	* @value I am the value being inspected.
	*/
	public boolean function isFalsy( any value ) {

		if ( isNull( value ) ) {

			return( true );

		}

		if ( ! isSimpleValue( value ) ) {

			return( false );

		}

		if ( isBoolean( value ) || isNumeric( value ) ) {

			return( ! value );

		}

		return( value == "" );

	}

</cfscript>

As you can see, we loop over the key-values pairs in the passed-in Struct; and, we collect every key whose value passes the isTruthy() call. This gives us a collection of class names which we then serialize as a space-delimited list. And, when we run this ColdFusion code, we get the following HTML:

HTML with conditional CSS class names rendered in Lucee CFML.

As you can see, each LI element has a class attribute that contains only the class names with the relevant truthy values.

This approach feels so clean to me, I'm frankly shocked that it's taken me close to a decade of using Angular before I realized that the same approach can be applied in ColdFusion template rendering. The trick will be figuring out how to take this function and make it generally available to my Lucee CFML templates.


Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.