Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Erik Meier and Jesse Shaffer and Bob Gray
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Erik Meier Jesse Shaffer Bob Gray

Using getApplicationMetadata() To Provide Global Defaults In ColdFusion

By
Published in Comments (2)

A couple of weeks ago on Twitter, James Moberg and I were discussing ColdFusion application metadata. In recent years, ColdFusion has provided a getApplicationMetadata() function for retrieving the configuration associated with the current request (remember, the Application.cfc framework component is freshly instantiated on every single request). I've never used the getApplicationMetadata() function before; but, after my previous post on the ColdFusion custom tags, it occurred to me that I could use the ColdFusion application framework as a hook into providing global defaults for my custom tag attributes.

The Application.cfc has traditionally been used to configure the built-in ColdFusion features: application timeouts, session management, data-sources, mail servers, Java settings, etc. But, there's no reason that we can't use the Application.cfc to configure our own custom-built functionality. After all, the getApplicationMetadata() function returns everything in the this scope (of the application component). As such, as long as we define our custom configuration within the this scope, there's no reason that we can't consume this data lower down in the request processing.

To explore this concept, I'm going to create a ColdFusion custom tag that transforms its generated content based on several optional attributes:

  • trimming - I determine which whitespace trimming function should be applied. Options are: trim, ltrim, rtrim.

  • casing - I determine which letter-casing function should be applied. Options are: lcase, ucase, ucfirst, party.

  • whitespace - I determine whether or not the remaining whitespace should be replaced with visible characters (. for space, - for tab, + for newline). Options are: visible.

All of these attributes will have defaults defined within the custom tag logic. But, I'm also going to allow global defaults to be defined within the Application.cfc component using the public property: transformerAttributeDefaults. For example, here's the Application.cfc for this demo:

component
	output = false
	hint = "I define the application settings and event handlers."
	{

	this.name = "GlobalDefaultsDemo";
	this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 );
	this.sessionManagement = false;
	this.setClientCookies = false;

	// Override (some of) the default attributes for the Transformer.cfm custom tag.
	this.transformerAttributeDefaults = {
		casing: "party"
	};

}

Now, from anywhere within the request processing, I can access these settings by calling:

getApplicationMetadata().transformerAttributeDefaults

Of course, these are only meant as defaults. Which means, if the developer passes-in attributes for a particular custom tag invocation, the invocation instance values should win. The order of precedence therefore becomes:

  1. (lowest) Internal custom tag defaults.

  2. (middlest) Global custom tag default overrides (if they exist).

  3. (highest) Per-instance attribute assignments.

In my custom tag, I can wire this up as such (truncated example):

<cfscript>

	tagDefaults = applyGlobalDefaultOverrides({
		trimming: "trim",
		casing: "lcase",
		whitespace: "hidden"
	});

	// Define tag attributes and defaults.
	param name="attributes.variable" type="variableName";
	param name="attributes.trimming" type="string" default = tagDefaults.trimming;
	param name="attributes.casing" type="string" default = tagDefaults.casing;
	param name="attributes.whitespace" type="string" default = tagDefaults.whitespace;

	// ... truncated custom tag code ... //

	/**
	* If the application has overridden the tag attribute defaults, I apply them to the
	* given defaults structure before returning it.
	*/
	private struct function applyGlobalDefaultOverrides( required struct defaults ) {

		var appMetadata = getApplicationMetadata();
		var appMetadataKey = "transformerAttributeDefaults";

		if ( appMetadata.keyExists( appMetadataKey ) ) {

			defaults.append( appMetadata[ appMetadataKey] );

		}

		return defaults;

	}

</cfscript>

Here, I'm defining applyGlobalDefaultOverrides(), a private method local to my ColdFusion custom tag. This method takes the internal default values (lowest precedence) and overrides those defaults using the getApplicationMetadata() function. The resultant struct is then used to define the defaults within the module's subsequent CFParam tags. This allows the per-invocation attributes to ultimately take the highest precedence.

If we then invoke this ColdFusion custom tag:

<cf_Transformer variable="request.text" whitespace="visible">

	This is Sparta!

</cf_Transformer>

<cfoutput>
	<pre>[#encodeForHtml( request.text )#]</pre>
</cfoutput>

... we end up invoking the Transformer.cfm module with the following attributes:

  • whitespace="visible" - highest precedence value provided by the tag invocation code.

  • casing="party" - middlest precedence value provided by the global defaults in our Application.cfc settings.

  • trimming="trim" - lowest precedence value provided by the internal defaults of the custom tag.

Now, if we run this ColdFusion code, we get the following page output:

[ThIs.Is.SpArTa!]

It was fully trimmed; the whitespace was made visible; and, the party casing was applied. All of the various attribute inputs were coalesced correctly into a single invocation configuration.

Here's the full code for the ColdFusion custom tag:

<cfscript>

	// Since this ColdFusion custom tag deals with generated output, we only care about
	// the tag in its "end" mode once we have generated content to consume.
	if ( thistag.executionMode == "start" ) {

		exit
			method = "exitTemplate"
		;

	}

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

	tagDefaults = applyGlobalDefaultOverrides({
		trimming: "trim",
		casing: "lcase",
		whitespace: "hidden"
	});

	// Define tag attributes and defaults.
	param name="attributes.variable" type="variableName";
	param name="attributes.trimming" type="string" default = tagDefaults.trimming;
	param name="attributes.casing" type="string" default = tagDefaults.casing;
	param name="attributes.whitespace" type="string" default = tagDefaults.whitespace;

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

	switch ( attributes.trimming ) {
		case "trim":
			thistag.generatedContent = thistag.generatedContent.trim();
		break;
		case "ltrim":
			thistag.generatedContent = thistag.generatedContent.ltrim();
		break;
		case "rtrim":
			thistag.generatedContent = thistag.generatedContent.rtrim();
		break;
	}

	switch ( attributes.casing ) {
		case "ucase":
			thistag.generatedContent = thistag.generatedContent.ucase();
		break;
		case "lcase":
			thistag.generatedContent = thistag.generatedContent.lcase();
		break;
		case "ucfirst":
			thistag.generatedContent = thistag.generatedContent.ucfirst();
		break;
		case "party":
			thistag.generatedContent = thistag.generatedContent
				.reReplace( "(\w)(\w)", "\U\1\L\2", "all" )
			;
		break;
	}

	if ( attributes.whitespace == "visible" ) {

		thistag.generatedContent = thistag.generatedContent
			.reReplace( " ", ".", "all" )
			.reReplace( "\t", "-", "all" )
			.reReplace( "(\r\n?|\n)", "+\1", "all" )
		;

	}

	// Write the generated content to the calling scope and reset the output.
	// --
	// NOTE: In Adobe ColdFusion, we could have referenced the caller scope like a
	// "magic tunnel" into the calling context. However, Lucee CFML doesn't expose the
	// calling context in this manner. As such, we're going to use setVariable() to allow
	// for a proper variable path rooted in the calling page scope.
	setVariable( "caller.#attributes.variable#", thistag.generatedContent );
	// Reset the output.
	thistag.generatedContent = "";

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

	/**
	* If the application has overridden the tag attribute defaults, I apply them to the
	* given defaults structure before returning it.
	*/
	private struct function applyGlobalDefaultOverrides( required struct defaults ) {

		var appMetadata = getApplicationMetadata();
		var appMetadataKey = "transformerAttributeDefaults";

		if ( appMetadata.keyExists( appMetadataKey ) ) {

			defaults.append( appMetadata[ appMetadataKey] );

		}

		return defaults;

	}

</cfscript>

In this exploration, I'm using this as a means to provide ColdFusion custom tag settings. But, there's no reason that this couldn't be used to provide settings for ColdFusion components or even User Defined Functions (UDFs).

That said, whenever you define global behaviors, you have to be cautious. For one thing, it puts a large distance in between the configuration code and the consuming code. This can make the code harder to understand and debug. And, for another thing, it might change the behavior of vendor code in an unexpected way (though, this is more relevant for native ColdFusion settings, such as useJavaAsRegexEngine, which determines how Regular Expressions are evaluated).

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

Reader Comments

15,728 Comments

I should mention that - over on Twitter - Zac Spitzer mentioned that the getApplicationMetadata() has some overhead associated with it. I don't know what the magnitude of that would be; but, he's very familiar with how Lucee CFML is implemented.

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