Created
March 15, 2023 13:08
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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