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.

Why I Like

With the position I'm in now, I've had the joy of finally being able to write (full time) for ColdFusion 9. There were many great improvements and updates within CF 9, but two of mine were the increased parity to tags, and the ability to write fully scripted components. I was one of the first to post a fully scripted Application.cfc. I've pushed most of my code examples, since then, using cfscript. I wrote a brief primer on extending ColdFusion, by writing custom components for use server wide. I use cfscript constantly in my day to day development, and used to use as much as I could before CF 9. And yet three times, in as many weeks, people have asked me "Why?"

I've gotten this question before. Most, who've mentioned it, don't really mind it one way or another, they just wanted to know why it's my preference. Others were adamantly opposed to it, because it is so different in style from other scripted languages. There have been efforts to improve cfscript, and even calls to deprecate cfscript, in it's current form, and reinvent it (or switch wholly to server side ActionScript). Last month, Adam Tuttle created the CFCommunity, on GitHub, for open source contributions in filling in the few remaining gaps in cfscript through custom component objects. But, why do I like it?

I've been a ColdFusion developer now for over a decade. Like every other programming language I've worked with I was self taught, and came to CF after learning Visual Basic (6), some C++, and InstallScript (which is what Install Shield used to use for custom installers). Aside from progressively improving my knowledge and usage of HTML/XHTML and CSS, I was primarily a 'server side' developer for much of the beginning of my CF career. I'm a programmer, not a designer, so I wrote (hopefully) good logic. The browser wars had soured me on JavaScript, so I avoided it when I could. Luckily libraries like ExtJS and JQuery came on the scene. By this point my grasp of ColdFusion (and programming, in general) was much more advanced, so putting effort into interface development and usability was a good step. This also meant heavily re-aquainting myself with JavaScript, and remembering what a joy scripting was.

After a while, I found that I was writing more JavaScript than ColdFusion, and focusing more time on client-side development than on the server. Complex server-side code had become second nature, and came rather quickly, whereas client-side code was still relatively new and challenging. I was writing two to three lines of client-side code for every line of ColdFusion (at least). In the process, I've also had to adjust how I approach some of my ColdFusion development, to accomodate ajax interface development. It's been a lot of fun.

While I love ColdFusion, writing complex logic in JavaScript again had shown me how much more rapid scripting was. While ColdFusion's tag syntax is fantastic for transitioning html developer's, and great for generating client-side code (cfoutput and cfloop stuff), straight scripting syntax for logic is much faster, trimmer, and concise. I will agree with those who say that cfscript needs an overhaul. Function naming is inconsistent, as is treatment of arguments and argument naming. All of that aside, it's still quick, trim, and powerful, and the Java behind the functions is (sometimes) more current, and better performant, than the Java behind their tag counterparts.

I wish Adam luck with his CFCommunity project on GitHub. I think it will be a great thing for the ColdFusion community, as a whole, if it can gain steam, and hope to make some time to help contribute to it myself. I also hope that, in some future version of ColdFusion, that the Adobe engineering team will do a thorough review of cfscript and work to create a scripted ColdFusion that the community, as a whole, can agree upon as a new standard. In the meantime, I'm happy to use what we have, as it's still very performant, and a lot of fun!

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]

A Scripted Query Param & Whitespace Gotcha

I discovered this one a while back, but forgot to write a post on it. Did you realize that formatting queries could affect the execution of scripted queries? Consider the following function:

view plain print about
1/**
2 *    FUNCTION login
3 *    A function to validate a user login, and return a struct of user details
4 *
5 *    @access public
6 *    @returnType struct
7 *    @output true
8 */

9function login(required struct formScope) {
10    var retVal = {"success"=true,"message"="","data"=""};
11    var sql = "SELECT     u.userID,
12                        u.username,
13                        u.password,
14                        u.dateCreated,
15                        u.lastUpdated
16                FROM    users u
17                WHERE     u.username = :username
18                AND        u.password = :password
19                AND        u.isActive = 1";
20    var q = new Query(datasource = VARIABLES.instance.dsn,sql = sql);
21    q.addParam(name = "username", value = ARGUMENTS.formScope.username, cfsqltype = "cf_sql_varchar");
22    q.addParam(name = "password", value = ARGUMENTS.formScope.password, cfsqltype = "cf_sql_varchar");
23
24    try {
25        retVal.data = LOCAL.q.execute().getResult();
26        // Check for no recordCount, and throw a 'no records' exception
27        if(!retVal.data.recordCount){
28            throw(type="MH-Custom",errorCode="001",message="The user " & ARGUMENTS.formScope.username & " could not be authenticated. Please check your credentials and try again.");
29        }
30    } catch (any excpt) {
31        retVal.success = false;
32        if(excpt.type eq "MH-Custom"){
33            retVal.message = excpt.message;
34        } else {
35            // TODO: Add admin notification in here somewhere
36            retVal.message = "There was a problem executing this request, and our administrators have been notified";
37            WriteDump(var=VARIABLES.instance,label="instance");
38        }
39        if(StructKeyExists(excpt,"errorCode") AND Len(excpt.errorCode)){
40         retVal["errorCode"] = excpt.errorCode;
41        }
42    }
43    return retVal;
44}

It's a pretty basic function, with a query to check submitted form fields against the database. Right? So, why would it error? "Error? What error?" Yes, it errors. Here's the code for a basic call, along with a dump to output that to the page:

view plain print about
1<cfscript>
2    REQUEST.testObj = CreateObject("component","com.multihome.core.Security").init(DSN='multihome');
3    REQUEST.test = REQUEST.testObj.login({username='admin',password='admin'});
4    WriteDump(var=REQUEST.test);
5
</cfscript>

Dumping that result shows you the error coming through:

CFDump 1

So, to get at the root of this I had to comment out all of my try/catch work:

CFDump 2

Whoops! Forgot my onError handler. OK, I'll comment that out. Here we go! Now we get to the meat of it (the dump was the same, but sometimes you just want to see the raw error):

view plain print about
1Error Executing Database Query
2
3Parameter 'username AND u.password' not found in the list of parameters specified
4
5SQL: SELECT u.userID, u.username, u.password, u.dateCreated, u.lastUpdated FROM users u WHERE u.username = :username AND u.password = :password AND u.isActive = 1
6
7The error occurred in C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 108
8Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 137
9Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 472
10Called from C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\query.cfc: line 605
11Called from C:\Inetpub\com\multihome\core\Security.cfc: line 68
12Called from C:\Inetpub\wwwroot\multihome\index.cfm: line 21
13Called from C:\Inetpub\wwwroot\multihome\application.cfc: line 228

Did you get anything out of that? Neither did I. The first thing I did was go review the scripted 'new Query()' documentation on the Adobe site. That didn't help. According to the documentation, everything appears to be fine. The next thing I did was look at other examples out there. Yep, still good. Next, I started comparing to other instances of code that I know to work. Wait a minute....Look at this:

SQL code with whitespace characters

See anything odd? Yes, I show whitespace characters in my editor (ColdFusion Builder). Pretty easy to do. Just go to your preferences and change it: go to Window | Preferences | General | Editors | Text Editors, and select Show whitespace characters. What you see in this picture are tabs, spaces, and End of Line markers. "OK, so what?" Well, here's where it gets strange. Let's take our original query, and put it all on a single line:

view plain print about
1var sql = "SELECT u.userID, u.username, u.password, u.dateCreated, u.lastUpdated FROM users u WHERE u.username = :username AND u.password = :password AND u.isActive = 1 ";
2var q = new Query(datasource = VARIABLES.instance.dsn,sql = sql);
3q.addParam(name = "username", value = ARGUMENTS.formScope.username, cfsqltype = "cf_sql_varchar");
4q.addParam(name = "password", value = ARGUMENTS.formScope.password, cfsqltype = "cf_sql_varchar");

If you run this, all is well:

