Created
March 12, 2023 16:45
-
-
Save bennadel/8da3a88917c107449f7172e6ab9b2f5d to your computer and use it in GitHub Desktop.
Rendering A Fly-Out Form Panel Using Turbo Frames With Hotwire And Lucee CFML
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
<!--- | |
When rendered as a top-level request, we can render the form AS-IS. However, if we're | |
rendering inside a Turbo Frame (ie, we're trancluding the form into another page), we | |
have to render the form inside a like-named Turbo Frame so that Hotwire can merge the | |
results back into the live page. | |
-- | |
NOTE: In a more robust architecture, this could be implemented much more seamlessly as | |
a layout selection, such as a "standard" layout vs a "fly-out" layout. However, to | |
keep things as simple as possible, I'm rendering both types of layouts right here in | |
the same template so that we can see the mechanics at play. | |
---> | |
<cfif request.turbo.isFrame> | |
<turbo-frame id="fly-out-frame"> | |
<div class="fly-out"> | |
<div class="fly-out__content"> | |
<!--- !!! Reused Form UI !!! ---> | |
<cfinclude template="_form.cfm" /> | |
</div> | |
<a href="index.htm" class="fly-out__backdrop"> | |
Close | |
</a> | |
</div> | |
</turbo-frame> | |
<!--- Standard page layout, non-frame version. ---> | |
<cfelse> | |
<cfmodule template="../tags/page.cfm"> | |
<!--- !!! Reused Form UI !!! ---> | |
<cfinclude template="_form.cfm" /> | |
</cfmodule> | |
</cfif> |
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> | |
header | |
statusCode = request.template.statusCode | |
statusText = request.template.statusText | |
; | |
content | |
type = "text/vnd.turbo-stream.html; charset=utf-8" | |
; | |
</cfscript> | |
<!--- | |
Turbo Drive is expecting our POST request to do one of the following: | |
1. Redirect to another page (upon successful execution). | |
2. Re-render with a non-200 response (to show error messages). | |
3. Respond with a set of Turbo Stream directives (to mutate the existing DOM). | |
The problem that we face here is that our parent Turbo Frame has [target="_top"]. | |
Which means that if we re-render our form to show the errors, it will swap out the | |
entire page content, not just the fly-out content. As such, in order to maintain the | |
same page layout AND show errors, we need to use a Turbo Stream directive to REPLACE | |
THE ENTIRE FORM, complete with errors, in order to update the view. This is why we | |
wrapped the form in a DIV[id="note-form"] - so that we could hot-swap it! | |
---> | |
<turbo-stream action="replace" target="note-form"> | |
<template> | |
<!--- !!! Reused Form UI !!! ---> | |
<cfinclude template="_form.cfm" /> | |
</template> | |
</turbo-stream> |
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
<cfoutput> | |
<!--- | |
This wrapper DIV serves to give us a target that we can REPLACE with a Turbo | |
Stream directive if / when we need to re-render the form with error messages. | |
---> | |
<div id="note-form"> | |
<h2> | |
Add Note | |
</h2> | |
<cfif errorMessage.len()> | |
<p class="error-message"> | |
#encodeForHtml( errorMessage )# | |
</p> | |
</cfif> | |
<form method="post" action="create/index.htm"> | |
<p> | |
<input type="text" name="text" size="40" autofocus /> | |
</p> | |
<p> | |
<button type="submit"> | |
Add Note | |
</button> | |
<a href="index.htm"> | |
Cancel | |
</a> | |
</p> | |
</form> | |
</div> | |
<!--- | |
This script tag will be executed every time the view is merged into the page, | |
whether as the initial rendering or as part of a Turbo Stream action. I'm using it | |
to focus the input. THe [autofocus] attribute works on the first render, but not | |
on the subsequent rendering. This script tag makes up the difference. | |
---> | |
<script type="text/javascript"> | |
document.querySelector( "input[name='text']" ).focus(); | |
</script> | |
</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
<cfscript> | |
param name="request.context.text" type="string" default=""; | |
errorMessage = ""; | |
// Processing the note form submission. | |
if ( request.isPost ) { | |
try { | |
application.noteService.createNote( request.context.text.trim() ); | |
// NOTE: Since our Turbo Frame has [target="_top"], Turbo Drive is going to | |
// apply any response - including a Location header - to the top-level page, | |
// not to the Turbo Frame. That means that upon success, we can simply | |
// redirect the user back to the main page in order to render the newly- | |
// created note. | |
location( url = "../index.htm", addToken = false ); | |
} catch ( any error ) { | |
errorResponse = application.errorService.getResponse( error ); | |
request.template.statusCode = errorResponse.statusCode; | |
request.template.statusText = errorResponse.statusText; | |
errorMessage = errorResponse.message; | |
} | |
// We only make it this far if there are errors in the form validation (and we | |
// didn't redirect the user back to the main page). If the request can support | |
// consuming a Turbo Stream (ie, it's been enhanced by Hotwire), then we need to | |
// update the UI using stream directives, otherwise Turbo Drive will overwrite the | |
// entire page (remember, [target="_top"]) with our response. | |
if ( request.turbo.isStream ) { | |
include "_error.stream.cfm"; | |
exit; | |
} | |
} | |
include "_create.cfm"; | |
</cfscript> |
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> | |
notes = application.noteService.getNotes(); | |
</cfscript> | |
<cfmodule template="./tags/page.cfm"> | |
<cfoutput> | |
<h2> | |
Welcome to My Site | |
</h2> | |
<p> | |
<!--- | |
We're opening our note form into a Turbo Frame AND we're advancing the | |
location URL. This way, if someone were to refresh the page, they would | |
still be presented with the note form (albeit no longer located within the | |
Turbo Frame / Fly-out panel). | |
---> | |
<a | |
href="create/index.htm" | |
data-turbo-frame="fly-out-frame" | |
data-turbo-action="advance"> | |
Add note | |
</a> | |
</p> | |
<ul> | |
<cfloop item="note" array="#notes#"> | |
<li> | |
#encodeForHtml( note.text )# | |
</li> | |
</cfloop> | |
</ul> | |
<!--- | |
Our Fly-Out Turbo Frame will use [target="_top"] so that any navigation events | |
or redirects within the frame will be applied to the top-level page. This will | |
make it possible/easier to REDIRECT BACK TO THE MAIN PAGE with up-to-date note | |
information after an embedded fly-out form mutates the application state. | |
---> | |
<turbo-frame | |
id="fly-out-frame" | |
target="_top"> | |
</turbo-frame> | |
</cfoutput> | |
</cfmodule> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment