Intro to jqGrid Part 5: Search

At this point we've created a basic grid, filled it with data, refined the display of our columns and added event handlers to handle multiselect options. We've used custom cell formatters, used a custom datatype function, and even added and populated a toolbar in the process. Now let's start looking at some things that aren't necessarily jqGrid specific, but incorporate them for use in our grid. How about a search?

jqGrid includes some things for doing data search, that will automatically build modal windows and stuff. But sometimes you want to format things your own way, or incorporate jqGrid for use within an existing interface. One of the advantages for us, using the datatype function, is that we can preprocess our postdata prior to the ajax request.

[More]

Intro to jqGrid Part 4: Event Handling

By now our demo is really beginning to flesh out a bit. You've probably created your own grid, read through some of the extensive documentation, and started to figure a few things out on your own. In our series, we've created a basic grid, populated it with remote data, and refined our column configuration a bit. This is a lot of information, encompassing in depth explanations on many key basic grid configuration options, writing our paging query, ajax data handling, and more. Now, let's get into some 'grid' stuff that might not be so obvious.

Toolbars

First, we're going to set ourselves up for the future. We're going to want to see some data, but in order to do so we're going to need some buttons to click on. This is a good time to talk about jqGrid's toolbar implementation. jqGrid makes it easy to add toolbars to it's display, but it's a bit of a departure from standard JQuery plugin behavior, as it doesn't allow you to create a toolbar from an existing DOM element, so you have to use script to define and create your toolbar and it's associated elements. The first thing you have to do is add the configuration attribute to your jqGrid config object:

jqGridDemo.js - Toolbar Config

view plain print about
1grid.jqGrid({
2    ...
3    toolbar:[true,"top"],
4    ...
5});

The toolbar config option takes a two element array for an argument. The first (boolean) element enables the toolbar. The second element defines the toolbar's placement. This value can be "top", "bottom" or "both". Adding this configuration attribute will cause jqGrid to automatically create a div for each toolbar defined. It uses a specific naming convention, every time, to create the toolbars: top - "t_" + grid element's id, bottom - "tb_" + grid element's id (i.e.: "t_gridTest"). If you ran your template now, you would see that an empty toolbar has been added to your grid display:

Now that we have a toolbar, we need to add some buttons. JQueryUI has a button object, for nice consistent buttons, so we'll create the DOM elements needed on the toolbar first, then make them into JQUI buttons.

jqGridDemo.js - Toolbar Buttons

view plain print about
1$('#t_gridTest').append('<button id=\"addButton\">Add</button><button id=\"exportButton\">Export</button>').addClass('customToolbar');

jqGridDemo.js - JQUI Buttons

view plain print about
1$('button#addButton').button({icons: {primary: 'add'}}).click(function(ev){
2    ev.preventDefault();
3    
4    return false;
5});
6
7$('button#exportButton').button({icons: {primary: 'page_white_go'}, disabled: true}).click(function(ev){
8    ev.preventDefault();
9    
10    return false;
11});

Here we've created an Add and an Export button on our toolbar, while also adding a new CSS class (customToolbar). Then I made JQUI buttons of our new toolbar buttons, applying appropriate icon classes (defined in our css file). I've also disabled the Export button for the moment, but we'll go into that later.

OK, now we have a toolbar with some nice, pretty buttons on it, let's start talking about some more grid-centric things, like selection models. Data grids serve a wide range of purposes. In the basic model, we were just viewing data, even if we did allow for sorting and paging. We could've just created a table in DOM and used jqGrid's tableToGrid method to make a simple view table. But, chances are you're writing a Data Grid implementation to interact with data. And a common action is to select a record (or records) to interact with.

Multiselect

There are, essentially, three types of selection actions with a Data Grid: individual record selection, multiple record selection, and specific cell selection. The act of any of these selections is an event, and jqGrid includes the ability to add custom Event Handlers directly to the grid configuration. In our implementation, I want to show working with multiple selections, so we need to add a few configuration options. First, let's setup for multiselection:

jqGridDemo.js - Multiselect Configuration

view plain print about
1grid.jqGrid({
2    ...
3    multiselect: gridMultiSelect >
0,
4    multiselectWidth: 25,
5    ...
6});

These are direct grid configuration options. The first, multiselect, is a boolean value that, if true, will automatically create a column of checkboxes on our grid. The second option, multiselectWidth, defines the width of that checkbox column. Notice that I've tied the multiselect attribute to the gridMultiSelect global variable I added a few posts back. If we change that global variable now (gridMultiSelect = 1) you will see it's effects when you reload the page.

And now we have checkboxes for multiple record selection. The user can select, or deselect, records at will, or even 'select all' by clicking the checkbox in the header column (NOTE: this only selects all currently loaded records in the grid). That gridMultiSelect variable is also an offset value, when calculating column positions in our JSON remapping. The inclusion of the checkbox column (multiselect) requires us to shift our positions by one space.

Now there are two things we have to think about: 1) What do we do when a user clicks a row (or selects 'all')? and 2) How do we know what's been selected?

Well, there are all kinds of things you can do with the records selected. What we'll talk about, in our example, is correcting a small issue with jqGrid itself. Let me explain. jqGrid keeps an array of id's, for all selected records, in the read-only grid attribute selarrrow. You can programmatically access those at any time by grabbing that variable from jqGrid.

