Skip to content

Commit

Permalink
#169 Add <cbwire:script> and <cbwire:assets> functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
grantcopley committed Sep 29, 2024
1 parent 8072a7b commit e4df642
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 13 deletions.
14 changes: 11 additions & 3 deletions models/CBWIREController.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ component singleton {
}
/**
* Instantiates a CBWIRE component, mounts it,
* and then calls its internal renderIt() method.
* and then calls its onRender() method.
*
* @name The name of the component to load.
* @params The parameters you want mounted initially. Defaults to an empty struct.
Expand Down Expand Up @@ -57,6 +57,10 @@ component singleton {
* @return A struct representing the response with updated component details or an error message.
*/
function handleRequest( incomingRequest, event ) {
// Track state for entire request
local.httpRequestState = {
"assets": {}
};
// Perform initial deserialization of the incoming request payload
local.payload = deserializeJSON( arguments.incomingRequest.content );
// Set the CSRF token for the request
Expand All @@ -82,10 +86,15 @@ component singleton {
._withPath( _componentPayload.snapshot.memo.name )
._withEvent( event )
._withIncomingPayload( _componentPayload )
._getHTTPResponse( _componentPayload );
._getHTTPResponse( _componentPayload, httpRequestState );
} )
};

// Return assets from components
if ( local.httpRequestState.assets.count() ) {
local.componentsResult[ "assets" ] = local.httpRequestState.assets;
}

