Archive for the ‘Releases’ Category


CFWheels HTMX Plugin published

A few weeks ago I published a Todo app using CFWheels on the backend and HTMX to provide the interactivity on the front end to make the app look and feel like a full blown SPA app. As I was developing that app I ran into a few things that I wish we had to make development with HTMX a little easier. But I’m getting ahead of myself.

What is HTMX

Well, HTMX was released a couple of years ago and in that short time has just about exploded in the django community. So what is HTMX, HTMX tries to answer the following questions:

  • Why should only <a> and <form> be able to make HTTP requests?
  • Why should only click & submit events trigger them?
  • Why should only GET & POST methods be available?
  • Why should you only be able to replace the entire screen?

By removing these arbitrary constraints, htmx completes HTML as a hypertext medium. You may even start wondering why these features weren’t in HTML in the first place. So let’s look at an examples.

  <script src="https://unpkg.com/[email protected]"></script>
  <!-- have a button POST a click via AJAX -->
  <button hx-post="/clicked" hx-swap="outerHTML">
    Click Me
  </button>

This block of code tells the browser:

When a user clicks on this button, issue an AJAX request to /clicked, and replace the entire button with the HTML response.

Let’s look at a typical anchor tag:

<a href="/blog">Blog</a>

This anchor tag tells the browser:

When a user clicks on this link, issue an HTTP GET request to ‘/blog’ and load the response content into the browser window.

You can see how HTMX feels like a familiar extension to HTML. With this in mind lets look at a following block of HTML:

<button hx-post="/clicked"
    hx-trigger="click"
    hx-target="#parent-div"
    hx-swap="outerHTML"
>
    Click Me!
</button>

This tells HTMX:

When a user clicks on this button, issue an HTTP POST request to ‘/clicked’ and use the content from the response to replace the element with the id parent-div in the DOM

So by using hx-get, hx-post, hx-put, hx-patch, or hx-delete we gain access to all the HTTP verbs. Imagine a delete button on a table row that actually issues a HTTP Delete to your backend.

The hx-trigger attribute gives us access to all the page events. HTML elements have sensible defaults, the button tag will get triggered by a click by default and an input tag will get triggered by a change event by default. But there are some special events as well, like the load event that will trigger the action when the page is initially loaded or the revealed event that will trigger the action, when the element scrolls into view. Think of an infinite scroll UX pattern where an element scrolls into view, which triggers a call to the backend to load more data that gets added to the bottom of the page.

The hx-target attribute lets you specify a different tag to target than the element that triggered the event. You have the typical CSS selectors but also some special syntax like closest TR to target the closest table row.

The last attribute shown in the example above is the hx-swap which specifies how to swap the response into the element. By default, the response replaces the innerHTML of the target element but you can just as easily replace the entire target element by using outerHTML. There are a few more designators that allow you to finely control placing the response before or after the target element in its parent element or at the begging of or end of a target’s child elements.

This is just scratching the surface of what HTMX can do but you should be getting the picture. By sprinkling in a handful of HTML attributes into your markup you can gain interactivity that was the domain of full blown JavaScript frontend frameworks in the past.

Why should we care as CFWheels developers

By default HTMX is backend agnostic. It just deals with HTML and doesn’t care what backend technology you use to generate it. This could just as easily be used in a plain vanilla CFML app or your framework of choice, hopefully it would be CFWheels since you are here reading this. Wheels has some built in features that make working with HTMX a breeze. We already have a templating system, we already have a router and controllers to intercept the HTTP request. We have a number of rendering methods that make responding to requests simple.

If the request is a for a full page, use the renderView() method or simply let the controller hand the request off to the view which in turn renders the view page. If the request is for a portion of the final page then use the renderPartial() method and return a snippet of code tucked away in a partial. The same partial could be used by your initial view page, keeping your code DRY. Sometimes, you just want to return a small bit of text or no text at all and it doesn’t make sense to build out a view or partial for every instance of these scenarios, that’s when the renderText() method comes in handy. Imagine a typical index page from a CRUD application that lists a bunch of rows of data and some action buttons on each row. Let’s assume, one of these buttons is a delete button. Look at the following code:

// image this button on a table row
<button hx-delete="/products/15" 
        hx-target="closest hr"
        hx-swap="outerHTML"
        hx-confirm="Are you sure?">
    Delete
</button>

// imagine this code in your action
function delete() {
  aProduct = model("product").findByKey(params.key);
  aProduct.delete();
  renderText("");
}

So what does the above combination do:

When the user clicks on the Delete button, prompt the user to make sure they are sure they wish to delete the record, if the user affirms the request, issue a DELETE request to the server. The server in turn deletes the record and sends back an empty text response to the client. When the response comes back to the frontend, find the closes table row, and remove it from the table.

We just made an Ajax call to the server, removed the record from the database, and correspondingly updated the UI by just removing a single element from the DOM.

What does this plugin do?

By default, HTMX adds some request headers to the call sent to the backend which can be interrogated to see if the request is in fact an HTMX request. If the request is actually an HTMX request, some additional request headers are made available which can add more color to the call being processed. This plugin automatically adds these header elements to the params structure which makes them automatically available to your controller actions. This makes it easier to work with this data and incorporate it into your request processing logic. Take a look at the following example:

function index() {
  if (params.htmx.reqeust) {
    renderPartial(partial="myPartial", layout="false");
  }
}

This code block says:

When a request comes in to the index action of the controller, check to see if this is an HTMX request and if it is, respond with the mypartial partial and don’t wrap it with the layout. Otherwise respond with the index view page of the current controller.

Think of a paginated index page, where the first call to the index action sends the view with the first page of data and a button or element on the page triggers additional calls to the same action but this time only the next page of data is sent to the front end.

Installing the Plugin

To install this plugin, issue the following command from the root of your application in a CommandBox prompt:

install cfwheels-htmx-plugin

Once installed, reload your application and you’re off to the races.

Wheels CLI matures to Version 1.0

It’s hard to believe it took so long to get here but modern CFML development has come a long way thanks to tools like CommandBox and ForgeBox. The Wheels CLI is built as a CommandBox module and wouldn’t have even been possible without the support of the fine folks at Ortus Solutions.

The first commit to the repo for this project was committed back in July of 2016. It’s taken a while, that’s an understatement, to get here but Wheels itself jumped to 2.0, CommandBox matured, and we were able to put the plumbing in place to support the communication between the CLI and the running server. With nearly 300 commits in the repo, 25 commands in the CLI, and over 20 pages of documentation, it’s now time to take the alpha/beta label off send this baby out into the world.

Some of the more notable commands are wheels new to use our wizard to start a brand new project. With this command and the corresponding wheels generate app command, you can start a new Wheels project in a directory, specify the template to use, pick the CF engine to use, configure the datasource, and setup your reload password. In fact there’s a whole host of generate commands for every type of object you may want to create. There are a bunch of dbmigrate commands to interact with database migrations.

To install the CLI issue the following command:

box install cfwheels-cli

Don’t forget to check out the full CLI Commands section in the guides too.

CFWheels 2.3.0 Released

This is the official v2.3.0 release. It is dropping a little over a week from Release Candidate 1. We simply wanted to make sure the new CI/CD workflow was functioning before calling the release final. We feel confident that we’re good to mark this release as final. There are no new enhancement or bug fixes in this release from 2.3.0.rc.1.

Download Zip

If updating from CFWheels 2.2.x:

If should be an easy upgrade, just swap out the wheels folder.

Changelog

Please refer to release 2.3.0.rc.1 for details.

CFWheels 2.3.0-rc.1 Released

This version has been cooking for a while and there have been many contributors. But since this is my first release a the helm with a new CI pipeline in place, I felt more comfortable doing a Release Candidate first.

Download Zip

If updating from CFWheels 2.2.x:

If should be an easy upgrade, just swap out the wheels folder.

Changelog

View Enhancements

  • Adds association error support via includeAssociations argument #1080 – [Nikolaj Frey]