view plain print about
1grid.jqGrid('getGridParam','selarrrow');

Id's

But if you play with it a bit you'll discover a few things. The first thing you'll figure out is that we aren't (yet) getting the ID of our records, getting a generic rowcount for our id instead. This is because we've used the function datatype, and remapped our JSON to our column configuration, so the id attribute of the jsonReader is basically being ignored. (I've presented a fix for this to the jqGrid team, which they will hopefully implement in the future.) For this reason, we have to set a key in our column model. This will setup an internal keyIndex variable that jqGrid uses in it's processes. Under normal circumstances, the key attribute would be defined on your ID column in the column model. When remapping JSON output, though, this isn't how this would be accomplished. You would need to review your incoming JSON, figure out the index of the ID column in the response, then apply that key attribute to that column index in the column model configuration. In our example the ID column is still first, lining up correctly, but in more complex column models and queries this can be different. Here's how you add that to the column model configuration.

jqGridDemo.js - Column Model Configuration

view plain print about
1colModel: [
2    {name: 'ID', hidden: true, key: true},
3    ...
4],

The importance in this can be viewed in the output. Here's a sample of the before and after difference in what jqGrid generates:

Grid Row Output - Before Key Definition

view plain print about
1<tr id="1" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">

Grid Row Output - After Key Definition

view plain print about
1<tr id="FF318BAB-3048-71C2-17E1634637074ECF" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">

Why is this important? When you go to the next page of records, if you hadn't applied the key then the first record displayed would also show an id of 1. And this now brings us to the bit that we're going to address with jqGrid. If you select a few rows, pull the selarrrow, then page and select some more and check again, you will see that jqGrid isn't tracking id's across paging requests. This means that, if you go back to page 1, your original selections are no longer checked. For some this may be ok, but many users will expect different behavior. This is part of a developer's life, is anticipating user behavior, and adjusting to meet good usability guidelines.

Event Handlers

So, what is expected behavior? If a user selects a record, then goes to another page, and then returns to the original page, then the user will expect their selections to have been maintained across paging requests. This becomes especially important to users working with very large amounts of data. So, how do we handle this? Well, we setup an Event Handler for our selections, and programmatically control it. First, let's create another global variable to hold selected id's.

view plain print about
1var gridCols = {set:false},
2    gridMultiSelect = 1,
3    selArr = [];

This is just an array, just like jqGrid uses internally. Next thing we'll do is setup some Event Handlers. Any time a selection (or deselection) is made, then we need to control the contents of our new selArr array. First we'll setup a method to apply to our onSelectRow grid Event Handler.

jqGridDemo.js - rowSelectionHandler

view plain print about
1var rowSelectionHandler = function (id, status) {
2    // process code here
3};

jqGridDemo.js - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    onSelectRow: rowSelectionHandler,
4    ...
5});

A review of jqGrid's Events documentation shows that the onSelectRow attribute will apply an event handler, and passes two arguments: id - the record id of the row being selected, and status - a boolean value noting selection (true) or deselection (false). jqGrid's onSelectAll attribute is similar, passing idArr - an array of selected row id's, and status - if all were selected or deselected. We'll setup a handler for that as well.

jqGridDemo.js - selectAllHandler

view plain print about
1var selectAllHandler = function (id, status) {
2    // process code here
3};

jqGridDemo.js - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    onSelectAll: selectAllHandler,
4    ...
5});

Now, it may be that we'll want to use these two methods for something other than keeping track of our new array. Ultimately, the work for handling our new array is the same for either, we just have to code in handling the difference between a single id being passed, or an array of id's being passed. We'll setup a separate method for the array manipulation, and call it from our new handler methods.

jqGridDemo.js - Handlers and selectionManager

view plain print about
1var rowSelectionHandler = function (id, status) {
2    selectionManager(id, status);
3 // anything else
4};
5
6var selectAllHandler = function (idArr, status) {
7    selectionManager(idArr, status);
8 // anything else
9};
10
11var selectionManager = function (id, status) {
12    // was it checked (true) or unchecked (false)
13    if(status){
14        // if it's just one id (not array)
15        if(!$.isArray(id)){
16            // if it's not already in the array, then add it
17            if($.inArray(id,selArr) < 0){selArr.push(id)}
18        } else {
19            // which id's aren't already in the 'selected' array
20            var tmp = $.grep(id,function(item,ind){
21                return $.inArray(item,selArr) < 0;
22            });
23            // add only those unique id's to the 'selected' array
24            $.merge(selArr,tmp);
25        }
26    } else {
27        // if it'
s just one id (not array)
28        if(!$.isArray(id)){
29            // remove that one id
30            selArr.splice($.inArray(id,selArr),1);
31        } else {
32            // give me an array without the 'id's passed
33            // (resetting the 'selected' array)
34            selArr = $.grep(selArr,function(item,ind){
35                return $.inArray(item,id) >
-1;
36            },true);
37        }
38    }
39    $('#t_gridTest button#exportButton').button((selArr.length >
0)?'enable':'disable');
40};

I've tried to comment the code above to explain our new selectionManager method. The only line I haven't explained is the last, which either enables or disables the Export button in the toolbar, depending on if any items are selected or not. Now, if you made selections, paged through and made other, deselected items, even selected 'all', you could look at the selArr variable and see that we are now tracking all selected records across paging requests. The only thing missing is that these records aren't selected (visually) when you return to a page. We can rectify that by applying a handler to our gridComplete attribute.