// Set the response headers to prevent caching
event.setHTTPHeader( name="Pragma", value="no-cache" );
event.setHTTPHeader( name="Expires", value="Fri, 01 Jan 1990 00:00:00 GMT" );
Expand Down Expand Up @@ -203,7 +212,6 @@ component singleton {
local.singleFileComponent = variables.singleFileComponentBuilder
.setInitialRender( true )
.build( fullComponentPath, arguments.name, getCurrentRequestModule() );

if ( isNull( local.singleFileComponent ) ) {
writeDump( local );
abort;
Expand Down
79 changes: 69 additions & 10 deletions models/Component.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ component output="true" {
property name="_wirebox" inject="wirebox";

property name="_id";
property name="_compileTimeKey";
property name="_parent";
property name="_initialLoad";
property name="_lazyLoad";
Expand All @@ -29,6 +30,8 @@ component output="true" {
property name="_isolate";
property name="_path";
property name="_renderedContent";
property name="_scripts";
property name="_assets";

/**
* Constructor
Expand All @@ -52,6 +55,7 @@ component output="true" {
}

variables._params = [:];
variables._compileTimeKey = hash( getCurrentTemplatePath() );
variables._key = "";
variables._cache = [:];
variables._dispatches = [];
Expand All @@ -65,6 +69,8 @@ component output="true" {
variables._redirectUsingNavigate = false;
variables._isolate = false;
variables._renderedContent = "";
variables._scripts = [:];
variables._assets = [:];

/*
Cache the component's meta data on initialization
Expand Down Expand Up @@ -195,7 +201,7 @@ component output="true" {
*/
function template( viewPath, params = {} ) {
// Normalize the view path
local.normalizedPath = _getNormalizedViewPath( viewPath );
local.normalizedPath = _getNormalizedViewPath( arguments.viewPath );
// Render the view content and trim the result
return _renderViewContent( local.normalizedPath, arguments.params );
}
Expand Down Expand Up @@ -1074,21 +1080,55 @@ component output="true" {
*/
function _renderViewContent( normalizedPath, params = {} ){
if ( !variables._renderedContent.len() ) {
local.templateReturnValues = {};
// Render our view using an renderer encapsulator
savecontent variable="local.viewContent" {
cfmodule(
template = "RendererEncapsulator.cfm",
cbwireComponent = this,
normalizedPath = arguments.normalizedPath,
params = arguments.params
params = arguments.params,
returnValues = local.templateReturnValues
);
}
_parseTemplateReturnValues( local.templateReturnValues );
variables._renderedContent = local.viewContent;
}

return variables._renderedContent;
}

/**
* Parses the return values from the RendererEncapsulator.
*
* @return void
*/
function _parseTemplateReturnValues( returnValues ) {
// Parse and track cbwire:script tags
arguments.returnValues.filter( function( key, value ) {
return key.findNoCase( "script" );
} ).each( function( key, value, result ) {
// Extract the counter from the tag name
local.counter = key.replaceNoCase( "script", "" );
// Create script tag id based on compile time id and counter
local.scriptTagId = variables._compileTimeKey & "-" & local.counter;
// Track the script tag
variables._scripts[ local.scriptTagId ] = value;
} );

// Parse and track cbwire:assets tags
arguments.returnValues.filter( function( key, value ) {
return key.findNoCase( "assets" );
} ).each( function( key, value, result ) {
// Extract the counter from the tag name
local.counter = key.replaceNoCase( "assets", "" );
// Create assets tag id based on compile time id and counter
local.assetsTagId = variables._compileTimeKey & "-" & local.counter;
// Track the assets tag
variables._assets[ local.assetsTagId ] = value;
} );
}

/**
* Validates that the HTML content has a single outer element.
* Ensures the first and last tags match and that the total number of tags is even.
Expand Down Expand Up @@ -1209,10 +1249,11 @@ component output="true" {
* for subsequent requests.
*
* @componentPayload struct | The payload to hydrate the component with.
* @httpRequestState struct | The state of the entire HTTP request being returned for all components.
*
* @return struct
*/
function _getHTTPResponse( componentPayload ){
function _getHTTPResponse( componentPayload, httpRequestState ){
// Hydrate the component
_hydrate( arguments.componentPayload );
// Apply any updates
Expand Down Expand Up @@ -1264,6 +1305,14 @@ component output="true" {
local.response.effects[ "redirect" ] = variables._redirect;
local.response.effects[ "redirectUsingNavigate" ] = variables._redirectUsingNavigate;
}
// Add any cbwire:scripts
if ( variables._scripts.count() ) {
local.response.effects[ "scripts" ] = variables._scripts;
}
// Add any cbwire:assets to the global http request state
if ( variables._assets.count() ) {
httpRequestState.assets.append( variables._assets );
}

return local.response;
}
Expand Down Expand Up @@ -1494,7 +1543,7 @@ component output="true" {
"path": _getComponentName(),
"method":"GET",
"children": variables._children.count() ? variables._children : [],
"scripts":[],
"scripts": variables._scripts.count() ? variables._scripts.keyArray() : [],
"assets":[],
"isolate": variables._isolate,
"lazyLoaded": false,
Expand Down Expand Up @@ -1544,6 +1593,15 @@ component output="true" {
return variables._id & "-" & variables._children.count();
}

/**
* Returns the component's script tags.
*
* @return struct
*/
function _getScripts(){
return variables._scripts;
}

/**
* Returns the component's meta data.
*
Expand All @@ -1568,19 +1626,20 @@ component output="true" {
* @return string
*/
function _generateWireEffectsAttribute() {
local.effects = {};
local.listenersAsArray = variables.listeners.reduce( function( acc, key, value ) {
acc.append( key );
return acc;
}, [] );


if ( local.listenersAsArray.len() ) {
local.effects = {
"listeners": local.listenersAsArray
};
local.effects[ "listeners" ] = local.listenersAsArray;
}
if ( variables._scripts.count() ) {
local.effects[ "scripts" ] = variables._scripts;
}
if ( local.effects.count() ) {
return _encodeAttribute( serializeJson( local.effects ) );
}

return "[]";
}

Expand Down
54 changes: 54 additions & 0 deletions models/preprocessor/TemplatePreprocessor.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
component {
/**
* Returns content with scripts and assets parsed.
*
* @content string | The raw template contents
*
* @return string
*/
function handle( content ) {
// Handle <cbwire:script> tags
content = parseScripts( content );
// Handle <cbwire:assets> tags
content = parseAssets( content );
return content;
}

/**
* Parses <cbwire:script> tags.
*
* @content | The raw template contents
* @counter | Tracks the instances of cbwire:script
*
* @return string
*/
function parseScripts( content, counter = 1 ) {
if ( counter == 1 ) {
content = replaceNoCase( content, "</cbwire:script>", "</cfsavecontent>", "all" );
}
content = replaceNoCase( content, "<cbwire:script>", "<cfsavecontent variable=""attributes.returnValues.script#counter#"">" );
if ( findNoCase( "<cbwire:script>", content ) ) {
content = parseScripts( content, counter + 1 );
}
return content;
}

/**
* Parses <cbwire:assets> tags.
*
* @content | The raw template contents
* @counter | Tracks the instances of cbwire:assets
*
* @return string
*/
function parseAssets( content, counter = 1 ) {
if ( counter == 1 ) {
content = replaceNoCase( content, "</cbwire:assets>", "</cfsavecontent>", "all" );
}
content = replaceNoCase( content, "<cbwire:assets>", "<cfsavecontent variable=""attributes.returnValues.assets#counter#"">" );
if ( findNoCase( "<cbwire:assets>", content ) ) {
content = parseAssets( content, counter + 1 );
}
return content;
}
}
27 changes: 27 additions & 0 deletions test-harness/tests/specs/CBWIRESpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,33 @@ component extends="coldbox.system.testing.BaseTestCase" {
expect( parent.snapshot.memo.children[ keys[ 1 ] ][ 2 ] ).toBe( child.snapshot.memo.id );
} );

it( "should not render cbwire:script tags", function() {
var result = CBWIREController.wire( "test.should_not_render_cbwire_script_tags" );
expect( result ).notToInclude( "<cbwire:script>" );
expect( result ).notToInclude( "</cbwire:script>" );
expect( result ).notToInclude( "This should not be rendered" );
} );

it( "should not render cbwire:assets tags", function() {
var result = CBWIREController.wire( "test.should_not_render_cbwire_assets_tags" );
expect( result ).notToInclude( "<cbwire:assets>" );
expect( result ).notToInclude( "</cbwire:assets>" );
expect( result ).notToInclude( "tailwind.min.css" );
} );

fit( "should track scripts and assets in snapshot memo", function() {
var result = CBWIREController.wire( "test.should_track_scripts_and_assets_in_snapshot_memo" );
var parsing = parseRendering( result );
writeDump( parsing.snapshot.memo );
abort;
expect( parsing.snapshot.memo.scripts ).toBeArray();
expect( parsing.snapshot.memo.scripts.len() ).toBe( 1 );
expect( parsing.snapshot.memo.scripts[ 1 ] ).toBe( "https://cdn.jsdelivr.net/npm/[email protected]/dist/alpine.min.js" );
expect( parsing.snapshot.memo.assets ).toBeArray();
expect( parsing.snapshot.memo.assets.len() ).toBe( 1 );
expect( parsing.snapshot.memo.assets[ 1 ] ).toBe( "tailwind.min.css" );
} );

xit( "should provide original path to component when there is a template rendering error", function() {
var result = CBWIREController.wire( "test.should_raise_error_for_template_rendering_error" );
expect( function() {
Expand Down
15 changes: 15 additions & 0 deletions test-harness/wires/test/should_not_render_cbwire_assets_tags.cfm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<cfoutput>
<div>
<h1>Should not render cbwire assets tags</h1>
</div>
</cfoutput>

<cfscript>
// @startWire
// @endWire
</cfscript>

<cbwire:assets>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css">
</cbwire:script>
17 changes: 17 additions & 0 deletions test-harness/wires/test/should_not_render_cbwire_script_tags.cfm
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<cfoutput>
<div>
<h1>Should not render cbwire script tags</h1>
</div>
</cfoutput>

<cfscript>
// @startWire
// @endWire
</cfscript>

<cbwire:script>
<script>
console.log('This should not be rendered');
</script>
</cbwire:script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<cfoutput>
<div>
<h1>Should track scripts and assets</h1>
</div>
</cfoutput>

<cfscript>
// @startWire
// @endWire
</cfscript>

<cbwire:script>
<script>
console.log('This should be tracked');
</script>
</cbwire:script>

<cbwire:script>
<script>
console.log('This should be tracked also');
</script>
</cbwire:script>

<cbwire:assets>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css">
</cbwire:script>

0 comments on commit e4df642

Please sign in to comment.