CFDump 3

As you can see, everything works fine now. But, when I format my SQL for readability again, I again get the error. When I went back, and looked at examples that worked (in my editor) I discovered that lines following lines with params were directly preceded with one or more spaces. So, just to test, I added a single space right before those lines that followed lines referencing params:

view plain print about
1var sql = "SELECT     u.userID,
2                    u.username,
3                    u.password,
4                    u.dateCreated,
5                    u.lastUpdated
6            FROM    users u
7            WHERE    u.username = :username
8             AND    u.password = :password
9             AND    u.isActive = 1";
Code with adjust whitespace

This took care of it. My error went away, and my query executed properly, and my query was still formatted for readability. After another round of the great Tabs vs Spaces debate at work I had to change my editor's default preferences back, which is what caused/highlighted this issue. (I told you guys we needed to stick with 4 spaces ;) Maybe it's a bug in the SQL parser, or there's a method to the madness, but adding that single space before those lines is all that's required to get back on track.

MSOC Part 8: onRequest Event Handlers

Some of the most powerful and versatile ColdFusion application event handlers are often the most ignored: the onRequest event handlers. Not sure why these so often seem to get ignored, but I can't name for you how many times, and how many apps, I've started working on that weren't using any of these methods in any capacity, and needed to.

Like the onApplication and onSession before it, the onRequest event handlers include methods that fire immediately before (onRequestStart) and after (onRequestEnd) the page request. Also like the scopes of those handlers, the onRequest handlers allow access to the REQUEST scope variables without having to lock access to those variables. Those are pretty standard, and not much to figure out. But the onRequest handlers also include two additional handlers: onRequest, and onCFCRequest. These two are a bit more confusing to understand "why?", so we'll go a little more in depth.

[More]

ColdFusion 9 Hotfix 2 Released

Adobe has released the ColdFusion 9.0.1 Hotfix 2, available on the update page. This is a cumulative hotfix, containing fixes for security issues, items around ORM, resolution to questions of JSON serialization, integration bits for Exchange, and much more.

Install has some quirks. It's not just a simple 'upload the file' bit, so you'll want to pay careful attention to the instructions, and backup affected files in advance. This will get so much easier with the next version of ColdFusion, but for now it's worthwhile to jump through the hoops. It is a 'cumulative' hotfix, and word from those in the know say that it is safe to skip over the CHF 1 install, if you haven't done it already, as all it's changes are within this hotfix as advertised.

Legacy Code and Some Modern Browsers

Working on some legacy code the other day, and came across one that was driving me nuts. I had a form that was part of a tabbed interface, and the form would not submit in Firefox or Chrome. Finally, after some trial and error, I was on a specific tab when I hit submit, and saw the following:

Unable to display content. Adobe Flash is required.

Did you see that? Some kind of form validation. It was odd though, because I didn't see that popup from any other tab, nor did it shift focus to the tab with that field (though focus was on that field). So, I went searching.

After quite a bit of time I found out something even more odd. There was no form validation on that field. Zilch. Nada. What the...? Now I was really stumped.

So, I started looking at the code of the form itself, specifically at that field. Here's what I found:

view plain print about
1<input type="text" name="somefield" required="No">

Like I said, some legacy code. Have you figured it out yet? Here's the deal. This form used to be a cfform, and the original developer had added the required attribute on a cfinput (unnecessarily). At some point, the form was switched over to a standard form, with it's own validation, and the cfinput was switched quickly to an input, without removing the attribute. No big deal, right?

Well, it wasn't a big deal, for a very long time. But, the browsers are updating. They're slowly implementing changes to support html5. And, guess what. There are changes to the input elements for html5. Now you have a required attribute? It will automatically validate that field, with a default message, and stop form processing if the field is empty. Never mind that you've implemented the required attribute incorrectly for html5, it'll still validate it regardless.

Removing the required attribute, which was no longer necessary in our application, resolved the issue.

How 'bout that gotcha?

Previous Entries / More Entries