jqGridDemo.js - - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    gridComplete: gridLoadInit,
4    ...
5});

jqGridDemo.js - gridLoadInit

view plain print about
1var gridLoadInit = function () {
2    // if the 'selected' array has length
3    // then loop current records, and 'check'
4    // those that should be selected
5    if(selArr.length >
0){
6        var tmp = grid.jqGrid('getDataIDs');
7        $.each(selArr, function(ind, val){
8            var pos = $.inArray(val, tmp);
9            if(pos > -1){
10                grid.jqGrid('setSelection',val);
11            }
12        });
13    }
14};

jqGrid's gridComplete attribute allows us to define an Event Handler that fires once data has loaded into the grid (this is not to be confused with loadComplete, which occurs after every server request.) What we're saying here is, after the data is rendered in the grid we will loop the selArr array and 'check' any id's that match any records displayed in our grid. This gridLoadInit method, that we've created, will now run anytime the grid's data is reloaded (initial load, paging requests, sorting, etc).

And so we've created a solution to rectify a small oversight within jqGrid. In the process we covered the importance, and the process, behind properly identifying a key column, in setting proper row id's, how to add a checkbox column through the multiselect attribute, how to apply event handlers to our grid, as well as adding a toolbar and filling it with controls. Next post we'll get to binding event handlers to our Action column icons, and search for some other nuggets to impart. Until then, I hope this all helps someone, and sample code is located in the Download link at the bottom of the post.

2011 In Review, and the View for 2012

My, how time flies when you're having fun! It seems like only yesterday that I was welcoming in 2011, and now we're here a year later. So many things have happened in the last year, and rereading that post I see that I missed some things I should've done, but let's take a look in retrospect.

I wrote 27 blog posts in 2011. This is nothing, compared to guys like Ray Camden or Ben Nadel, but for me it was quite a bit, especially when you consider that between March and August I released only one post. Very early in the year, I began a series on creatingmany sites with one codebase. In the process, the series has evolved to contain a fairly detailed primer in ColdFusion application architecture (because of it's importance to this process), has currently spanned 8 separate posts, and was even referenced by Sean Corfield in his great presentations on the same topic. 2012 will see the completion of that CF app discussion, and gradually move it back to the MSOC topic itself, as there is still a ton to talk about there, and a lot of interest in the topic. I also began a series on the jqGrid JQuery plugin. jqGrid is another Data Grid visualization tool (I have now written about three, including Ext JS and DataTables), and is a clear choice for those who must use JQuery. (To be fair, JQueryUI is working on a grid component, but they are still behind the curve, and way behind Sencha.) Finally, one common thread seen in the majority of my posts, is how much I've embraced cfscript. I wrote a lot of things, on a variety of topics, but most of my code examples were pure scripted examples.

Now let's talk about some other departures from the norm for Cutter.

You did not see a lot of content around Ext JS. In fact, I stopped writing Ext JS books. This is not, in any way, a reflection on my feelings for Ext JS. I still believe that Sencha has built one of the best client-side libraries for web application development. In evaluating the overall ROI, I realized that I was writing more for the community than the money, and that my reach was greater through my blog, while giving me flexibility on when and what I deliver from a content standpoint. That said, I didn't have a single project this year that used Ext JS, so had very little time to experiment and write about it. This year, I'm going to expand on a personal project, and get back to some great Ext JS content for my readers.

You, also, did not see me speak at any conferences this past year. Nor at any user group meetings. This wasn't because I didn't want to, but because of some more personal reasons. I'm not going to go in depth here, other than to say that I've had some long standing health issues that required me to have some surgery done on my mouth. (Mark Drew is making a joke right now...) Aside from the fact that this has been very costly (chewing up any conference/travel budget), it also meant that my speech has been affected for a good part of the year. Thankfully this experience is (mostly) over now, and I hope to get back to presenting sometime this year. Any user group looking for a speaker this year, please contact me through the Contact link on this blog.

One group I am hoping to speak to this year is the Northeast Florida CFUG. I have to call Mike back, but he's looking to get things kicked off again, and I want to help it be successful. If you're in or around the Jacksonville area, make sure to keep an eye on the site for upcoming events.

One other thing I'm looking to do is to migrate all of my projects into GitHub. I've been using Git at work, and I am loving it, and I think combining GitHub with RIAForge is a great way to promote the terrific technologies we work with every day. I will make the time, I promise.

This comes to the final discussion of this post, Adobe. I again had the pleasure of being an Adobe Community Professional this past year. Due to my health issues, I didn't get to do everything I would've wanted to this year, but I've tried to be a good supporter. There are some fabulous things coming in ColdFusion Zeus and, by extension, to ColdFusion Builder as well. There has been a lot of hub-bub over Adobe's communications flubs regarding Flash, mobile, and Flex. I've avoided much of the discussion, other than to say "be patient and watch". Flash isn't going away, and neither is Flex. HTML 5 is a beautiful thing, if you aren't developing desktop browser applications (i.e. You're only writing for mobile/tablet development). There, that is my whole contribution to that discussion. Give it a rest.

2012 will be a fantastic year. Set yourself some clear, definable goals. Break them down, step by step, and write the steps down on paper. Each successive step, print out in large letters and place it somewhere where you will see it each and every day. Set yourself up to succeed, and you will. Have a great year, everyone, and I can't wait to hear what you have planned for 2012.