Bug Fixes

  • onerror handler should increase user defined requestTimeout value #1056 – [Adam Chapman]
  • deletedAt should also respect timestamp mode (UTC) #1063 – [David Belanger]
  • Fixes No output from Debug() usage in plugin test cases #1061 – [Tom King]
  • Development mode will now properly return a 404 status if view not found #1067 – [Adam Cameron, Tom King]
  • 404 status now properly returned without URL rewriting #1067 – [Adam Cameron, Tom King]
  • Internal Docs in ACF2018 should now not display duplicate categories [Tom King]
  • Internal Docs search now resets itself properly on backspace with empty value #982 – [Brandon Shea, Tom King]
  • ValidatesConfirmationOf() now correctly enforces prescence of confirmation property #1070 – [Adam Cameron, Tom King]
  • resource()/resources() now allows empty only property to utilise as non-route parent #1083 – [Brian Ramsey]
  • Handle XSS Injection in development enviroment – [Michael Diederich]
  • Fix params bug in CLI API [#1106] – [Peter Amiri]

Miscellaneous

  • Update Docker Lucee Commandbox version to 5.2.0 – [Adam Chapman, Tom King]
  • Minor internal obselete reference to modelComponentPath removed – [Adam Chapman, Tom King]
  • Minor visual fix for long migration logs overflow in modal (scroll) – [Brian Ramsey]
  • Add test suite for Lucee and H2 Database to the GitHub Actions test suite. – [Peter Amiri]
  • On going changes to update the H2 drivers [#1107] – [Peter Amiri]
  • Fixes some syntax formating introduced by cfformat [#1111] – [Adam Chapman]
  • Minimum ColdFusion version is now ColdFusion (2018 release) Update 3 (2018,0,03,314033) / ColdFusion (2016 release) Update 10 (2016,0,10,314028) / ColdFusion 11 Update 18 (11,0,18,314030) #923 – [Michael Diederich]
  • Wheels save(allowExplicitTimestamps=true) doesn’t produce the expected result [#1113] – [SebastienFCT]

Potentially Breaking Changes

  • Automatic Time Stamps: the deletedAt column was using the server’s local time for the timestamp while createdAt / updatedAt were using the timestamp selected for the timestamp mode. The default for CFWheels’ timestamp mode is UTC and therefore all future deletedAt timestamps will be in UTC unless you’ve changed the default. Please review any SQL that uses deletedAt for datetime comparison.

CFWheels Fully Embraces ForgeBox Packages

As you may know, many years ago CFWheels embraced the distribution of Plugins via ForgeBox packages instead of maintaining our own directory. But the framework itself remains illusive. There was some work done in the last few months to put up packages for the framework but those packages were being maintained by hand which made them a show stopper for a long term solution.

Well, thanks to a new CI workflow based on GitHub Actions we now have the building and publishing of the packages fully automated. Giving credit where credit is due, the new workflow borrows heavily from the ColdBox workflow. It used GitHub Actions, Ant, and CommandBox to automate the process.

So what does all this mean for you, let’s cut to the chase. This means you can now install a fresh copy of the framework using the following command:

box install cfwheels-base-template

This will pull down a copy of the latest stable release of the template files and then pull down a copy of the latest stable release of the framework via package dependencies. In fact the CI workflow mentioned about publishes two packages cfwheels which is the core framework directory and cfwheels-base-template which is all the other files you need to scaffold the framework.

We’ve even backfilled all the prior released versions of the framework all the way back to version 1.0.0. So you can install a particular version of the framework using the following command:

box install [email protected]

In addition you can install the bleeding edge which includes all the work in process towards the next major release using:

box install cfwheels-base-template@be

And if you ever just need to get a copy of the latest framework files simply use the following command:

box install cfwheels

All this means that upgrading to a newer version of the framework should be much easier going forward. Frankly you should just need to modify the version of the dependency in the box.json file and issue a box update command. But we’ll document that more fully when we make our next release.

For now please feel free to play with all this package goodness and let us know if we fumbled anything.

CFWheels 2.2 Released

It’s been a while coming. Can I blame the pandemic? Lots of nice little tweaks and fixes in this version. Please see the changelog for all details.

Download zip

If updating from CFWheels 2.1.x:

It should be an easy upgrade , just swap out the wheels folder.

If updating from CFWheels 2.0.x:

  • replace your wheels folder from the one in the download, and
  • outside the wheels folder, ensure you’ve got a file at events/onabort.cfm and create it if needed.
  • rename any instances of findLast() to findLastOne() (this has been changed due to Lucee 5.3 having a new inbuilt function called findLast() which clashes with the wheels internals)

As always, a huge thanks to all contributors – stay safe out there!

CFWheels 2.1 Released

Today sees the release of CFWheels 2.1. Only a couple of bug fixes since the beta, so please refer to the changelog for a list of all changes.

Download now (zip)

If updating from CFWheels 2.0.x:

  • replace your wheels folder from the one in the download, and
  • outside the wheels folder, ensure you’ve got a file at events/onabort.cfm and create it if needed.
  • rename any instances of findLast() to findLastOne() (this has been changed due to Lucee 5.3 having a new inbuilt function called findLast() which clashes with the wheels internals)

Happy Easter, and stay safe!

CFWheels 2.1.0-beta Now Available

It’s been far too long in the making, but the beta for 2.1 has now arrived! Please do check it out: this should be considered an essential upgrade for anyone on 2.x. A huge thanks to all have contributed!

Make sure to check the “Potenitally Breaking Changes” section below, and please report any bugs.

What’s New:

New GUI

Probably the most obvious change in 2.1 beta is the new internal user interface. Previously, Wheels internal pages like test outputs and routing tables could accidentally be broken by your app, as they extended your Controller.cfc by default. Now, they’re completely isolated, and have been significantly beefed up to show everything that you might want to look at as a developer. 

The new GUI has it’s own dedicated internal routes which can be accessed directly at /wheels/info (assuming you’ve got URL rewriting on) or via the usual links in the debug footer. 

It’s made up of six main sections:

  • General Wheels info – which displays all the settings for your development environment, such as datasources and other core configuration;   
  • a new routing table – which includes a handy route tester as well as a quick search;   
  • improved test outputs so you can more easily access unit tests for your app, core tests if you’re on the master branch, and plugin tests if they’re available;   
  • a new database migration interface, which allows for SQL previews, so you can actually check your migration is going to do what you think it’s going to do before executing;   
  • a more comprehensive documentation output, which includes your own autogenerated application docs as well as the internal wheels function references,   
  • and a better plugins list, showing all the information we know about any installed plugins. 

This GUI is only available in development mode by default, but can be enabled in other modes – this isn’t recommended, but it can be done if you really need to check the configuration or try and track something specific down. You can enable it via set(enablePublicComponent=true) in your environment specific configuration files. 

Improved CORS handling

Next up is some much needed and improved CORS handling. CORS, or Cross-Origin Resource Sharing, will be very familiar to any of you who have a javascript component or fully fledged progressive web app which runs on one domain, but needs to get information from another. A full explanation of CORS is probably beyond the scope of this post, but if you’re thinking of running your own API on a standalone domain where other applications talk to it, you’ll need to be able to handle CORS.

In Wheels 2.0, you could handle CORS requests, but they could only be configured in a very broad way. For instance, the CORS Allow Methods just returned all methods by default in an OPTIONS request, which kinda defeated the whole point.

The CORS Headers –  Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Credentials can now be directly set by their respective functions. 

However, for most people, a new helper configuration, accessControlAllowMethodsByRoute() will be the single most useful function to set: it allows for automatic matching of available methods for a route and sets the CORS Header Access-Control-Allow-Methods automatically; so now when your OPTIONS request hits your wheels app, it will actually respond with the available methods which are allowed for that resource endpoint. This makes it much easier to diagnose why certain requests might not get through, and means javascript libraries such as axios can respond more appropriately to hitting the wrong URL with the wrong HTTP method.

Improvements to mapper()

Redirects can now be put directly in the mapper() routing call, so you can quickly add a simple redirect for a route if you need.

mapFormat can now be set as false, which bypasses the additional ‘.[format]’ routes which were put in by default for each resource. So if you don’t use them, you can now just turn them off, and make your routing table a lot cleaner. This can be set either globally on the mapper() call itself, or on a per resource basis when using resources()

params._json if request is Array

Previously, if you POSTed or PUT/PATCHed json to an endpoint with an array as it’s root element, it would just get ignored, and you’d not be able to access it in the params struct. This has now been changed and if the incoming payload is a json array, it will be available at params._json which matches rails conventions.

New FlashAppend Behaviour

You can now change the default flash behaviour to append to an existing key, rather than directly replacing it. To turn on this behaviour, add set(flashAppend=true) to your /config/settings.cfm file. This allows you to more easily collect flash notifications during a request:
flashInsert(success="One");
flashInsert(success="Two");

Plugin performance

The plugins system has been reverted back to 1.x behaviour, as it was simply non-performant; more on this in a future post.

Full Changelog:

Potentially breaking changes

  • The new CFWheels internal GUI is more isolated and runs in it’s own component: previously this was extending the developers main Controller.cfc which caused multiple issues. The migrator, test runner and routing GUIs have therefore all been re-written.
  • The plugins system behaviour no longer chains multiple functions of the same name as this was a major performance hit. It’s recommended that plugin authors check their plugins to run on 2.1
  • HTTP Verb/Method switching is now no longer allowed via GET requests and must be performed via POST (i.e via _method)

Model Enhancements

  • Migrator now automatically manages the timestamp columns on addRecord() and updateRecord() calls – #852 [Charley Contreras]
  • Migrator correctly honors CFWheels Timestamp configuration settings (setUpdatedAtOnCreate, softDeleteProperty, timeStampMode, timeStampOnCreateProperty, timeStampOnUpdateProperty) – #852 [Charley Contreras]
  • MSSQL now uses NVARCHAR(max) instead of TEXT #896 [Reuben Brown]
  • Allow createdAt and updatedAt to be explicitly assigned using the allowExplicitTimestamps=true argument – #887 – [Adam Chapman]

Controller Enhancements

  • New set(flashAppend=true) option allows for appending of a Flash key instead of replacing – #855 – [Tom King]
  • flashMessages() now checks for an array of strings or just a string and outputs appropriately – #855 – [Tom King]
  • flashInsert() can now accept a one dimensional array – #855 – [Tom King]

Bug Fixes

  • Allow uppercase table names containing reserved substrings like OR and AND – #765 [Dmitry Yakhnov, Adam Chapman]
  • Calculated properties can now override an existing property – #764 [Adam Chapman, Andy Bellenie]
  • Filters are now correctly called if there is more than one after filter – #853 [Brandon Shea, Tom King, Adam Chapman]
  • Minor fix for duplicate debug output in the test suite – #176 [Adam Chapman, Tom King]
  • Convert handle to a valid variable name so it doesn’t break when using dot notation – #846 [Per Djurner]
  • The validatesUniquenessOf() check now handles cases when duplicates already exist – #480 [Randall Meeker, Per Djurner]
  • validatesConfirmationOf() now has a caseSensitive argument to optionally perform a case sensitive comparison – #918 [Tom King]
  • sendFile() no longer expands an already expanded directory on ACF2016 – #873 [David Paul Belanger, Tom King, strubenstein]
  • Automatic database migrations onApplicationStart now correctly reference appropriate Application scope – #870 [Tom King]
  • usesLayout() now can be called more than once and properly respects the order called – #891 [David Paul Belanger]
  • Migrator MSSQL adapter now respects Time and Timestamp Column Types – #906 [Reuben Brown]
  • Automatic migrations fail on application start – #913 [Adam Chapman]
  • Default cacheFileChecking to true in development mode – [Adam Chapman, Steve Harvey]
  • Migrator columnNames list values are now trimmed – #919 [Adam Chapman]
  • Fixes bug when httpRequestData content is a JSON array – #926 [Adam Chapman]
  • When httpRequestData content is a JSON array, contents are now automatically added to params._json – #939 [Tom King]
  • Fixes bug where Migrator $execute() always appends semi-colon – #924 [Adam Chapman]
  • Fixes bug where model createdAt property is changed upon update – #927 [Brandon Shea, Adam Chapman]
  • Fixed silent application.wheels scope exception hampering autoMigrateDatabase – #957 [Adam Chapman, Tom King]

Miscellaneous

  • Added the ability to pass &lock=false in the URL for when reload requests won’t work due to locking – [Per Djurner]
  • Basic 302 redirects now available in mapper via redirect argument for GET/PUT/PATCH/POST/DELETE – #847 – [Tom King]
  • .[format] based routes can now be turned off in resources() and resource() via mapFormat=false – #899 – [Tom King]
  • mapFormat can now be set as a default in mapper() for all child resources() and resource() calls – #899 – [Tom King]
  • HEAD requests are now aliased to GET requests #860 – [Tom King]
  • Added the includeFilters argument to the processRequest function for skipping execution of filters during controller unit tests – [Adam Chapman]
  • Added the useIndex argument to finders for adding table index hints #864 – [Adam Chapman]
  • HTTP Verb/Method switching is now no longer allowed via GET requests and must be performed via POST #886 – [Tom King]
  • CORS Header Access-Control-Allow-Origin can now be set either via a simple value or list in accessControlAllowOrigin() #888 [Tom King]
  • CORS Header Access-Control-Allow-Methods can now be set via accessControlAllowMethods(value) #888 [Tom King]
  • CORS Header Access-Control-Allow-Credentials can now be turned on via accessControlAllowCredentials(true)#888 [Tom King]
  • accessControlAllowMethodsByRoute() now allows for automatic matching of available methods for a route and sets CORS Header Access-Control-Allow-Methods appropriately #888 [Tom King]
  • CORS Header can now be set via accessControlAllowHeaders(value) #888 [Tom King]
  • Performance Improvement: Scanning of Models and Controllers #917 [Adam Chapman]
  • Added the authenticityToken() function for returning the raw CSRF authenticity token #925 [Adam Chapman]
  • Adds enablePublicComponentenableMigratorComponent,enablePluginsComponent environment settings to completely disable those features #926 [Tom King]
  • New CFWheels Internal GUI #931 [Tom King]
  • pluginRunner() now removed in favour of 1.x plugin behaviour for performance purposes #916 [Core Team]
  • Adds validateTestPackageMetaData environment setting for skipping test package validation on large test suites #950 [Adam Chapman]
  • Added aliases for migrator.TableDefinition functions to allow singular variant of the columnNames property #922 [Sébastien FOCK CHOW THO]
  • onAbort is now supported via events/onabort.cfm #962 [Brian Ramsey]

CFWheels 2.0.2 Security Release

Today sees a security release for the 2.x series.

It is strongly recommended to update to CFWheels 2.0.2 if you are running either 2.0.0 or 2.0.1. This issue does not affect 1.x releases. This release introduces a potentially breaking change, so you are encouraged to test your application appropriately before deploying. Thanks to Bryan Welter for bringing it to our attention.

Download 2.0.2

Potential Breaking Changes

  • Blank strings in SQL are no longer converted to null

CFWheels 2.0.1 maintenance Release

Today sees a maintenance release for the 2.x series.

Download 2.0.1 today to fix the following:

Bug Fixes

  • Fixes reload links on application test suite page – #820 [Michael Diederich]
  • Set dbname in cfdbinfo calls when using custom database connection string – #822 [Per Djurner]
  • Fixes humanize() function – #663 [Chris Peters, Per Djurner, kmd1970]
  • Enables the rel attribute for stylesheetlinkTag() – #834 [Michael Diederich]
  • Returning a NULL value from a query with NULL support enabled no longer throws an error – #834 [Michael Diederich]
  • Accessing a route with incorrect verb now provides a more useful error message – #800 [Tom King]
  • Fixed bug with arrays in URLs – #836 [Michael Diederich, Per Djurner]
  • startFormTag now properly applies the method attribute – #837 [David Paul Belanger]
  • Incompatible plugin notice now ignores patch releases unless specified – #840 [Risto, Tom King]