Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Will Belden
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Will Belden

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

By
Published in Comments (1)

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.

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

Reader Comments

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