Intro to jqGrid Part 3: Columns

OK, in our last two posts we built a basic grid and populated it with data. As you can see, so far it's a very basic grid.

Nothing much to it, really. Let's start adding a few important pieces. jqGrid includes a large set of Column Model Options, but there really aren't a ton that you'll need. Here we'll run through some basics.

First, the id field really isn't something you typically need to show anyone. So, just hide it.

jqGridDemo.js - Column Model - Hide

view plain print about
1colModel: [
2    {name: 'ID', hidden: true},
3    ...
4],

Oh yeah, and the views column is a count of the number of page views. As a number, it should probably be right justified.

jqGridDemo.js - Column Model - Align

view plain print about
1colModel: [
2    ...
3    {name: 'VIEWS', align: 'right'}
4],

Great! But that column is way too wide! Without any additional info, jqGrid will attempt to size the columns according to their data, and currently it's just making three even columns. Let's size it down.

jqGridDemo.js - Column Model - Width

view plain print about
1colModel: [
2    ...,
3    {name: 'VIEWS', align: 'right', width: 60, fixed: true}
4],

Alright! We've set a 'fixed' width, so that any resizing of the grid (even automatic resizing) will maintain the set column width. We set it to give full width of the column title, as well as some room for the sort markers when the column is being sorted.

Now let's talk about three options that can be somewhat confusing: index, label and name. Up until now we've used the name option, which has mirrored the column name being returned. However, we might want our column header to be different than the actual column name. For this, we use the label option.

jqGridDemo.js - Column Model - Label

view plain print about
1colModel: [
2    ...,
3    {name: 'POSTED', label: 'Release Date'}
4],

This changed the label used in the column header, while maintaining a reference used when sorting the grid by the posted field. This is good, until you do something like this:

jqGridDemo.js - Column Model - Remap

view plain print about
1grid.jqGrid('setGridParam',{remapColumns:[
2    gridCols['ID'] + gridMultiSelect,
3    gridCols['ID'] + gridMultiSelect,
4    gridCols['TITLE'] + gridMultiSelect,
5    gridCols['POSTED'] + gridMultiSelect,
6    gridCols['VIEWS'] + gridMultiSelect
7]});

jqGridDemo.js - Column Model - Index

view plain print about
1colModel: [
2    {name: 'ID', hidden: true},
3    {name: 'Action', index: 'ID', label: 'Action', width: 80, fixed: true, sortable: false, align: 'center'},
4    {name: 'Title'},
5    {name: 'Posted', label: 'Release Date'},
6    {name: 'Views', align: 'right', width: 60, fixed: true}
7],

"Cutter, What are you doin' ta me!?!" Yeah, now it's confusing. I've added a column. A column that also references the ID field in the return dataset. In this instance the index really isn't truly necessary, but I'll try to explain it for you anyway. Up until now, jqGrid has used the name option as the value that is passed back to the server on a sort request. Here's the thing though: each column has to have a unique reference. That's what the name option is for; being a unique column reference within jqGrid. So, if you have two columns whose underlying data is the same (as it shows you in our new Remap config), then you need a unique reference for jqGrid (the name), and the index field reference that jqGrid will send back to the server on sort requests (again, with the sortable: false I've thrown in here, it's really moot for us). So, to recap:

  • name - A unique column reference used by jqGrid
  • index - A data field reference used in sort requests. If not present then the name is used.
  • label - If present it will override the name option, for what to display in the column header.

You probably noticed that I added a little something to our column remap code.

view plain print about
1gridCols['ID'] + gridMultiSelect,

This goes along with a new variable I added to our global variable declarations at the very top of our script.

view plain print about
1var gridCols = {set:false},
2    gridMultiSelect = 0;

I'll probably not use that on this round, but it will become important, so I'll leave it for now.

Column Formatting

Now that we've talked about some of the more important column options, let's get into column formatting. Now that we've added some configuration you'll notice a new Action column. Right now, if you ran your template, you'd see a truncated ID value in the cells. We'll need that ID in our output, but the Action column we're building will be used to display action icons (edit, delete, etc). jqGrid has functions for doing this, if you're using it's edit packages, but my app has custom editors for a lot of this data, so we'll apply a custom column formatter to show these action icons.

