Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created March 15, 2023 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/6e89243d1f17a63d8fd2a2e0a5f923ba to your computer and use it in GitHub Desktop.
Save bennadel/6e89243d1f17a63d8fd2a2e0a5f923ba to your computer and use it in GitHub Desktop.
I've Never Had A Good Story For View-Rendering Helpers In ColdFusion
<button
data-action="parent##logAction"
data-parent-child-id-param="#encodeForHtmlAttribute( item.id )#"
data-parent-child-data-param="#encodeForHtmlAttribute( serializeJson( item ) )#">
Trigger Parent Action
</button>
<cfscript>
// In a production app, this ColdFusion component would be cached in the application
// scope, or wired into a dependency-injection mechanism.
application.viewHelper = new ViewHelper();
// Then, I would likely alias it for the sake of brevity wherever it was needed.
view = application.viewHelper;
</cfscript>
<cfoutput>
<!--- Serializing complex values (as JSON) for use in an attribute. --->
<form>
<input
type="hidden"
name="state"
#view.attr( "value", { id: 1, name: "Ben" } )#
/>
</form>
<!--- Optionally including [selected] attribute based on condition. --->
<form>
<select>
<cfloop index="i" from="1" to="10">
<option
#view.attr( "value", i )#
#view.selectedAttr( i == 5 )#>
#i#
</option>
</cfloop>
</select>
</form>
<!--- Optionally including [disabled] attribute based on condition. --->
<form>
<button #view.disabledAttr( false )#>
I am enabled
</button>
<button #view.disabledAttr( true )#>
I am disabled
</button>
</form>
<!--- Optionally including [checked] attribute based on condition. --->
<form>
<cfloop index="i" from="1" to="5">
<input
type="radio"
name="r"
value="1"
#view.checkedAttr( i == 2 )#
/>
</cfloop>
</form>
<!--- Coalescing [class] attribute based on conditions. --->
<p #view.classAttr([
"block": true,
"block--modifier": true,
"block__element": false
])#>
I have CSS classes.
</p>
<!--- Coalescing [style] attribute. --->
<p #view.styleAttr([
"color": "red",
"font-style": "italic"
])#>
I have styles.
</p>
<!--- I render multiple "data-" attributes with encoded values. --->
<p #view.dataAttrs([
"action": "parent##logAction",
"parent-child-id-param": 1,
"parent-child-data-param": { id: 1, name: "Ben Nadel" }
])#>
I have "data-"" attributes.
</p>
</cfoutput>
component
output = false
hint = "I provide utility methods for rendering HTML in a view template."
{
/**
* I output the given attribute with proper attribute encoding. If the value is a
* complex data structure, it is serialized as JSON.
*/
public string function attr(
required string name,
any value = ""
) {
if ( isSimpleValue( value ) ) {
return( "#name#=""#encodeForHtmlAttribute( value )#""" );
} else {
return( "#name#=""#encodeForHtmlAttribute( serializeJson( value ) )#""" );
}
}
/**
* I output the given boolean attribute if the given condition is Truthy.
*/
public string function booleanAttr(
required string name,
any condition = false
) {
return( isTruthy( condition ) ? name : "" );
}
/**
* I output the "checked" boolean attribute if the given condition is Truthy.
*/
public string function checkedAttr( any condition = false ) {
return( booleanAttr( "checked", condition ) );
}
/**
* I output the "class" attribute, including every key in the given collection whose
* corresponding value is Truthy.
*/
public string function classAttr( required struct values ) {
var classNames = filterStructOnTruthyValues( values )
.keyArray()
.toList( " " )
;
if ( classNames.len() ) {
return( "class=""#classNames#""" );
}
return( "" );
}
/**
* I output a "data-*" attribute for each key-value pair in the given collection.
*/
public string function dataAttrs( required struct values ) {
var pairs = values.keyArray()
.map(
( key ) => {
return( attr( "data-#key#", values[ key ] ) );
}
)
.toList( " " )
;
return( pairs );
}
/**
* I output the "disabled" boolean attribute if the given condition is Truthy.
*/
public string function disabledAttr( any condition = false ) {
return( booleanAttr( "disabled", condition ) );
}
/**
* I output the "selected" boolean attribute if the given condition is Truthy.
*/
public string function selectedAttr( any condition = false ) {
return( booleanAttr( "selected", condition ) );
}
/**
* I output the "style" attribute using the given key-value CSS properties.
*/
public string function styleAttr( required struct values ) {
var pairs = [];
for ( var key in values ) {
pairs.append( "#key#: #values[ key ]# ;" );
}
var styles = pairs.toList( " " );
if ( styles.len() ) {
return( "style=""#styles#""" );
}
return( "" );
}
// ---
// PRIVATE METHODS.
// ---
/**
* I filter the given collection down to the keys with Turthy values.
*/
private struct function filterStructOnTruthyValues( required struct collection ) {
var filteredCollection = collection.filter(
( key, value ) => {
// CAUTION: The filter() method will include null keys in the iteration.
// As such, we can't use the isTruthy() method on its own or we'll run
// into null reference errors.
return( ! isNull( value ) && isTruthy( value ) );
}
);
return( filteredCollection );
}
/**
* I determine if the given value is a Falsy.
*/
private boolean function isFalsy( any value = false ) {
if ( ! isSimpleValue( value ) ) {
return( false );
}
if ( isBoolean( value ) || isNumeric( value ) ) {
return( ! value );
}
return( value == "" );
}
/**
* I determine if the given value is a Truthy (which is any value that isn't Falsy).
*/
private boolean function isTruthy( any value = false ) {
return( ! isFalsy( value ) );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment