FireWire Posted May 2 Share Posted May 2 Hey all! This is a module to enhance forms built using the Pro FormBuilder module by providing the ability to submit them in place using AJAX and HTMX. FormBuilderHtmx works in harmony with FormBuilder by handling front-end rendering and AJAX and lets FormBuilder manage form configuration and processing. FormBuilderHtmx provides a drop-in replacement for the $forms->render() method and provides all native features and behavior (and adds a few extra superpowers to boot). Noteworthy features: Zero configuration, install and render AJAX powered FormBuilder forms immediately Render multiple forms on the same page. Supports both multiple instances of the same form or different forms. Each form is processed independently. Non-intrusive, can be used alongside FormBuilder's native rendering methods and does not modify core module behavior Perfect for forms embedded in popups and modals Does not conflict with styling and other JavaScript already in-place, only handles the form submission/response loop Automatically disables the `Submit` button on submission to prevent duplicate requests Provides the ability to add a custom 'spinner' shown when a form is being processed Gives you the ability to add additional HTML attributes to your FormBuilder <form> element. Add additional custom functionality using HTMX attributes, hook into form actions with your JavaScript, or even add AlpineJS directly to your forms. Compatible with FieldtypeFormSelect, let users choose which forms to embed, your code determines how they are rendered Uses HTMX, a stable, powerful, and tiny (14kb gzipped) library, installation documentation available here This module is BYOH (Bring Your Own HTMX) in that the HTMX library is not included or available within this module. This ensures long-term stability by not locking FormBuilderHtmx to external asset versioning. FormBuilderHtmx uses stable core HTMX features so use the library version that works for you and confidently add this module to both new, existing, and future ProcessWire applications. In some instances CSRF protection may need to be disabled to submit forms with this module. Just test your forms and you're good to go. Using this module is truly easy. <!-- Replace the native $forms->render() method with $htmxForms->render() --> <?php $htmxForm = $htmxForms->render('your_form_name') ?> <!-- Use native ProcessWire properties and methods as usual --> <?php echo $htmxForm->styles; echo $htmxForm->scripts; echo $htmxForm; ?> Presto. You can optionally include a helpful 'spinner' or activity animation that will be showed to users while their form request is being processed. Check out these ready-to-go examples you can use in your projects. <style> /* Add these styles to your CSS, the `.htmx-request` must be present as shown here. Be sure to include any CSS your 'spinner' may need, and style everything as desired */ .activity-indicator { display: none; } .htmx-request .activity-indicator, .htmx-request.activity-indicator { display: block; } </style> <!-- Optional second argument matches that of the $forms->render() method for pre-populated values The third argument is the CSS selector matching your 'spinner' element --> <?= $htmxForms->render('your_form_name', [], '#indicator-for-the-form') ?> <div id="indicator-for-the-form" class="activity-indicator"> <span class="spinner"></span> </div> Presto (again) Check out the documentation for detailed usage and other available features. Pull requests and issues filed on Github are welcome, or drop by here to get some help! Install as a ProcessWire module Install using Composer Download from the FormBuilderHtmx Github repository . Cheers! 22 4 Link to comment Share on other sites More sharing options...
netcarver Posted May 2 Share Posted May 2 Yay! Thank you, @FireWire looking forward to trying this out later. 2 Link to comment Share on other sites More sharing options...
wbmnfktr Posted May 4 Share Posted May 4 I was just playing around with your module and wanted to export my playground profile so I can use it in another PW instance and then this happened: Spoiler Dangit… Fatal Error: Uncaught TypeError: FormBuilderHtmx::renderAjaxResponseMarkup(): Argument #2 ($submitKey) must be of type string, null given, called in site/modules/FormBuilderHtmx/FormBuilderHtmx.module.php on line 68 and defined in site/modules/FormBuilderHtmx/FormBuilderHtmx.module.php:117 #0 site/modules/FormBuilderHtmx/FormBuilderHtmx.module.php(68): FormBuilderHtmx->renderAjaxResponseMarkup('InputfieldForm2', NULL, '<!DOCTYPE html>...') #1 wire/core/WireHooks.php (1085): FormBuilderHtmx->{closure}(Object(HookEvent)) #2 wire/core/Wire.php (484): WireHooks->runHooks(Object(Page), 'render', Array) #3 wire/modules/Process/ProcessPageView.module (184): Wire->__call('render', Array) #4 wire/modules/Process/ProcessPageView.module (114): ProcessPageView->renderPage(Object(Page), Object(PagesRequest)) #5 wire/core/Wire.php (416): ProcessPageView->___execute(true) #6 wire/core/WireHooks.php (968): Wire->_callMethod('___execute', Array) #7 wire/core/Wire.php (484): WireHooks->runHooks(Object(ProcessPageView), 'execute', Array) #8 index.php (55): Wire->__call('execute', Array) #9 {main} thrown (line 117 of site/modules/FormBuilderHtmx/FormBuilderHtmx.module.php) This error message was shown because: you are logged in as a Superuser. Error has been logged. Tried to export everything with ProcessExportProfile in it's latest version on ProcessWire 3.0.238. 1 Link to comment Share on other sites More sharing options...
FireWire Posted May 4 Author Share Posted May 4 @wbmnfktr I think I have an idea of what happened. Possibly typed some || where some && should have been. Pushed a potential fix to the dev branch. Can you give it a shot and let me know if it fixes the issue? 1 Link to comment Share on other sites More sharing options...
wbmnfktr Posted May 5 Share Posted May 5 This is looking way better. The export works as expected again and the module is in the profile. Thanks, @FireWire! 1 Link to comment Share on other sites More sharing options...
FireWire Posted May 5 Author Share Posted May 5 Glad that worked! Fix has been pushed to main. Thanks for finding, reporting, and testing @wbmnfktr Download latest on Github 1 Link to comment Share on other sites More sharing options...
gebeer Posted May 13 Share Posted May 13 Thanks for the module! htmx is awesome and absolutely the right tool for such things. 3 Link to comment Share on other sites More sharing options...
gebeer Posted May 13 Share Posted May 13 On 5/2/2024 at 10:50 PM, FireWire said: Note- this module is BYOH (Bring Your Own HTMX) as the library is not provided with FormBuilderHtmx You could do a check for existance of window.htmx and then conditionally include it from CDN. Sth along these lines if (typeof window.htmx === 'undefined') { var script = document.createElement('script'); script.src = "https://unpkg.com/htmx.org@latest"; script.integrity = "sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"; // Not sure if this works reliably with @latest script.crossOrigin = "anonymous"; document.head.appendChild(script); script.onload = function() { console.log("htmx loaded successfully!"); }; script.onerror = function() { console.error("Failed to load htmx!"); }; } else { console.log("htmx is already loaded."); } 1 Link to comment Share on other sites More sharing options...
FireWire Posted May 13 Author Share Posted May 13 7 hours ago, gebeer said: Not sure if this works reliably with @latest Yeah, the subresource key is a hash of the file contents so that will break at some point on @latest. Alternatively, I don't want to introduce external resource versioning since it would mean the module has to be updated to keep up with HTMX. Biggest deal for me is that, as an online privacy advocate, I can't recommend something that I wouldn't use myself. 7 hours ago, gebeer said: htmx is awesome and absolutely the right tool for such things. Agreed! Great way to add SPA features without fundamentally changing ProcessWire rendering. I'd argue that it's the way that interactive UI should be built to begin with. 3 Link to comment Share on other sites More sharing options...
HMCB Posted May 18 Share Posted May 18 Just finding this. THIS IS HUGE! Thank you, @FireWire. 2 Link to comment Share on other sites More sharing options...
gornycreative Posted June 1 Share Posted June 1 I was playing around with this today. A few things I noticed. ->styles and ->scripts return null, but I could pull the original form output and use the extractions from there. I get an AJAX response and the submission works, but the parsing of the whole page object returned didn't seem to work (the whole page replaced the original form on the page). I don't have any PR yet, just going to tinker now, but thought I'd report my findings. I should comment - I am using latte templates for output. I may need to hook into output markup somehow. 1 Link to comment Share on other sites More sharing options...
FireWire Posted June 2 Author Share Posted June 2 23 hours ago, gornycreative said: arsing of the whole page object returned didn't seem to work (the whole page replaced the original form on the page) Can you double check that CSRF is disabled for the form? I was able to replicate the issue, working on it. 23 hours ago, gornycreative said: ->styles and ->scripts return null, but I could pull the original form output and use the extractions from there. This is a good point... I haven't been using either of those since I include my own styles. So I guess only the render method is "drop in". I'll look into this. Link to comment Share on other sites More sharing options...
gornycreative Posted June 2 Share Posted June 2 2 hours ago, FireWire said: I haven't been using either of those since I include my own styles. Yeah and I've been debating rolling my own styles and template also since I tend to try to use update UIKit or TW or whatever I am using and I have only started messing with FormBuilder, but I figure it may still be worth covering? Not sure. Link to comment Share on other sites More sharing options...
FireWire Posted June 3 Author Share Posted June 3 14 hours ago, gornycreative said: but I figure it may still be worth covering? It is. Next revision will truly be passthrough in that it adds HTMX and then returns the FormBuilder instance so you can continue to use all of the built in methods, including rendering FormBuilder scripts and styles- so a true drop-in replacement. Appreciate your feedback and help with making this module better! 2 Link to comment Share on other sites More sharing options...
FireWire Posted June 4 Author Share Posted June 4 New version pushed. This resolves several issues and also adds expected features. The $htmxForms->render() method is now a true drop-in replacement. All methods/properties available on $forms->render() are available. The $htmxForms render method returns the original object expected from $forms->render(), so FormBuilder scripts and styles can be used The same form can be used multiple times on the same page when all are rendered using FormBuilderHtmx, each separately tracks state/errors/submissions Has more thorough checking for form submissions to detect HTMX form submissions vs. traditional form submissions Resolves issue where the full page markup was appearing in place of the form in some instances when submitting A note on multiple instances of the same form on one page- if one is rendered using FormBuilder and a submission has errors, all instances of that form will show those errors regardless if rendered using FormBuilder or FormBuilderHtmx. This is a core behavior of FormBuilder AFAIK and is not something introduced by FormBuilderHtmx. A benefit to rendering multiple instances of the same form using FormBuilderHtmx is that each form is individually processed. Have done a lot of testing with great results, but as always, community testing and feedback is appreciated! Available for download via the Github repo. @gornycreative & @Sanyaissues let me know if this fixes the issues you're seeing. 2 1 Link to comment Share on other sites More sharing options...
Sanyaissues Posted June 4 Share Posted June 4 That was fast @FireWire, You are on fire! Now my form is returning the Success Message and not the whole page. THANKS. I'll keep testing. 1 Link to comment Share on other sites More sharing options...
Sanyaissues Posted June 4 Share Posted June 4 @FireWire if the form is rendered inside a template using Markup regions, there's an error after submission: TypeError ProcessWire\FormBuilderHtmx::renderHtmxResponse(): Return value must be of type string, null returned search► File: .../modules/FormBuilderHtmx/FormBuilderHtmx.module.php:169 159: /** 160: * Finds the form that has been submitted and extracts the markup to return what HTMX expects 161: * @param string $renderedPageMarkup Rendered full page markup 162: */ 163: private function renderHtmxResponse(string $renderedPageMarkup): string 164: { 165: $pattern = "/<div id=[\"']{$this->htmxFormId()}[\"']((.|\n|\r|\t)*)<!--\/.FormBuilder-->/U"; 166: 167: preg_match($pattern, $renderedPageMarkup, $matches); 168: 169: return $matches[0]; 170: } 171: } Link to comment Share on other sites More sharing options...
FireWire Posted June 4 Author Share Posted June 4 @Sanyaissues I've never used markup regions before. Knew they existed but haven't been part of my workflow. From the looks of it, that error indicates that when the form is submitted, the return markup doesn't contain the form or doesn't contain the identifying markup FormBuilderHtmx adds to a form that identifies it. With markup regions adding a step where it handles replacement of markup in the document, I'm not sure how that affects form rendering. This error is intentional as it helps identify where forms aren't rendering in a way that FormBuilderHtmx expects. Need to see your code and how you're embedding your forms. If you embed a form using markup regions without FormBuilderHtmx using $forms->render(), what happens? Link to comment Share on other sites More sharing options...
Sanyaissues Posted June 4 Share Posted June 4 (edited) @FireWire using $forms->render with Markup Regions or using FormBuilderHtmx, without the markup regions, everything works. Regarding my template with the markup regions the setup is kind of simple: Spoiler // /site/config.php $config->useMarkupRegions = true; $config->appendTemplateFile = '_main.php'; // /site/templates/_main.php <?php namespace ProcessWire; ?> <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="<?= urls()->templates ?>styles/tailwind/tailwind.css"> </head> <body> <main data-pw-id="content"></main> <!-- Heres my markup region --> </body> <script src="<?= urls()->templates ?>scripts/vendors/htmx/htmx.min.js"></script> </html> // /site/templates/mytemplate.php <?php namespace ProcessWire; ?> <!-- This main will replace the one in _main.php --> <main pw-replace="content" class="max-w-screen-sm mx-auto> <?= $htmxForms->render(page()->form_selector, [], '#indicator') ?> <div class="activity-indicator hidden" id="indicator"> <div class="flex justify-center items-center absolute w-full h-full bg-white/50 border top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"> <span class="loader w-8 h-8 border-4 border-gray-300 border-b-orange-500 rounded-[50%] inline-block box-border"></span> </div> </div> </main> Question aside: It's possible to return to htmx only the form using if($config->ajax) or something else? Edited June 4 by Sanyaissues Improving my explanation for future reference Link to comment Share on other sites More sharing options...
FireWire Posted June 4 Author Share Posted June 4 6 minutes ago, Sanyaissues said: using $forms->render or using FormBuilderHtmx, without markup regions, everything works. If the forms only work when markup regions is not used then that's more of something to do with FormBuilder itself. FormBuilderHtmx just hooks into the FormBuilder module so if FormBuilder doesn't work, this module won't either. It may be better to look into markup regions in the FormBuilder support threads. 8 minutes ago, Sanyaissues said: Question aside: It's possible to return to htmx only the form using if($config->ajax) or something else? Not sure why that would be beneficial? Htmx sends headers to indicate that the request is from htmx (via ajax), and the module adds identifying markup to tell htmx where to insert the form results. So while ajax is used, detecting it separately wouldn't be useful. The module does more than just return the form, if it just returned the form then ajax submissions would stop working for it altogether. There is a lot more that goes on behind the scenes to make this work in harmony with FormBuilder. Link to comment Share on other sites More sharing options...
Sanyaissues Posted June 4 Share Posted June 4 21 minutes ago, FireWire said: If the forms only work when markup regions is not used then that's more of something to do with FormBuilder itself. Excuse my english! What I mean, was: the form WORKS using markup regions with $form->render AND also, the form works using FormBuilderHtmx in a regular template. @FireWire Link to comment Share on other sites More sharing options...
FireWire Posted June 4 Author Share Posted June 4 @Sanyaissues Updated the module to support markup regions. Download and let me know if this resolves the issue. https://github.com/SkyLundy/FormBuilderHtmx Link to comment Share on other sites More sharing options...
Sanyaissues Posted June 5 Share Posted June 5 The error persist @FireWire Spoiler TypeError ProcessWire\FormBuilderHtmx::renderHtmxResponse(): Return value must be of type string, null returned search► File: .../modules/FormBuilderHtmx/FormBuilderHtmx.module.php:175 165: /** 166: * Finds the form that has been submitted and extracts the markup to return what HTMX expects 167: * @param string $renderedPageMarkup Rendered full page markup 168: */ 169: private function renderHtmxResponse(string $renderedPageMarkup): string 170: { 171: $pattern = "/\n?<div id=[\"']{$this->htmxFormId()}[\"']((.|\n|\r|\t)*)(<!--\/.FormBuilder-->|<span data-formbuilder-htmx-end><\/span>)/U"; 172: 173: preg_match($pattern, $renderedPageMarkup, $matches); 174: 175: return $matches[0]; 176: } 177: } Update 1: I created a new template that only renders the form (loaded with Markup Regions), and it is working. However, in another template with a repeater field that I render with <?= page()->render('banners') ?>, I see the error above. The repeater isn't related to the form, but there is something in the way I'm rendering the content inside my repeater that is breaking FBhtmx. I will try to find out why the error occurs and keep you updated. Update 2: I found the issue! Imagine a template with a Page Reference field, where the value is a PageArray allowing users to select multiple values. The field is rendered using <?= page()->render('myField') ?> and the field template is: // /site/templates/fields/myfield.php <?php namespace ProcessWire; foreach($value as $item){ echo $item->render(); } ?> When our page with the form and myField is rendered, the hook after Page::render will be executed for each value of myField as well as for the page itself. If $this->renderHtmxResponse($e->return) is executed with the pages of myField, a null value will be returned. My temporal fix: private function addPostFormProcessingHook(): void { $this->wire->addHookAfter( 'Page::render', fn ($e) => !empty($e->return) && ( $this->isHtmxRequest() && $e->return = $this->renderHtmxResponse($e->return) ) ); } private function renderHtmxResponse(string $renderedPageMarkup): string { $pattern = "/\n?<div id=[\"']{$this->htmxFormId()}[\"']((.|\n|\r|\t)*)(<!--\/.FormBuilder-->|<span data-formbuilder-htmx-end><\/span>)/U"; preg_match($pattern, $renderedPageMarkup, $matches); return $matches[0] ?? ''; } 1 Link to comment Share on other sites More sharing options...
FireWire Posted June 5 Author Share Posted June 5 @Sanyaissues Github PR? Link to comment Share on other sites More sharing options...
FireWire Posted June 5 Author Share Posted June 5 Thanks to @Sanyaissues for the great work and PR that makes FormBuilderHtmx work when rendering in loops of repeater field content ?. Latest version on Github has the fix merged. Available for download on the main branch. 1 2 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now