jqGrid provides predefined formatters for many things, but you can also create your own custom formatters to create your own cell templates. A custom formatter is just a function, applied through the column model, that returns the string to be displayed in the cell. Your function will take three arguments, cellvalue, options, and rowObject. The cellvalue is the value of the data that jqGrid is trying to apply to the cell (in our case, a record's ID). The options is an object containing the rowId and the colModel of the record being applied. The rowObject is the data for the entire row of the record being applied.

jqGrid provides the ability to apply these functions as extensions of it's built in formatter package. Let's write a basic actionFormatter function that returns just the first two characters of the ID field, to get started.

jqGridDemo.js - actionFormatter - figure 1

view plain print about
1$.extend($.fn.fmatter, {
2    actionFormatter: function(cellvalue, options, rowObject) {
3        return cellvalue.substr(0,2);
4    }
5});

jqGridDemo.js - Column Model - Custom Formatter

view plain print about
1colModel: [
2    ...
3    {name: 'Action', index: 'ID', label: 'Action', width: 80, fixed: true, sortable: false, align: 'center', formatter: 'actionFormatter'},
4    ...
5],

That was easy! You see now how we get to value being applied to the cell. Now let's really change it up, by applying the custom output we discussed before. First, we need the style references to the icons we're going to use.

jqGridDemo.css - icons

view plain print about
1/* Basic layout of all trigger icons */
2.icon-trigger { margin: 2px; vertical-align: middle; display: inline-block; width: 16px; height: 16px; }
3.action-trigger { cursor: pointer; }
4.disabled-trigger {opacity:0.4;filter:alpha(opacity=40)!important;}
5
6/* delete icon image for trigger */
7.delete { background: url('/resources/images/icons/delete.png') no-repeat scroll 0px 0px transparent !important; }
8
9/* pencil icon image for trigger */
10.pencil { background: url('/resources/images/icons/pencil.png') no-repeat scroll 0px 0px transparent !important; }

For our demo, I'm using the highly useful FamFamFam Silk icon library. Here I've defined some classes for the display of icon 'triggers', or icons that are used as buttons for actions. Next, we'll adjust our actionFormatter to apply the proper output.

jqGridDemo.js - actionFormatter - figure 2

view plain print about
1$.extend($.fn.fmatter, {
2    actionFormatter: function(cellvalue, options, rowObject) {
3        var retVal = "<span class=\'icon-trigger action-trigger pencil\' rel=\'" + cellvalue + "\' \/>";
4        retVal += "<span class=\'icon-trigger action-trigger delete\' rel=\'" + cellvalue + "\' \/>";
5        return retVal;
6    }
7});

As you can see, now when you re-run your template you have a nice, formatted Action column, with action icons for 'edit' and 'delete'.

So, in this post we covered some of the more important Column Model display options, as well as creating a custom column formatter. In our next entry we'll tie some functions to our 'action icons', and talk about row selection options. You can find sample code attached in the Download link at the bottom of the page.

Intro to jqGrid Part 2: Getting the Data

As I mentioned in an Intro to jqGrid, sometimes you have to deal with remote data that isn't in the 'standard' JSON recordset format. I also like to reuse my server-side code, and prefer not to unnecessarily hack native data to meet a client-side need. For this reason, it is often necessary to write client-side methods that parse the server-side native format. Unfortunately, many JQuery plugins only handle the 'standard' JSON recordset format:

[More]

Intro to jqGrid

While there are better choices (ExtJS for instance) for component and architecture libraries, some time ago someone entrenched our current system in JQuery and JQueryUI. But, since JQueryUI is already so prevelent within the system, it would be very time consuming to replace it. JQueryUI isn't bad, by any means, just incomplete, from the standpoint of building "applications". "But, JQueryUI is the bomb! How can you say such things?" Well...

[More]

How I Do Things: ColdFusion and Ajax Requests

I get a lot of questions about handling Ajax requests. What are some best practices? How do you format your requests? How do you taylor your functions in ColdFusion? Those sort of questions. We (the ColdFusion community) have embraced Ajax. Many of us spent so much time working mostly server side code on top of simple (and often poorly thought out and formatted) forms, but user habits have changed. And so have we. You have to move with the times to keep up in the digital rat race.

Let's start with the stuff most CFers already know; the server side stuff. When we make an Ajax request, most often we will hit a function within a CFC. We need data. You can hit a .cfm page, and return a bunch of preformatted HTML, but often that's a lot of data transfer when your really only need a few bits of data. HTML code can get heavy, what with all the tags and such. Similarly, you could return data in XML form (WDDX is native in ColdFusion). XML is sometimes a required format, for certain requests, but like HTML it can often be overly verbose. When working with server side requests from the client, you want to minimize data transfer as much as possible. That's why Flash performs best using the AMF format, and why most Ajax applications work with JSON.

When constructing your ColdFusion functions for remote requests, you don't have to write functions that can only be used via Ajax. In fact, that would probably be bad practice, as it would really minimize the code reusability of your functions. Basically, the only true requirement, at the server side, is that you set your function's access attribute to remote.

view plain print about
1/*    
2 *    COMPONENT SomeComponent
3 *    Just some example component
4 *
5 *    @name SomeComponent
6 *    @displayName SomeComponent
7 *    @output false
8 */

9component {
10    /*
11     *    FUNCTION SomeFunction
12     *    This is just some function you've written
13     *
14     *    @access remote
15     *    @returnType struct
16     *    @output false
17     */

18    function SomeFunction(required string arg1, required numeric arg2) {
19        var retVal = {"success" = true, "message" = "", "data" = ""};
20        // some function stuff here
21        return retVal
22    }
23}

That's it. Nothing to it. Did you see the required line? The one you need for making a remote request of this function?

view plain print about
1*    @access remote

Ok, we're off and running. Sort of. "Hey, Cutter, what's with returning a struct? I thought we were working with JSON?" Well, let me explain...

Have you ever worked on some Ajax call, loaded the page up in the browser, and nothing happened? You change variables, do this and that, and get nowhere? Finally you open up Firebug, watch the request, and see that you CF code has errored out for some reason. Instead of data coming back, the browser is getting the thousands of lines of HTML and Javascript that is the ColdFusion error display. In the immortal words of Charlie Brown: AAUUGGHH!

What happens if your user sees this behavior? Some variable isn't getting set when you think it is, or some db guy added or removed a column from your database without your knowledge. An error gets thrown, and you never even realize it. Ouch! Users are quick to abandon you. How can you gracefully handle these things?

Well, how do you deal with error handling within your application? If this answer is "I don't", then you might want to explore another career path. No, just kidding (kind of). If you don't use error handling, this is the perfect scenario to start. There is no reason why you're users should see (or, in this case, not see) these types of issues within your application. By carefully thinking things out, you can provide a better user experience.

view plain print about
1/*
2     *    FUNCTION SomeFunction
3     *    This is just some function you've written
4     *
5     *    @access remote
6     *    @returnType struct
7     *    @output false
8     */

9    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
10        var retVal = {"success" = true, "message" = "", "data" = ""};
11        var q = new Query();
12        try {
13            // do your query stuff, setting the result to the "data" key
14            if (!retVal["data"].recordCount) {
15                throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
16            }
17        } catch (any excpt) {
18            LogTheError(excpt); // Custom error logging for us
19            retVal["success"] = false;
20            if (!ARGUMENTS.debug) {
21                retVal["data"] = ""; // Clear the "data" key, so we don't pass unneeded bits back to the client. Override this by passing 'debug' into the request.
22                if (excpt.type eq "ourCustom") {
23                    retVal["message"] = excpt.message;
24                    retVal["errorCode"] = excpt.errorCode;
25                } else {
26                    retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
27                    retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
28                }
29            } else {
30                retVal["message"] = excpt.message;
31                // and anything else you might want
32            }
33        }
34        return retVal;
35    }

