Showing posts with label coldfusion10. Show all posts

Boss MVC - A Lightweight Coldfusion MVC Framework

I don't normally do side-projects using ColdFusion, but one night some time last year, after drinking some neat vodka, I ended up playing around with ColdFusion and building an MVC framework.

The code isn't by any means production ready, and is only the product of two nights (and several glasses of vodka) but it was an experiment into what kind of nice MVC framework type functionality could be exposed in CF.  I have only ever used Fusebox, which as an MVC framework is found wanting in many instances, however, I know there are lots of other MVC frameworks in CF, that undoubtedly have nicer features.

Anyway, it's all over on my GitHub, but I thought I would re-post the details here. Just for funsies.

Some of the main things I wanted to experiment with were involved with simplifying of the framework and removal of boiler plate code. E.g. :

  • Annotation or config defined mapping for URL > Controller methods
  • Simple returns from controllers - just returning view name and model data (to remove the temptation of building HTML code in controllers - a clearer separation of controller and view
  • clearly & appropriately scoped variables in the views (everything to local, as under the hood, everything is being rendered from a CFC, so if you don't local scope the view then you may get surprises)

Controllers:

At the moment, it is dead simple to configure, just grab the framework and create a controller as follows (it assumes that there will be a directory in the root of your webapp called controllers)
/controllers/Home.cfc:
/**
 * @Controller
 **/
component output = "false"  {

    /**
     * @RequestMapping
     **/
    public Array function default( Struct resourceParams="" ){
        return [ "homepage" ];
    }
}
The above code will direct all requests to the application root ("/") to the default() function. The URL pattern for either the @Controller or @RequestMapping can be left blank (or populated) - the values provided are concatenated to produce the URL mapping for a function.
You can also have variable sections of the URL: /controllers/User.cfc
/**
 * @Controller /user
 **/
component output = "false"  {

    /**
     * @RequestMapping /:username
     **/
    public Array function default( Struct resourceParams="" ){
        return [ "userpage" ];
    }
}
In the above example, you can see that @Controller and @RequestMapping have a URL pattern defined. You will also note the @RequestMapping has a ":" before username - this indicates a variable section of the URL. This code will handle requests that hit the URL /user/rob etc - Any named variable section of the URL will be available to the controller function in teh resourceParams struct (as seen above).

Views:

View definition and resolution is also real simple (hopefully). Firstly, you can define a view layout by name - this defines a named view and CFM template, along with any templates that make up the overall view. This is all configured in JSON notation.
At startup, the framework scans the /views directory for any "*.config" files - any it finds it loads up, and then the views defined will be available for use in the application.
/views/view-layout.config:
[
    {
        "name": "homepage",
        "template": "home.pagetemplate",
        "include": {
            "header": "home.header",
            "body": "home.body",
            "footer": "home.footer"
        }
    },
    {
        "name": "userpage",
        "template": "user.pagetemplate",
        "include": {
            "header": "user.header",
            "body": "user.body",
            "footer": "user.footer"
        }
    }
]
The above defines two views, "homepage" & "userpage" - both are made up of similar components. The convention for the templates is relative to the /views directory, and uses "." instead of "/" (e.g. "template": "user.pagetemplate" will expect a file /views/user/pagetemplate.cfm ).
As you can hopefully work out from the above, the "homepage" view is based on /views/home/pagetemplate.cfm and is made up of the three templates /views/home/header.cfm, body.cfm, footer.cfm.
All nested templates are simply placed in the local scope (for the template being rendered), so our pagetemplate cfm looks like this:
/views/home/pagetemplate.cfm:
<html lang="en">
    <cfoutput>#local.header#</cfoutput>
    <body>
        <cfoutput>#local.body#</cfoutput>
        <cfoutput>#local.footer#</cfoutput>
    </body>
</html>
Once we have built a view definition, and have created our templates, a controller simply has to return the viewname as the first argument of an array. If we look again at our Home controller:
/controllers/Home.cfc:
/**
 * @Controller
 **/
component output = "false"  {

    /**
     * @RequestMapping
     **/
    public Array function default( Struct resourceParams="" ){
        return [ "homepage" ];
    }
}
We see that we just want to render the "homepage" view for all requests. Simple controller & simple view composition. Pretty boss.

Models:

Obviously, most of the time we will want to pass data (in the form of a model) to our views for rendering, this is also really simple!
We have seen that in the controller, if there are variable names in the URL that is passed to the controller function in the arguments.resourceParams struct - That data is also automatically passed through to the view template, but can be supplemented by any data accessed in the controller.
Let's look again at out User controller, but lets say we want to pass some additional data to the view for rendering:
/controllers/User.cfc:
/**
 * @Controller /user
 **/
component output = "false"  {

    /**
     * @RequestMapping /:username
     **/
    public Array function default( Struct resourceParams="" ){
        return [ "userpage", { greeting: "Yo!"} ];
    }
}
This time, we are returning a second element in the array - this is a Struct of the model data, this Struct could contain any data you have fetched in the controller that you want to pass to your view template for rendering. As we have a variable URL section (username) and are passing some model data back to the view, both of these can be used.
/views/user/body.cfm:
<h1><cfoutput>#local.model.greeting#</cfoutput> <cfoutput>#local.model.username#</cfoutput>!</h1>
The URL variable sections and the returned model data are all available to the templates in "local.model".

ColdFusion: StructCopy & Magic Structs

Quick post about a Coldfusion oddity I came across this week, whilst attempting to use CF's built in StructCopy function.

Coldfusion has two primary mechanisms to clone a Struct (that's a map to Java folk): StructCopy() and Duplicate().  StructCopy is a shallow copy, where as Duplicate is a full blown deep copy - so if you are attempting to clone a complex nested struct then duplicate is probably the function for you (although, beware, as you may expect it comes with some performance penalty!).


What I actually wanted to do was clone the URL scope (which is for all intents and purposes is just a Struct of key/value pairs of query string params in the URL).  I just wanted to clone the current URL scope struct of query params so I could alter the struct (add additional params etc) without actually affecting the URL scope (normal FP type stuff). As the URL scope is always just going to be a struct of String key/value pairs (Strings being immutable), I figured the shallow copy StructCopy function would do (the struct would always be a simple single level struct, and all key/values would be Strings - so any changes to them would not affect the original URL scope).


Oh no. It doesn't work.

To be fair to CF, the URL scope isn't a straight forward Struct - it is actually a Coldfusion URLScope object - but just masquerades as a Struct most of the time, writeDump()'ing it labels it as a Struct, passing it to a function that requires a Struct argument - no probs.

Here is some example code:

In the above scenario, after the structDelete - both Structs output as the same thing. The key "rob" has been removed from both Structs. Well actually, just the one. But actually the cloned struct isn't cloned at all, it's just the original URL scope again.


To me, that sucks. Really.

Like I said, I get that URL is not really a true struct, so I don't blame CF for not wanting to play nicely (although duplicate( url ) will work), but returning the URL structure? not cool.

There are a few options that I see can happen if CF doesn't want to StructCopy:
  • Throw an exception. To me, this is the best option. Everyone knows where we are, and really it is an exception - if we are saying truly, that StructCopy cannot copy a URLScope object, then its an exception.
  • Return an empty struct - not ideal, but again, forces handling of this potential - kind of StructCopy saying, look guys, I tried to copy but failed, so here's an empty struct.

In no circumstance is it cool to just silently return the URLScope object.  Appearing to be working correctly (you get a what looks like a struct back, with all the same keys/values as your original struct, so all good right?) but actually providing the exact opposite functionality you actually want. Dangerous.

I was lucky that the code I was writing flagged up the errors immediately in an obvious fashion - but if you are cloning just to avoid an unusual edge-case, you may be in for an unpleasant suprise.

Handsome - A ColdFusion Dashboard

A while ago I came across a dashboard webapp made by Shopify called Dashing. It's a fairly simple concept, it's a web dashboard using Ruby & some slick js libraries to create your own custom dashboard with stats/graphs/etc.  The Shopify guys had made it for use internally showing their own stats and had open sourced.

I had a look around, and it looked pretty nice, and I was interested in how they had put it together, so decided to have a go at making my own version of it.

Obviously the real work is in making all the different widget adaptors so they can be re-used & fed any data needed to make it highly customisable - the kind of work that can only be done/worthwhile if working on an actual product with real data/use-cases.

You can see an example of the Shopify guys library over on Heroku (when it's running) - and their code is on their GitHub.

The version I built was originally built with ColdFusion 10 and a bunch of javascript libraries - including backbone.js.  Here are some screenshots - it's pretty simple, just some drag & drop components with pretty colours and widgets - so far an RSS reading widget, a line chart, a gage and a basic text widget(latest tweet).




My code is all here. My demo is here (demo is actually the same code dropped into a quick spring app, as I don't have CF hosting) - running on AppFog - when it's running.  I actually wrote this some time ago last year, but figured I might as well jam it up here, with some screenshots