Now, there are probably better ways to write your catch statements, but I'm just trying to lay some foundation here. The basic idea is, taylor a message to send back to your users in the event of a failure. "ErrorCode?" Many enterprise products have custom error codes they use to denote that a specific error has occurred (like the above 'RC_000' for no returned records).

So, we have a success key, denoting whether the requested ColdFusion function did what we wanted. We have a data key that we use for returning the data generated by the request. And, we have a message key that we may, or may not, use only in the event of an error. We've also used and errorCode in this scenario, only in the event of an error. "Cutter, something doesn't look right?"

view plain print about
1// You use the quotes and this struct access notation to preserve the casing of key names, which is important in JavaScript
2    var retVal = {"success" = true, "message" = "", "data" = ""};
3    ...
4    retVal["message"] = excpt.message;

That should be the basics of the server side stuff. But, then the question comes, "What if our model isn't under the web root? What if the model CFC's aren't web accessible?" OK, fair enough. Then you'll need a proxy CFC. Let's regroup a sec, and show what this might look like.

view plain print about
1/*    
2 *    COMPONENT SomeComponent
3 *    Just some example component
4 *    This example sits outside the webroot, in a 'com' directory that's mapped in the CFIDE
5 *
6 *    @name SomeComponent
7 *    @displayName SomeComponent
8 *    @output false
9 */

10component {
11    /*
12    *    FUNCTION SomeFunction
13    *    This is just some function you've written
14    *
15    *    @access public
16    *    @returnType struct
17    *    @output false
18    */

19    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
20        var retVal = {"success" = true, "message" = "", "data" = ""};
21        var q = new Query();
22        try {
23            // do your query stuff, setting the result to the "data" key
24            
25            if (!retVal["data"].recordCount) {
26                throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
27            }
28        } catch (any excpt) {
29            LogTheError(excpt); // Custom error logging for us
30            retVal["success"] = false;
31            if (!ARGUMENTS.debug) {
32                retVal["data"] = ""' // Clear the "data" key, so we don't pass unneeded bits back to the client. Override this by passing 'debug' into the request.
33                if (excpt.type eq "ourCustom") {
34                    retVal["message"] = excpt.message;
35                    retVal["errorCode"] = excpt.errorCode;
36                } else {
37                    retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
38                    retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
39                }
40            } else {
41                retVal["message"] = excpt.message;
42                // and anything else you might want
43            }
44        }
45        return retVal;
46    }
47}

And the remoting proxy...

view plain print about
1/*    
2 *    COMPONENT SomeComponent_Remote
3 *    Just some example proxy component
4 *
5 *    @name SomeComponent_Remote
6 *    @displayName SomeComponent_Remote
7 *    @output false
8 *    @extends "com.cc.Examples.SomeComponent"
9 */

10component {
11    /*
12     *    FUNCTION SomeFunction
13     *    This is just some function you've written
14     *
15     *    @access remote
16     *    @returnType struct
17     *    @output false
18     */

19    function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
20        return Super.SomeFunction(argumentCollection = ARGUMENTS);
21    }
22}

"Wait a second! I thought you said this post was about Ajax? Where's the JSON? Where's the Javascript?" Well, Ajax is useless without the data, right? Now that you have your CFC's and functions setup, let's look at the Javascript to make our requests. First we'll look at a basic JQuery Ajax call. First, let's set a global JS variable in the page.

view plain print about
1<cfscript>
2    param type = "boolean" name = "URL.debug" default = false;
3    
4    savecontent variable = "REQUEST.adder" {
5        WriteOutput('<script type="text/javascript">var debug = #URL.debug#;</script>');
6    }
7    REQUEST.addHeaderOutput &= REQUEST.adder;
8
</cfscript>
9...
10<cfhtmlhead text="#REQUEST.addHeaderOutput#" />

Next we'll setup our Ajax call.

view plain print about
1$.ajax({
2    url: '/my/cfc/location/SomeComponentRemote.cfc'
3    dataType: 'json',
4    data: {
5        method: 'SomeFunction', // The method we're calling
6        returnFormat: 'JSON', // Give us the return as JSON
7        debug: debug, // the global default 'debug' set earlier by ColdFusion
8        arg1: 'This is my argument',
9        arg2: 42
10    },
11    success: function (response, status, options) {
12        if (response.data) {
13            // Do something with the data
14        } else {
15            // This means the request failed, and response.data should contain
16            // a 'message' key, to present to the user, and an 'errorCode' key
17            // that you're application might act on
18        }
19    }
20});

Here's the same basic Ajax request, using Sencha's Ext JS.

view plain print about
1Ext.Ajax.request({
2    url: '/my/cfc/location/SomeComponentRemote.cfc',
3    params: {
4        method: 'SomeFunction', // The method we're calling
5        returnFormat: 'JSON', // Give us the return as JSON
6        debug: debug, // the global default 'debug' set earlier by ColdFusion
7        arg1: 'This is my argument',
8        arg2: 42
9    },
10    success: function (response, options) {
11        var response = Ext.util.JSON.decode(response.responseText)
12        if (response.data) {
13            // Do something with the data
14        } else {
15            // This means the request failed, and response.data should contain
16            // a 'message' key, to present to the user, and an 'errorCode' key
17            // that you're application might act on
18        }
19    }
20});

I'm always surprised at how few CFers know this little bit. In case you missed it, the key piece to your request parameters comes down to one important argument.

view plain print about
1returnType: 'JSON'

When this argument is passed in the Ajax request parameters, ColdFusion will automatically serialize your native ColdFusion response objects into JSON. This is what allows you to set your returnType to a struct in your function definitions.

Most of this stuff has been around for quite a while. Some use it a lot more than others. Some are just afraid of client side stuff in general. There's a ton of material out there on how to do these things. This is my way of working with it. That doesn't make it right, or better than anybody else's. It's just what I've developed as a working pattern over the years. How do you handle it?

New Job, New Home, A Lot of Work

It's been a very busy year, up til now. Work ramped up in February, contracting me for additional hours for a month and a half straight, after which I've worked on a sting of side projects. This helped me finance a move to Jacksonville, Florida. My new (daytime) job is full-time telecommute, which allows me to put my desk anywhere. Teresa wanted to get back to sunshine and beaches, being tired of the cold and snow of Tennessee winters, and chose Jacksonville for it's location and proximity to family and friends. Jacksonville is a great area, and we nailed a terrific place in Fleming Island. I like it because there's lots of tech (user groups and such), and it's not far from other tech centers (Orlando, Tampa, Atlanta, etc). It doesn't hurt that I can maintain a year around tan or that the beach is a short drive away.

A lot of work has come my way, often tacking an additional 40 to 60 hours a week on top of my normal day job schedule. Often I'll take a project that takes a week or two, then take a few weeks off to spend with the family (and catch up on my reading). I have a list of posts I need to write, due to exposure to some projects I hadn't previously been exposed to. Part of that already started with some exposure to the DataTables JQuery plugin, but I'm also lining up posts for jqGrid, jsTree, and the cfUniForm project. Evernote is filling up with little tidbits. The most difficult piece is coming up with the time to write examples. I'm particular about writing well formed code and documentation, which is why my posts sometimes get spaced out a bit.

One of the things I have discovered, in my exposure to these other projects, is how much I miss working with Ext JS day-to-day. JQuery UI is a good project, but lacks the maturity of Ext JS, and is missing too many key components for writing web applications (Data Stores, Grid, Tree, Menus, Tooltips, etc). My exposure to those other projects was an attempt to fill needs for which Ext JS would have been better suited, while locked into using JQuery UI. The JQuery UI team is working on closing that gap, but there is a lot of catch up necessary to match the breadth and power of Ext JS.

Speaking of Ext JS, Packt Publishing asked me to write the next Ext JS book on my own. While very flattered, I had to carefully weigh what that commitment would mean. Ultimately, I could not justify committing seven and a half months to writing the book with all of the other responsibilities I have right now. I will write a few articles for Packt (as part of my contract on the last book), but feel like I can continue to create blog content that would be more timely (no six month editorial process) and have a greater reach, and do so as my schedule permits without being a burden on my family. Sencha has already announced What to Expect in Ext JS 4.1, and recently put Ext Designer 1.2 in Beta, so there's a lot to talk about here.

Last, but definitely not least, I'm following all the buzz about the upcoming ColdFusion "Zeus". A quick Google Search already brings up a ton of info that Adobe has put out regarding the next version of the ColdFusion server platform, and it looks to once again be a significant release. Some of the big things already mentioned have been the move from JRun to Tomcat, the retirement of Verity in favor of Solr, the upgrade to Axis 2, and the inclusion of closures in CFML. That's just some of what's coming, as Adobe appears to be giving more and more detail during the various conferences through the year (and you never know the whole story until it's released).

Using The DataTables JQuery Plugin

For adminstrative applications, most of my readers know I'm a huge proponent of the Ext JS library. But for front-end, consumer facing sites, I'm often pushed to use JQuery. JQuery is very light weight, and wonderful for DOM manipulation, but it isn't a component library. When you want widgets for advanced data display, you have to use something like JQueryUI. Unfortunately, JQueryUI doesn't yet have a grid component (though they are working on it). So when I recently needed a dynamic, paging data grid, I started looking for something that used server-side data requests and could be skinned using the ThemeRoller. That's when I came upon the DataTables plugin.

It took me some time to figure out the works of how the plugin makes server-side requests. What I found was that, by default the plugin passes an extreme amount of data on a request, and not typically in a format very conducive for our needs. I also had to find a way to pass the method name and data returnFormat needed. That's when I discovered that I could override it's default request. Once I figured that out, I wrote a method to parse the data to create a request object more conducive to a ColdFusion Component request. It passes the following arguments along in a request:

  • iDisplayStart - The number of the first record in the requested set.
  • iDisplayLength - The number of records to return in the requested set.
  • sEcho - A DataTables request identifier, to be echoed back with the return.
  • aoSort - If present, this will be a JSON string representation of sort columns and orders. It's an array of objects:
    • colName - the column name
    • sortDir - the sort direction (asc|desc)

After getting data to my CFC, I had to build my paging query. For my example here I wanted to use the MySQL database I use for my blog, so this was a learning experience for me. The biggest trick for me was getting the TotalCount of records, as this is extremely different from MS SQL, requiring two separate SQL statements for the query and the count. Since DataTables can also sort off of different columns, I needed a way to dynamically set the ORDER BY clause of the query. You can't bind parameters to the ORDER BY clause, but you want to protect your server from SQL injection attack, so you have to validate that part of the request (especially as it's an ajax request, which would be easier to manipulate). Pete Frietag came up with a little regex expression that could be used in this case.

We set up our component to return a structure in the following format:

  • success - A boolean to denote success or failure of the request.
  • message - Only returned if the request fails, a message to state why the failure occurred.
  • totalCount - The total number of records available for the filters applied.
  • result - The paging query.

The last piece of the puzzle was back on the client-side again, where the ColdFusion return had to be put back into a format that can be consumed by the DataTables plugin. This was actually very easy because of the way that ColdFusion returns query data.

Once I had these methods, I wanted to find out how to write a feature plugin for DataTables. One where I could identify additional config arguments in DataTables, and have it automatically work. I contacted Allan Jardine, who wrote DataTables (and has some great web dev tools on his site). He never wrote in that capability, saying that the method override was the only way to make this happen. What I did discover was that I could add options to the standard DataTables config. I created a new option for DataTables, oCFReaderDT, which takes an object of options. Only one argument is required, "method", to define the method to call in the CFC request. I also setup the processor to accept an option, "sForm", as a string selector of a form whose values you may need in the request (i.e.: 'form#myForm'). Then I wrote a custom function that encapsulated the previously written methods into one method, that could then be used as the value of the "fnServerData" option in the DataTables configuration object.

In the download link below is a zip file with all of the files for the example, which has been heavily commented so you know what's going on. Though written for ColdFusion 9, I have included both scripted and non-scripted CFC's. I hope you find this useful, and please leave any comments/questions/suggestions through the Comment Form or Contact links below.

New Custom Tag for Google Maps: CFGMap

Two years back I had to write a new mapping implementation for my former employer, who wanted to move away from MapQuest. We chose Google Maps, for a number of reasons. I wrote the implementation using Scott Mebberson's implementation, the Google Maps Tag. I was quick and easy, and ultimately we had to ditch it at the last minute. Why? Google's licensing at the time was too restrictive for our use case. Running almost 2,000 sites of of one codebase, we would have had to get a separate license key for each site, or get an enterprise license through Google. We were going down that path originally, but the cost at the time was almost $40k, and required some work on their part that they couldn't make our deadline (during the holidays), so when the autorenewal kicked in on (cheaper) MapQuest we just rolled with it.

Last month my former employer once again wanted to get rid of MapQuest. First we looked at our implementation, realizing that the same hurdles were in place. Next we looked at ColdFusion 9's new cfmap tags. That implementation works the same way, requiring the API key per domain. Luckily, I remembered seeing a tweet from someone about changes to Google's Maps API. A change that wouldn't require an API key anymore. So, I went to check it out.

The latest version of the Google Maps Javascript API is very nice, and has one very significant change.

The JavaScript Maps API V3 is a free service, available for any web site that is free to consumers.

This was perfect, as all of our sites were free to consumers. The first thing I did was try to make some adjustments to my initial implementation from Scott's tags. This didn't work, as there were some major differences in Google's new implementation. I ended up rewriting the entire implementation to work with the new API, creating my own custom tag. In forking Scott's code, I had to keep the license the same, which allows me to put it back out to the community at large (and with the approval of my old boss).

CFGMap is now available on RIAForge for download. My simple example source code is included with the download, and all code is heavily documented. My example uses JQuery for the basic DOM manipulation involved, but JQuery is not required to use the tag itself. You'll want to pay special attention to the testmap.js file, which shows how you can access your map object to plot directions and stuff. The tag puts a map on the page, and plots the points you've fed to it. It will even trigger a callback method, that you define, for passing lat/lng info that's been goelocated back to your database, reducing the geolocation hits on subsequent map visits.

It's only the first go-around with the updated library, and I'm sure that changes will need to be made at some point. I welcome any and all feedback, questions, and suggestions.

More Entries