Cutter's Crossing - JQueryhttps://www.cutterscrossing.com/index.cfm
We talk about web development, often covering the three core pillars: HTML, CSS, and Javascript. There is a lot of legacy content here, discussing everything from ColdFusion to ExtJs, as well as more current stuff like modern EcmaScript, etc.en-usSun, 15 Sep 2019 15:33:02 -0000Wed, 02 May 2012 22:41:00 -0000BlogCFChttp://blogs.law.harvard.edu/tech/rssweb.admin@cutterscrossing.comweb.admin@cutterscrossing.comweb.admin@cutterscrossing.comnoJQuery Plugin: serializeCFJSONhttps://www.cutterscrossing.com/index.cfm/2012/5/2/JQuery-Plugin-serializeCFJSON
Quick note about my <a href="https://github.com/cutterbl/serializeCFJSON" target="_blank">serializeCFJSON</a> project out on GitHub. I wrote this quick JQuery plugin to convert ColdFusion's JSON representation of it's native query objects. ColdFusion represents datasets in a trim manner, as an object containing two arrays: One, an array of column names, and the other, an array of arrays, each representing one record of the set. Most representations of recordsets are an array of objects, each record represented as a collection of name/value pairs. ColdFusion's representation is much smaller, removing a lot of unnecessary duplication, but many pre-built frameworks and plugins look for the name/value pair objects.
What the plugin does is convert ColdFusion's representation into the more standard form. Ajax transfer is unaffected, as the trimmer format is still being passed. What we get is some very minor client-side overhead in the creation of a new object. What's more, the plugin recursively searches through a JSON object and converts any ColdFusion query that it finds. So, if you <a href="http://www.cutterscrossing.com/index.cfm/2011/9/26/How-I-Do-Things-ColdFusion-and-Ajax-Requests">nest your query inside a larger object</a>, the plugin will still convert it for you.
I put up a <a href="http://examples.cutterscrossing.com/serializeCFJSON">demo</a> in the Projects menu, at the top of this site. This is a refactor of my <a href="http://www.cutterscrossing.com/index.cfm/2012/4/4/Intro-to-jqGrid-Part-7-Grouping">Grouped jqGrid</a> example, using the plugin for the data conversion. The full sample code for the demo can be found in the <a href="https://github.com/cutterbl/serializeCFJSON" target="_blank">GitHub repository</a>.
Take her out for a spin, and let me know what you think.
JSONjqGridAjaxJQueryColdFusionWed, 02 May 2012 22:41:00 -0000https://www.cutterscrossing.com/index.cfm/2012/5/2/JQuery-Plugin-serializeCFJSONIntro to jqGrid Part 7: Groupinghttps://www.cutterscrossing.com/index.cfm/2012/4/4/Intro-to-jqGrid-Part-7-Grouping
OK, I know I said I was done, but I kept playing around and figured I should share. <a href="http://www.trirand.com/jqgridwiki/doku.php" target="_blank">jqGrid</a>, like other (read: <a href="http://www.sencha.com/products/extjs/" target="_blank">Ext JS</a>) good grid implementations, has the ability to <em>group</em> your data. What does that mean exactly? Well, let's look at our demo for inspiration. We've created a grid of blog post entries. Entries have associated <em>Categories</em>. We can reconfigure our grid to show the entries grouped by Category, with Category headers and accordion like separation.Accomplishing this is fairly easy, taking a combination of jqGrid configuration, and changes to our data retrieval process. First we'll look at our configuration. We need two new config attributes: <em>grouping</em> and <em>groupingView</em>.
<h4>jqGridDemo.js - Grid Configuration</h4>
<code>
grid.jqGrid({
...
sortname: 'Posted',
sortorder: 'asc',
postData:{method:"GetGroupedEntries",returnFormat:"JSON"},
grouping: true,
groupingView: {
groupField: ['categoryName'],
groupDataSorted: true
},
...
});
</code>
Simple enough. We said "Yes, we want a grouping grid", and then told it which column to group on. "Why is the <em>groupField</em> an array?" Good question. The writers of jqGrid are planning to support multiple grouping levels in the future, and will use this array form to define multiple columns at that time. Since jqGrid can only take one column right now, just place that top level grouping column. We also set <em>groupDataSorted</em> to <em>true</em>, so that it will pass the <em>groupField</em> column name as part of the <em>sortCol</em> argument on server-side requests. This means that all server-side requests will now have a <em>sortCol</em> in the form of <b>{groupField} asc, {sortCol}</b>. The need for this will become apparent when we adjust our paging query. Let's look at that next. You'll notice I adjusted the <em>postData</em> attribute, and changed the name of the method we'll call. I also changed our initial sorting column and direction.
In our <em>Entries.cfc</em>, let's copy our <em>GetEntries</em> method, paste it again, and rename the new one to <em>GetGroupedEntries</em>. From there, let's start off by looking at our query. We've never pulled the Categories before, so now we need to adjust to pull in the necessary information. Because there can be multiple categories assigned to an entry, <a href="http://www.blogcfc.com/index.cfm" target="_blank">BlogCFC</a> uses a mapping table, <em>tblblogentriescategories</em>, to link entries (<em>tblblogentries</em>) to categories (<em>tblblogcategories</em>). By adding some <em>JOIN</em> statements, and table aliases, we can now add our column to the query.
<h4>Entries.cfc - GetGroupedEntries Query</h4>
<code>
LOCAL.sql = "SELECT SQL_CALC_FOUND_ROWS b.id,
b.title,
c.categoryname,
b.posted,
b.views
FROM tblblogentries b
INNER JOIN tblblogentriescategories bec ON bec.entryidfk = b.id
INNER JOIN tblblogcategories c ON c.categoryid = bec.categoryidfk
WHERE 0 = 0
";
</code>
Then we're required to have our records grouped by category. This is why jqGrid adjusted the <em>sortCol</em> variable, because we need to add the <em>groupField</em> to our <em>ORDER BY</em> clause. You'll recall, though, that we had a bit of code to verify the validity of our <em>sortCol</em> variable. We'll need to adjust this first, or it will throw an error.
<h4>Entries.cfc - GetGroupedEntries: Set ORDER BY</h4>
<code>
LOCAL.scArr = ListToArray(ARGUMENTS.sortCol);
LOCAL.sortCol = (ArrayLen(LOCAL.scArr) eq 2) ? LOCAL.scArr[2] : ARGUMENTS.sortCol;
// Verify that your sort column and direction are valid. If not, then return an error.
if(ArrayFindNoCase(VARIABLES._COLUMNARRAY, Trim(LOCAL.sortCol)) AND ArrayFindNoCase(VARIABLES._DIRARRAY, ARGUMENTS.sortDir)){
LOCAL.orderby = ARGUMENTS.sortCol & " " & ARGUMENTS.sortDir;
} else {
StructAppend(LOCAL.retVal,{"success" = false, "message" = "Your sort criteria is not valid."},true);
return LOCAL.retVal;
}
</code>
What are we doin' here? Well, we convert <em>sortCol</em> argument to an array, and create a <em>LOCAL.sortCol</em> variable. If our array has two items, we set our local variable to the second item. Otherwise we just use the argument. We then test the local variable for validity (is this a valid sort column?), and set our <em>LOCAL.orderby</em>. Now we only need two minor adjustments to our javascript, to accomodate the new column. The first is in the <em>colModel</em> in the grid config.
<h4>jqGridDemo.js - Grid Configuration: Column Model</h4>
<code>
grid.jqGrid({
...
colModel: [
{name: 'Action', index: 'ID', label: 'Action', width: 60, fixed: true, sortable: false, resizable: false, align: 'center', formatter: 'actionFormatter', key: true},
{name: 'Title'},
{name: 'Posted', label: 'Release Date'},
{name: 'Views', align: 'right', width: 60, fixed: true},
{name: 'categoryName'}
],
...
});
</code>
The last bit of script is to add the column in our bits that <em>map</em> the ColdFusion return to the <em>colModel</em>.
<h4>jqGridDemo.js - populateGrid: Column Mapping</h4>
<code>
grid.jqGrid('setGridParam',{remapColumns:[
gridCols['ID'] + gridMultiSelect,
gridCols['TITLE'] + gridMultiSelect,
gridCols['POSTED'] + gridMultiSelect,
gridCols['VIEWS'] + gridMultiSelect,
gridCols['CATEGORYNAME'] + gridMultiSelect
]});
grid[0].addJSONData(d);
</code>
Now we can load our template in the browser to see the result.
<div style="text-align: center; margin: 0 auto;">
<img src="/images/jqGridDemo-pt7.png" title="jqGrid Pt 7 Grouping Demo 1" />
</div>
Great! We now have grouped data. We can page through, and see where new categories start. You'll notice that our total record count is much higher than before. That's because there's a one-to-many relationship between entries to categories, so entries are repeated in each category that they are a part of. This is great, but the first thing I notice is that the new column actually displays in the grid. Since the category is already the grouping header, I don't think we need to show it in the grid columns. Luckily there's an option to the <em>groupingView</em> that will allow us to hide the column, <em>groupColumnShow</em>.
<h4>jqGridDemo.js - Grid Config: groupingView</h4>
<code>
grid.jqGrid({
...
grouping: true,
groupingView: {
groupField: ['categoryName'],
groupDataSorted: true,
groupColumnShow: false
},
...
});
</code>
<div style="text-align: center; margin: 0 auto;">
<img src="/images/jqGridDemo-pt7b.png" title="jqGrid Pt 7 Grouping Demo 2" />
</div>
This hid the column but, as you can see, it also changed the size of the grid when it removed the column. This is a bug in jqGrid, and is filed with the development team, but in the meantime you can add some code to handle this issue. Our <em>gridComplete</em> configuration attribute is already setup to call our <em>gridLoadInit</em> method, which is called every time the data loads. We'll add a line at the end of this method to repair our grid width.
<h4>jqGridDemo.js - gridLoadInit Method</h4>
<code>
var gridLoadInit = function () {
...
grid.jqGrid('setGridWidth',800);
};
</code>
If we reload our page again, we'll see that the grid width is now as it should be, with the column being hidden as intended. What's next?
Each grouping section header has an icon, showing the user that the sections can be collapsed or expanded. In our current configuration (the default) all sections are expanded by default. Let's change that so that they're all collapsed on load. This is easily accomplished by adding <em>groupCollapse</em> to our <em>groupingView</em> configuration. We'll also change the classes of the expand and collapse indicators used, so that they match up with the rest of our demo. We're using the <a href="http://www.famfamfam.com/lab/icons/silk/" target="_blank">FamFamFam Silk</a> icon library, so we'll reference some images from that.
<h4>jqGridDemo.js - groupingView Configuration</h4>
<code>
groupingView: {
groupField: ['categoryName'],
groupDataSorted: true,
groupColumnShow: false,
groupCollapse: true,
plusicon: 'bullet_toggle_plus',
minusicon: 'bullet_toggle_minus'
},
</code>
The <em>plusicon</em> and <em>minusicon</em> attributes denote class references. We'll create these in our stylesheet.
<h4>jqGridDemo.css - icon references</h4>
<code>
/* bullet_toggle_minus icon image for trigger */
.bullet_toggle_minus { background: url('/resources/images/icons/bullet_toggle_minus.png') no-repeat scroll 0px 0px transparent !important; }
/* bullet_toggle_plus icon image for trigger */
.bullet_toggle_plus { background: url('/resources/images/icons/bullet_toggle_plus.png') no-repeat scroll 0px 0px transparent !important; }
.ui-jqgrid tr.jqgroup td { font-size: 1.1em; font-weight: bold !important; background-color: #98AFC7; }
</code>
When you reload your page you'll see that the groupings are now collapsed by default, and notice the new icons. I even added some style stuff to differentiate the headers from the data a little more clearly. Now, personally, I would prefer that all of the sections are collapsed on load, except the first one. We can do this by adding a line to our <em>gridLoadInit</em> method.
<h4>jqGridDemo.js - gridLoadInit Method</h4>
<code>
var gridLoadInit = function () {
...
grid.jqGrid('groupingToggle','gridTestghead_0');
grid.jqGrid('setGridWidth',800);
};
</code>
The <em>groupingToggle</em> method is used to open or close a grouping session, taking the row id as the argument. Grouping headers are just another table row, each with it's own unique id. It's format is {grid id}ghead_{section index}. Our grid id is <em>gridTest</em>, and JavaScript uses 0 based indexes, so the first section is index 0. Placing this in the <em>gridLoadInit</em>, a request for data is made, the data is applied to the grid in groups, the groups are collapsed (and the column is hidden) by configuration, the load method is called, then the first section is expanded, and the width is set on the grid. Whew! That's a lot of stuff. It now looks like this.
<div style="text-align: center; margin: 0 auto;">
<img src="/images/jqGridDemo-pt7c.png" title="jqGrid Pt 7 Grouping Demo 3" />
</div>
This is coming along very nicely. There's just one final thing I'd like to explore here, summary rows. One common function of grouping is to have summaries of specific columns: calculations (or functions) run on each record of data in a group to arrive at some 'summary' value. For instance, you might have some sort of user logging setup, and want to show the average number of page views per user. Or an e-commerce control panel, where you are grouping user orders, and want to show the total amount that's been spent by clients across all of their grouped orders. In our demo we show how many times each post has been viewed, so we'll break it down to the total number of views per category. We begin by adjusting our column model, to say we want to tally up the views.
<h4>jqGridDemo.js - jqGrid Config: Column Model</h4>
<code>
grid.jqGrid({
...
colModel: [
...
{name: 'Views', align: 'right', width: 60, fixed: true, summaryType: 'sum'},
...
],
...
});
</code>
There are several different <em>summaryType</em>s that may be applied to a column.
<ul>
<li>sum - apply the sum function to the current group value and return the result</li>
<li>count - apply the count function to the current group value and return the result</li>
<li>avg - apply the average function to the current group value and return the result</li>
<li>min - apply the min function to the current group value and return the result</li>
<li>max - apply the max function to the current group value and return the result</li>
</ul>
You can also apply a custom function, or even a template, to a column. For more information see the jqGrid wiki entry on <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:grouping" target="_blank">Grouping</a>. I don't have enough space in the <em>Views</em> column to put a label on that total, so I need something in the <em>Release Date</em> footer. Since I'm not really creating a true summary of that column I need to apply an empty method to the <em>summaryType</em>, then apply my output to the <em>summaryTpl</em> like this:
<h4>jqGridDemo.js - jqGrid Config: Column Model</h4>
<code>
var emptyMethod = function (){
return;
};
grid.jqGrid({
...
colModel: [
...
{name: 'Posted', label: 'Release Date', summaryTpl: '<div class=\"dateSummaryFooter\">Total Views: <\/div>', summaryType: emptyMethod},
...
],
...
});
</code>
After that I adjust my <em>groupingView</em> to give me the summary:
<h4>jqGridDemo.js - jqGrid Config: groupingView</h4>
<code>
groupingView: {
groupField: ['categoryName'],
groupDataSorted: true,
groupColumnShow: false,
groupCollapse: true,
plusicon: 'bullet_toggle_plus',
minusicon: 'bullet_toggle_minus',
groupSummary: [true]
},
</code>
Then I'll add a little styling, to make it stand out, and align my <em>summaryTpl</em> output.
<h4>jqGridDemo.css</h4>
<code>
div.dateSummaryFooter { text-align: right; }
tr.jqfoot td { background-color: #736F6E; color: #FFF; }
</code>
When you refresh the page you'll see how it all lays out.
<div style="text-align: center; margin: 0 auto;">
<img src="/images/jqGridDemo-pt7d.png" title="jqGrid Pt 7 Grouping Demo 3" />
</div>
As you can see, grouping in jqGrid is pretty easy to do. There are more options to refine your grouping view as well. See the documentation for more. As always, I welcome your feedback. Sample code can be obtained from the <b>Download</b> link below.
jqGridDevelopmentJQueryColdFusionWed, 04 Apr 2012 17:02:00 -0000https://www.cutterscrossing.com/index.cfm/2012/4/4/Intro-to-jqGrid-Part-7-GroupingIntro to jqGrid Part 6: The Action Columnhttps://www.cutterscrossing.com/index.cfm/2012/3/16/Intro-to-jqGrid-Part-6-The-Action-Column
So far we've covered <a href="http://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid">the base grid configuration</a>, populating the grid <a href="http://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-Data">with remote data</a>, controlling layout by <a href="http://www.cutterscrossing.com/index.cfm/2011/12/28/Intro-to-jqGrid-Part-3-Columns">refining the column model</a>, and given an <a href="http://www.cutterscrossing.com/index.cfm/2012/1/4/Intro-to-jqGrid-Part-4-Event-Handling">example of event handling</a> by binding a search form to our grid. This covers most of what the average developer might need to know about working with jqGrid, but I've been promising to tell you how to work with the icons we setup in our action column.jqGrid provides the ability to add predefined 'action' icons directly. That said, it's a small, predefined set of actions, and they build out dialogs and forms according to configuration details. This might be nice for prototyping, or small applications, but most of us already have interfaces for editing and stuff. I like finer control over the 'look' of my interfaces, and prefer to use stuff that's already built, if we have it. So, we make our own 'action' icons, and attach our own custom events.
First, let's look again at the custom cell renderer that we wrote for our 'action' column:
<code>
$.extend($.fn.fmatter, {
actionFormatter: function(cellvalue, options, rowObject) {
var retVal = "<span class=\'icon-trigger action-trigger pencil\' rel=\'" + cellvalue + "\' \/>";
retVal += "<span class=\'icon-trigger action-trigger delete\' rel=\'" + cellvalue + "\' \/>";
return retVal;
}
});
</code>
Yeah, this one was pretty simple. All it did was add two icons to the display and apply the cell value to the <em>rel</em> attribute. The cell value, in our case, is the ID column, which is the unique reference you would want for this type of scenario. Now, the first thing you might notice, in reviewing the current output, is that something looks a little off. Can you spot it?
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt6.png" />
</div>
If you look close you can see it. Every other row the icons and text are a little washed out. This is because of the <em>altRows</em> option we used in our jqGrid configuration. Removing that option will remove the alternate row highlighting, which is causing the washout effect. Here, let's run a little experiment. First, remove the <em>altRows</em> attribute from our grid configuration. A quick reload will show you the washout is gone, just like I said. Now, let's create a new style declaration in our stylesheet.
<code>
table#gridTest tr:even td { background-color: #F5F5F5; }
</code>
If you reload the page you'll notice that didn't do anything. Why is that? Well, good question. Maybe it's because the rows don't exist until after the request is made. Let's take a different tact. Let's change that style declaration, making it define a class.
<code>
.evenRow { background-color: #F5F5F5; }
</code>
Next, let's add a line to the end of our <em>gridLoadInit</em> method.
<code>
$('table#gridTest tr:even td').addClass('evenRow');
</code>
Now refresh your grid. You get the grid highlighting without the washout effect. Cool! You might remember that, in a <a href="http://www.cutterscrossing.com/index.cfm/2012/1/13/Intro-to-jqGrid-Part-5-Some-Other-Stuff">previous post</a>, the <em>gridLoadInit</em> method is attached to the grid's <em>gridComplete</em> configuration option, and fires each time data is loaded into the grid. That method becomes very important, as it allows you to do things with the records/rows once they are loaded. "But, what does this have to do with the 'Action' column stuff?" Well, that leads to our first method of attaching event handlers to our action icons.
<h3>Bound Handlers</h3>
For a number of reasons, I'm using an older version of <a href="http://www.jquery.com" target="_blank">JQuery</a> in these posts. You do have access to <em>.live()</em> binding, but sometimes it's nice to really take control of when you are binding/unbinding events. Basically, we want to apply event handlers to every row after it loads, and remove those bindings before we load new records. We'll start by creating a new method that we can call from within our <em>gridLoadInit</em> method. You could put all of the code directly in the method, but let's break it up a little to make maintenance a little easier. We'll write a new <em>bindActionHandlers</em> method inside our <em>$(document).ready()</em>.
<code>
/*
* FUNCTION bindActionHandlers
* This method is called within the gridLoadInit() method, and is used to
* bind event handlers to the action icons of new records
*/
var bindActionHandlers = function () {
};
</code>
Here we'll place all of our code to create new event bindings for our action columns. Let's start by binding a click handler to our <em>delete</em> icons.
<code>
var bindActionHandlers = function () {
// Delete icon binding
$('span.delete[class*="action-trigger"]', grid).bind("click", function(e){
e.preventDefault();
return false;
});
};
</code>
What we're saying here is a) apply this method as the click handler to <em>delete</em> icons inside our grid that include the <em>action-trigger</em> class, and b) prevent any default click action and return false when we're done. Now, let's think about this a minute. What would we expect a user to see and do when they are trying to delete this record? Well, we probably want to make sure that they really want to remove the record. Let's build them a little confirmation dialog. First, we probably need to know which record they're trying to remove. We probably also want to show them the title of the record in the confirmation. Let's set up some function local variables.
<code>
var bindActionHandlers = function () {
// Delete icon binding
$('span.delete[class*="action-trigger"]').bind("click", function(e){
e.preventDefault();
var row = $(this).parent().parent(),
rowId = row.attr("id"),
recId = $(this).attr('rel'),
title = grid.jqGrid('getCell',rowId,'Title');
return false;
});
};
</code>
OK, this gets us the <em>id</em> of the record, along with the <em>title</em>. Now we'll build a little confirmation box.
<code>
var bindActionHandlers = function () {
// Delete icon binding
$('span.delete[class*="action-trigger"]').bind("click", function(e){
e.preventDefault();
var row = $(this).parent().parent(),
rowId = row.attr("id"),
recId = $(this).attr('rel'),
title = grid.jqGrid('getCell',rowId,'Title');
// Create a dynamic dialog, that is destroyed when closed
$('<div>').dialog({
title:'Delete Confirmation',
width:425,
height:200,
modal:true,
create: function(){
$('span.ui-icon-closethick').html("");
},
close:function(){
$(this).dialog('destroy');
},
buttons:[
{text:'OK',
click:function(){
deleteItem(rowId,recId,$(this));
}},
{text:'Cancel',
click:function(){
$(this).dialog('close');
}}
]
}).html("Are you sure you want to delete the following?:<br />"+title);
return false;
});
};
</code>
Now we create a <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a> dialog on the fly, to use as a confirmation box. We setup <em>OK</em> and <em>Cancel</em> click handlers, and dynamically set the text of the dialog to reflect the <em>title</em> of the record being deleted. We also call a<em>deleteItem()</em> method, to <em>delete</em> the record. We'll build that later. Now that we've put all of this together, all we need to do is add a call to the <em>bindActionHandlers()</em> method to the end of our <em>gridLoadInit()</em> method.
<code>
var gridLoadInit = function () {
// if the 'selected' array has length
// then loop current records, and 'check'
// those that should be selected
if(selArr.length > 0){
var tmp = grid.jqGrid('getDataIDs');
$.each(selArr, function(ind, val){
var pos = $.inArray(val, tmp);
if(pos > -1){
grid.jqGrid('setSelection',val);
}
});
}
$('table#gridTest tr:even td').addClass('evenRow');
bindActionHandlers();
};
</code>
Now let's reload the page, and click the <em>delete</em> icon of our first row.
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt6b.png" />
</div>
The downside to dynamically applying your event handlers like this is that you also need to <em>unbind</em> handlers before you refresh your data. We already stubbed out a method for this in our <em>$(document).ready()</em>, <em>gridUnloader</em>.
<code>
/*
* FUNCTION gridUnloader
* Called from within the populateGrid method, this method is to unbind
* any event handlers created during grid load, by the gridLoadInit method.
*/
var gridUnloader = function () {
};
</code>
After that you just add the code to <em>unbind</em> the click handler
<code>
var gridUnloader = function () {
$('span.delete:not("disabled-trigger")', grid).unbind('click');
};
</code>
This says <em>unbind</em> any <em>click</em> handler applied to a grid's <em>delete</em> icons that don't have a class of <em>disabled-trigger</em>. ("Hey Cutter! What's That!" All in good time...) Now that you have the method, all you have to do is call it from the correct point of context. Within the <em>data</em> grid config option, which handles all requests for grid data, we had a piece of code that runs on the first request (to map column positions). This is in a conditional that basically says "if not set, then set it". We put an <em>else</em> clause here, to now see "if it is set, then we're reloading the grid. We know if we're reloading we need to <em>unbind</em> all our row level event handlers, so this is where our call to <em>gridUnloader</em> will go.
<code>
var populateGrid = function (postdata) {
$.ajax({
url: '/com/cc/Blog/Entries.cfc',
data: $.extend(true, {}, postdata, {search: $.toJSON(scrubSearch())}),
method:'POST',
dataType:"json",
success: function(d,r,o){
if(d.success){
// If loading for the first time, let's find out to which
// array positions our columns map.
if(!gridCols.set){
columnSetup(d.data);
} else {
// Unbind any bound event handlers in the current grid data
// Only necessary after the first grid load
gridUnloader();
}
grid.jqGrid('setGridParam',{remapColumns:[
gridCols['ID'] + gridMultiSelect,
gridCols['TITLE'] + gridMultiSelect,
gridCols['POSTED'] + gridMultiSelect,
gridCols['VIEWS'] + gridMultiSelect
]});
grid[0].addJSONData(d);
} else {
console.log(d.message);
}
}
});
};
</code>
Let's stop and take a minute to build that <em>deleteItem</em> method, and the remote method it will call. There's a few things that need to happen.
<ol>
<li>Make an ajax call to remove the record</li>
<li>If the call is successful, remove the row from the grid</li>
<li>If the call fails, show a message to the user</li>
<li>Close the Delete Confirmation dialog box</li>
</ol>
Let's start by writing a quick dialog configuration object for our generic error dialog. We'll place this inside our <em>$(document).ready()</em> statement.
<code>
// Error dialog box config
$('div#grid-dialog-error').dialog({
width:400,
autoOpen: false,
modal: true,
create: function(){
$('span.ui-icon-closethick').html("");
},
buttons:{
"OK": function(){
$(this).dialog("close");
}
}
});
</code>
We'll use this in a few places going forward. Right now, let's write our ajax call for removing a blog entry.
<code>
/*
* FUNCTION deleteItem
* This method is for deleting records and removing the
* corresponding grid entry
* @rowId (int) - ID of the row to be removed from the grid
* @recId (string) - The UUID of the blog entry to be removed
* @dlg (object) - JQuery element of the JQUI dialog
*/
var deleteItem = function (rowId, recId, dlg) {
$.ajax({
url: '/com/cc/Blog/Entries.cfc',
data: {
method: 'deleteEntry',
recId:recId,
returnFormat: 'json'
},
dataType: 'json',
success: function(d, r, o){
if (d.success) {
grid.jqGrid('delRowData',rowId);
dlg.dialog('close');
}
else {
$('span#grid-dialog-error-message').html(d.message);
$('div#grid-dialog-error').dialog('open');
}
}
});
};
</code>
This will make an ajax call to remove the record. If the call is successful, then the row will be removed and the dialog closed. If it fails, then our new error dialog will open to tell us why. This tells us a little about how we need to form our remote method.
<code>
/**
* FUNCTION deleteEntry
* Used to remove entries from the system
*
* @access remote
* @returnType struct
* @output false
*/
function deleteEntry(required string recId){
LOCAL.retVal = {"success" = true, "message" = "", "data" = ""};
// BEST PRACTICE: You'll want to verify that the user has the right to do this. Normally, that would go here.
LOCAL.sql = "DELETE FROM tblblogentries
WHERE id = :recId";
LOCAL.q = new Query(sql = LOCAL.sql);
LOCAL.q.addParam(name = "recId", value = ARGUMENTS.recId, cfsqltype = "cf_sql_varchar");
try {
// You would uncomment the following line to actually remove records, and remove the throw statement
// LOCAL.q.execute();
throw (message = "Intentional Exception: You didn't really think I'd delete entries, did you?", type = "custom_err", errorCode = "ce1001");
} catch (any excpt) {
// In testing, and with the .execute() commented out above, comment out the next line to watch the grid remove a row
LOCAL.retVal.success = false;
LOCAL.retVal.message = excpt.message;
}
return LOCAL.retVal;
}
</code>
Yes, I didn't really delete anything. The code is there, but we're not killing entries today. This does give you the basic idea on how this all works though. Trying to delete an item in the grid now will show you a confirmation dialog. When you click on <em>OK</em> the ajax call is made. Right now, we intentionally throw an error. The success marker causes the error dialog to display with the message. If you comment out the first line of the <em>catch</em>, in this method, and re-run your test, you will see the confirmation dialog close, and the deleted record's row removed from the grid.
<h3>Old School Event Handler</h3>
Nowadays, it's standard practice to <em>bind</em> event handlers at runtime. It's even referred to as best practice. That said, there are issues with these new methods, particularly in making sure to <em>unbind</em> events from items you're removing from the DOM (or when they aren't needed at all). Binding events at runtime has runtime implications, and can cause memory leaks if not carefully controlled. For this reason, sometimes it just makes more sense to take it on the old school way. To demonstrate, let's take a different tact with how we handle our <em>edit</em> icons.
<code>
$.extend($.fn.fmatter, {
actionFormatter: function(cellvalue, options, rowObject) {
var retVal = "<a href=\'javascript:void(0)\' onclick=\'editEntry(\"" + cellvalue + "\")\'><span class=\'icon-trigger action-trigger pencil\' rel=\'" + cellvalue + "\' \/></a>";
retVal += "<span class=\'icon-trigger action-trigger delete\' rel=\'" + cellvalue + "\' \/>";
return retVal;
}
});
</code>
We put a standard anchor tag in there, with the <em>onclick</em> attribute calling a new method, <em>editEntry</em>, to which we pass the Entry ID (cellvalue). It's not elegant, but it works. To prove that, let's build out our <em>editEntry</em> method. We don't want to build a full Blog entry editor in this blog post, but we can create a quick page to load in a dialog via an ajax call.
<code>
<cfsetting enablecfoutputonly="true" />
<cfparam type="string" name="FORM.recId" default="" />
<cfif !Len(FORM.recId)>
<cfoutput>You must supply an ID of a record to edit!</cfoutput>
<cfabort />
</cfif>
<cfoutput>
<p style="font-weight:bold;">You are editing #FORM.recId#
</cfoutput>
<cfsetting enablecfoutputonly="false" />
</code>
This is just a simple template that takes a single POST variable (recId) and outputs it on the page. Now all you need is the ajax call to render this in a dialog box.
<code>
var editEntry = function (recId) {
$('<div id="recordEdit">').dialog({
title: 'Entry Editor',
modal:true,
width: $(window).width()*.6,
height: $(window).height()*.6,
create: function(){
$('span.ui-icon-closethick').html("");
},
open: function(){
var dlg = $(this);
// Get the summary for the selected lesson
$.ajax({
url: '/edit.cfm',
type: 'POST',
data:{
recId: recId
},
dataType:'script',
success: function(d, r, o){
dlg.html(d);
}
});
},
close:function(){
$(this).html('').dialog('destroy');
setTimeout('$("#recordEdit").remove();',100);
},
buttons:[{
text:'Close',
click:function(){
$(this).dialog('close');
}
}]
});
};
</code>
Here we create a div element on the fly. Pay special attention to our <em>close</em> config option, where we have to pull off some trickery to <em>destroy</em> the JQueryUI Dialog, and <em>remove</em> the div from the DOM. This is very important, because every time we click on an <em>edit</em> icon it is going to create and open a new dialog. In the <em>open</em> config option we make an ajax call to retrieve <em>edit.cfm</em>, passing it the record ID (recId) of the record we want to edit. We place the return of the request in the content of the JQueryUI Dialog, which we sized to take up 60% of the window size.
<h3>Wrap It Up</h3>
At this point I think we have a pretty good "Intro" to jqGrid. If there's some critical piece of info you think I have missed, please feel free to let me know through the <b>Contact</b> link below. Sample code for our application can be found below, from the <b>Download</b> link. As always, give me your feedback and questions.
jqGridAjaxDevelopmentJQueryColdFusionFri, 16 Mar 2012 10:19:00 -0000https://www.cutterscrossing.com/index.cfm/2012/3/16/Intro-to-jqGrid-Part-6-The-Action-ColumnThe Joys of Developing for Internet Explorerhttps://www.cutterscrossing.com/index.cfm/2012/2/9/The-Joys-of-Developing-for-Internet-Explorer
<em>Note: Follow the madness here to it's conclusion, to discover yet another "Really? I didn't know that..." IE moment, that may save you heartache and pain.</em>
Is the sarcasm evident in my title? It should be. While Internet Explorer may have been "groundbreaking" when it was released, it has ever been the bain of the web developer's existance. Why, you ask? Because it refuses to adhere to standards, and just operates differently than everyone else. To be fair, Internet Explorer 10 is in the works, and supposedly closes the gap a fair amount (and even wins out in some of the html 5/css 3 support), and 7, 8 and 9 did progressively improve (if slowly). Unfortunately the fact remains that some companies/organizations/governments are stuck on Internet Explorer 6, or maybe even 7. If you are a web developer, and write cross-browser web applications, chances are Internet Explorer has hit you more than once in your career.
It's the dumbest things that hit you too. The most obscure "WTF!?!" moments that drive you crazy. That is a daily experience for me now.I've been a server-side developer for over two decades now, but in the last several years I have dealt more and more with client-side development. Why? Well, I started with HTML, and got into Javascript when it was introduced, and then got into server-side programming, so I already had some roots. With all of the work that I've done, working with <a href="http://www.sencha.com/products/extjs/" target="_blank">Ext JS</a> and <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a>, my current position threw me to the wolves to work on standardizing our application's interface development. Fun, right? It is (a lot), but the client is one of those mixed environments that is entrenched in IE, typically at 7 or 8. They can't upgrade, so it's our responsibility to make sure that our interfaces can render and function properly in these older, buggy, non-compliant POS browsers. (Note the frustration here.) Let me layout one of those typical, what-the-hell-is-goin-on-here, Internet Explorer moments for you.
<h3>The Application</h3>
Like most good development companies, we've made the shift to more modular, reusable code. It's a legacy codebase, serving the client well for years, and goes through constant review, revision, update and improvement. Many of us (developers in general) would love to just scrap our current codebase and start from scratch, but that's not really practical in most situations. What happens is you begin to slowly move away from <a href="http://www.laputan.org/mud/" target="_blank">The Big Ball of Mud</a> design pattern, by replacing small pieces of your application as updates/changes are required, with more modern code. Rather than one huge application, what you end up with are small, mini-applications, that can be plugged in anywhere within an application. Each of these 'applications' then have their own supporting files: html, javascript, and css. Welcome to the fun.
<h3>Collision Control</h3>
At this point, the issue isn't necessarily browser specific, but rather logistics. When you start adding in multiple mini-applications, things can (and will) butt heads. You will have issues with race conditions, as multiple scripts use variables of the same names, have functions with the same name that override each other due to execution order, id's repeated in a page (IE hates this a lot), and multiple event handlers accidentally added to the same DOM controls because selectors aren't specific enough. You tighten up your DOM selectors, come up with some variable and DOM naming standards, and learn about namespacing. Each a slow, progressive step towards sanity.
Then there's the issue of accidentally loading the same files multiple times. Our application uses JQueryUI, <a href="http://cfuniform.riaforge.org/" target="_blank">cfUniform</a>, <a href="http://www.tinymce.com" target="_blank">TinyMCE</a>, and <a href="http://www.trirand.com/blog/" target="_blank">jqGrid</a> extensively. Each of these mini-apps loads it's own support files. Suddenly, you're faced with coming up with systems of control, to ensure that you aren't loading scripts and CSS multiple times.
<h3>Back to the Browser</h3>
There's a ton of debate out there about loading multiple scripts and css files. Loading multiple files has performance issues for initial download, but maintenance is far easier with these bit modularized like your display and model. In many applications things aren't complex enough for multiple files to truly be an issue. But, when you are writing a complex application, like scheduling software, customer resource management, content management, etc., the number of 'modules' you might include in a page can grow and grow and grow, especially if your interface makes heavy use of ajax. This is where Internet Explorer can actually force your hand into creating a better experience, unintentionally and generally when it's inconvenient.
What happens when you load up your app, and notice that your forms (cfUniform) don't appear to be styled, and your highly configured and customized <abbr title="What You See Is What You Get">WYSIWYG</abbr> editor (TinyMCE) appears to be half loaded? But, only in IE? Well, you start troubleshooting. Maybe cfUniform didn't load? No, the stylesheet is what's important here, and Developer Tools say it's there. The editor? Did it initialize? Well, the style and font dropdown info is there, even if they aren't in dropdowns. What's going on?
You start removing script files, one at a time, to make sure there isn't a conflict. But wait, it works in everything but IE? Well, you try it anyway. You step debug things, trying to see a break. And you Google (and Google, and Google). These are my days. And, after three days of this single issue, you finally stumble on <a href="http://john.albin.net/css/ie-stylesheets-not-loading" target="_blank">an obscure post</a> (page 15 of a Google search). Please, tell me you're kidding me...
Yes, Internet Explorer (at least 6 through 9) only apply up to 31 stylesheets and/or style blocks. So, when your page (1 specific stylesheet) uses JQueryUI (1 plugin stylesheet), and a menu (3 plugin stylesheets), and a nav widget (1 plugin stylesheet), plus cfUniform (3 plugin + 2 override stylesheets), and jqGrid (1 plugin stylesheet), and TinyMCE (multiple dynamically loaded stylesheets, depending on plugin configuration), and...
You get the picture. Oh, and did I mention that some of those stylesheets were 10 line, IE only stylesheets to hack IE's different handling of CSS? Yeah, salt in the wound. To test this, I went looking for something that might be loaded by default, but wasn't needed on this page of my app (or at least, not for what I was testing). I found a few stylesheets I could disable in local development, and reloaded my page. Imagine my surprise when everything rendered as it should. I had to toggle it back and forth a few times just to verify. I think I spewed profanity for several minutes.
<h3>What To Do?</h3>
OK, so we had already been looking to form a strategy for combining files and minification. Notice I said "looking to". That is to say, we wanted one, and knew we needed one, and even done a little research, but we hadn't <b>solidified</b> one. If we had, we'd already have been working on it. Now, under the gun, with deadlines looming, we have to put it in gear. What to do? Well, in this case, improvise. Time not being on our side, we have to do this manually. Identify multiple CSS files that are used either constantly (every single page load) or extensively (85%+ page view). Combine and compress, and remove single <em>link</em> references from the code. Now, I didn't do this completely manually. I did write a script where I could define which files, in what order, and have the script build the file, while correcting internal url references so that image paths wouldn't break. Then I used an online compressor to minify the file. After all of this work (took the good part of an afternoon), I reload my pages and all of them function as intended, in all browsers. And, compressing 10 files down to 1, I now get my form styling, and my editor displays correctly, etc.
OK, is this the ideal solution? No, we'll still develop a comprehensive (and automated) strategy. But, this gets us over the deadline hump, and proves that the multiple stylesheets were the issue, and that this type of action can correct the issue. Proof of concept. You have to start somewhere I guess. But, how much easier web development would be if we didn't have to support Microsoft's mistakes.
jqGridSenchaAjaxDevelopmentJQueryUsabilityExtJSThu, 09 Feb 2012 10:51:00 -0000https://www.cutterscrossing.com/index.cfm/2012/2/9/The-Joys-of-Developing-for-Internet-ExplorerIntro to jqGrid Part 5: Searchhttps://www.cutterscrossing.com/index.cfm/2012/1/13/Intro-to-jqGrid-Part-5-Some-Other-Stuff
At this point we've <a href="http://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid">created a basic grid</a>, <a href="http://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-Data">filled it with data</a>, <a href="http://www.cutterscrossing.com/index.cfm/2011/12/28/Intro-to-jqGrid-Part-3-Columns">refined the display of our columns</a> and <a href="http://www.cutterscrossing.com/index.cfm/2012/1/4/Intro-to-jqGrid-Part-4-Event-Handling">added event handlers</a> to handle multiselect options. We've <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:predefined_formatter" target="_blank">used custom cell formatters</a>, used a custom <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#function" target="_blank">datatype <em>function</em></a>, and even added and populated a <a href="www.trirand.com/jqgridwiki/doku.php?id=wiki:options" target="_blank">toolbar</a> 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 <em>function</em>, is that we can preprocess our <em>postdata</em> prior to the ajax request.First, let's add a simple form to our example:
<h4>index.html - Simple Search</h4>
<code>
<form action="" name="searchForm" id="searchForm">
<fieldset title="Search">
<label for="title">Title</label><br />
<input type="text" name="title" size="100" /><br />
<div class="col">
<label for="from">From</label><br />
<input type="text" name="from" class="addDatePicker" />
</div>
<div class="col">
<label for="to">To</label><br />
<input type="text" name="to" class="addDatePicker" />
</div><br clear="all" />
To search for entries on a specific date, use the <em>To</em> field only.
<input type="submit" value="Search" name="searchBtn" id="searchBtn" /><br clear="all" />
</fieldset>
</form>
<div id="gridBlock">
...
</code>
Looking at our demo grid, I only truly have two fields to search on: <em>Title</em> and <em>Release Date</em>. So, we provide for searching against our <em>Title</em>, and for searching within a date range. To top off our simple search form, let's turn those <em>from</em> and <em>to</em> fields into <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a> <a href="http://jqueryui.com/demos/datepicker/" target="_blank">DatePicker</a> fields.
<h4>jqGridDemo.js - DatePicker initialization</h4>
<code>
$('input.addDatePicker').datepicker({
showOn: 'button',
buttonImage: '/resources/images/icons/calendar.png',
buttonImageOnly: true,
dateFormat: 'mm/dd/yy'
});
</code>
This adds a DatePicker element to any input with the <em>addDatePicker</em> class. At some point you'll want to add some range validation (client and server side).
For the moment the <em>action</em> attribute is blank. That's OK, for us, as we're going to provide a custom submit handler in our script:
<h4>jqGridDemo.js - Search Form Submit Handler</h4>
<code>
$('form#searchForm').submit(function(ev){
ev.preventDefault();
// something will go here
return false;
});
</code>
Next, we're going to think about how we're using our 'search'. Basically, this is a filter we're going to apply through our query: a list of fields and values to narrow our result set by set criteria. We can param all our fields in our remote method (and should), so to avoid confusion we're going to 'scrub' our form prior to making our ajax request. Basically, we don't need anything that's empty, or with a value of '0'.
<h4>jqGridDemo.js - scrubSearch</h4>
<code>
var scrubSearch = function(){
var frm = $('form#searchForm').serializeJSON();
for(var i in frm){
var val = frm[i];
// if value has no length, remove the key
if(val.length === 0){
delete frm[i];
}
if(!isNaN(val-0) && parseInt(val) === 0){
delete frm[i];
}
}
return frm;
};
</code>
"Wait a minute, Cutter. <a href="http://www.jquery.com" target="_blank">JQuery</a> doesn't have a <em>serializeJSON</em> method." No, it doesn't. JQuery has <a href="http://api.jquery.com/serializeArray/" target="_blank">serializeArray</a>, for pulling form values out, but doesn't have a nice method for pulling that into object notation, which is easier to work with. <a href="http://arjen.me" target="_blank">Arjen Oosterkamp</a> posted this little plugin, in the comments of the <em>serializeArray</em> documentation. While good, the one fault I had was that it didn't turn repeating field name (like checkbox usage) values into a list, which is standard behavior on a form submit, so I modified his work to accommodate.
<h4>jqGridDemo.js - serializeJSON</h4>
<code>
// Before your document ready statement
(function( $ ){
$.fn.serializeJSON=function() {
var json = {};
jQuery.map($(this).serializeArray(), function(n, i){
(json[n['name']] === undefined) ? json[n['name']] = n['value'] : json[n['name']] += ',' + n['value'];
});
return json;
};
})( jQuery );
</code>
By applying this method to our form, we get a nice object that we can then loop through for value comparisons. We return the object after we've removed any keys with empty or <em>0</em> values. Since form values are strings by default, we have to work a little magic for finding numeric values (JQuery 1.7 includes the new <a href="http://api.jquery.com/jQuery.isNumeric/" target="_blank">$.isNumeric()</a> method, but we aren't using 1.7 in our examples).
Now, on first look you might think we're going to call this from within our form's submit handler, but that's not the case. Our example is using a basic <em>html</em> page, but you might be using a dynamic page where you may pre-populate your form fields with data passed in the initial page request, to pre-filter grid results for some reason or another. For this reason, you would want to have this method called immediately on page load, and on any additional request as well. For this, we'll go back to our <em>populateGrid</em> method.
<h4>jqGridDemo.js - populateGrid</h4>
<code>
var populateGrid = function (postdata) {
$.ajax({
...
data: $.extend(true, {}, postdata, {search: $.toJSON(scrubSearch())}),
...
};
</code>
We used JQuery's <a href="" target="_blank">$.extend()</a> method to create a new <em>data</em> object, combining our <em>postdata</em> with a new object including <em>search</em>. JQuery (I wish I knew why) does not include anything for converting an object to a JSON string (only the reverse, with <a href="http://api.jquery.com/jQuery.parseJSON/" target="_blank">$.parseJSON()</a>), so we must include another plugin library for making this possible. Luckily there's the <a href="http://code.google.com/p/jquery-json/" target="_blank">jquery-json</a> project on Google Code, which is heavily influenced by work done at <a href="http://www.json.org" target="_blank">JSON.org</a>. All we need, to access the <em>$.toJSON()</em> method, is to add the file to our html head.
<h4>index.html - header script includes</h4>
<code>
...
<script type="text/javascript" src="/resources/scripts/jquery-plugins/jqgrid-4.3.1/js/jquery.jqGrid.min.js"></script>
<script type="text/javascript" src="/resources/scripts/jquery-plugins/jquery-json/jquery.json-2.3.min.js"></script>
<script type="text/javascript" src="/resources/scripts/custom/jqGridDemo.js"></script>
...
</code>
By handling this, in this fashion, the form's contents are taken into account on every grid load request. This means that paging and sorting requests will have the filter criteria, until such time as the form is cleared. It also means that our form's submit handler becomes that much simpler.
<h4>jqGridDemo.js - Search Handler figure 2</h4>
<code>
$('form#searchForm').submit(function(ev){
ev.preventDefault();
grid.trigger('reloadGrid');
return false;
});
</code>
This is great! When you submit the form the grid's contents are now filtered by your search criteria. The only thing you have to do now is have a way to 'clear' your search criteria. You can easily do this by adding a <em>reset</em> button to the form, and binding to the reset event.
<h4>index.html - Search Form Buttons</h4>
<code>
...
<input type="submit" value="Search" name="searchBtn" class="searchFormBtns" />
<input type="reset" value="Reset" class="searchFormBtns" /><br clear="all" />
...
</code>
<h4>jqGridDemo.js - Search Form Reset Handler</h4>
<code>
$('form#searchForm').bind('reset',function(ev){
setTimeout("$('#gridTest').trigger('reloadGrid');",1);
});
</code>
Our binding needs us to give the browser a moment to 'clear' the field values, prior to us triggering a grid reload. Since <em>setTimeout</em> will have no concept of scope, we cannot use our <em>grid</em> variable here.
This all takes care of things, from an interface perspective, but we still need to handle these filter requests at the server. We're going to first change our remote method signature to accept our new <em>search</em> parameter, which is now included on every request as a JSON string.
<h4>Entries.cfc - getEntries Method Signature</h4>
<code>
function GetEntries(numeric pageIndex = 1, numeric pageSize = 50, string sortCol = "ID", string sortDir = "desc", string search = "") {
</code>
We can then test the argument for value, and <em>param</em> all our search fields
<h4>Entries.cfc - getEntries figure 2</h4>
<code>
function GetEntries(numeric pageIndex = 1, numeric pageSize = 50, string sortCol = "ID", string sortDir = "desc", string search = "") {
...
if(Len(ARGUMENTS.search) AND IsJSON(ARGUMENTS.search)){
ARGUMENTS.search = DeserializeJSON(ARGUMENTS.search);
} else {
ARGUMENTS.search = {};
}
param name="ARGUMENTS.search.title" default="";
param name="ARGUMENTS.search.from" default="";
param name="ARGUMENTS.search.to" default="";
...
}
</code>
This now gives us some things to key off of for building our 'filtered' query. Parts of this are fairly simple: If the field exists, add the filter to the query, and the param to the query definition. The <em>title</em> is a good example.
<h4>Entries.cfc - getEntries figure 3</h4>
<code>
...
// Main data query
LOCAL.sql = "SELECT SQL_CALC_FOUND_ROWS id,
title,
posted,
views
FROM tblblogentries
WHERE 0 = 0
";
if(Len(ARGUMENTS.search.title)){
LOCAL.sql &= " AND title LIKE :title
";
}
LOCAL.sql &= "ORDER BY #LOCAL.orderby#
LIMIT :start,:numRec";
LOCAL.q = new Query(sql = LOCAL.sql);
LOCAL.q.addParam(name = "start", value = (ARGUMENTS.pageIndex-1) * ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
LOCAL.q.addParam(name = "numRec", value = ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
if(Len(ARGUMENTS.search.title)){
LOCAL.q.addParam(name = "title", value = "%#ARGUMENTS.search.title#%", cfsqltype = "cf_sql_varchar");
}
...
</code>
The <em>Release Date</em> stuff is a bit more tricky, as you have to think about how dates are treated. First, you may not need to worry about working with the <em>from</em> and <em>to</em> fields at all. We can do a little pre-check right after our <em>param</em> statements.
<h4>Entries.cfc - getEntries figure 4</h4>
<code>
...
LOCAL.hasFrom = Len(ARGUMENTS.search.from) AND IsDate(ARGUMENTS.search.from);
LOCAL.hasTo = Len(ARGUMENTS.search.to) AND IsDate(ARGUMENTS.search.to);
...
</code>
This allows us to verify that a value was passed, and that the string is a valid date value. Next we have to consider our logic. We stated on our form that the user can search for a specific day by only filling the <em>To</em> field. This means that, at the least, we need to have value in the <em>To</em> field to filter <em>Release Date</em>.
<h4>Entries.cfc - getEntries figure 5</h4>
<code>
// Main data query
LOCAL.sql = "SELECT SQL_CALC_FOUND_ROWS id,
title,
posted,
views
FROM tblblogentries
WHERE 0 = 0
";
if(Len(ARGUMENTS.search.title)){
LOCAL.sql &= " AND title LIKE :title
";
}
if(LOCAL.hasTo){
LOCAL.sql &= "AND posted BETWEEN :from
AND :to
";
}
</code>
What you see here is that we've added an <em>AND BETWEEN</em> clause to the query if <em>to</em> is passed in. But, why <em>BETWEEN</em>? The <em>posted</em> field, in our database, is a <em>DATETIME</em> datatype. So, for instance, you searched for entries posted on <em>08/01/2011</em> you would actually need to search the database for anything posted <em>BETWEEN</em> 00:00:00 and 23:59:59 of 08/01/2011. We need to handle either a range, or a single date. The basic sql is the same (<em>AND BETWEEN</em> statement), so what we have to concern ourselves with is how we create our <em>from</em> and <em>to</em> sql paramaters.
<h4>Entries.cfc - getEntries figure 6</h4>
<code>
if(LOCAL.hasFrom AND LOCAL.hasTo){
LOCAL.q.addParam(name = "from", value = CreateODBCDateTime(ARGUMENTS.search.from), cfsqltype = "cf_sql_timestamp");
LOCAL.q.addParam(name = "to", value = CreateODBCDateTime(ARGUMENTS.search.to & "23:59:59"), cfsqltype = "cf_sql_timestamp");
} else if (!LOCAL.hasFrom AND LOCAL.hasTo){
LOCAL.q.addParam(name = "from", value = CreateODBCDateTime(ARGUMENTS.search.to), cfsqltype = "cf_sql_timestamp");
LOCAL.q.addParam(name = "to", value = CreateODBCDateTime(ARGUMENTS.search.to & "23:59:59"), cfsqltype = "cf_sql_timestamp");
}
</code>
<a href="http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-6deb.html" target="_blank">CreateODBCDateTime</a> will create a sql date object format that the JDBC connector will properly convert for it's db and datatype. Thing is, when given a date, without time, then the date defaults to <em>{ts 'yyyy-mm-dd 00:00:00'}</em>. For this reason, our <em>to</em> value must add on the time to the last second of that day, to properly search any entries through that day. The says if a <em>from</em> and <em>to</em> were passed, then filter between the beginning of <em>from</em> and the end of <em>to</em>, else if only <em>to</em> was passed, then filter from the beginning of <em>to</em> to the end of <em>to</em>.
The last thing to notice in these blocks of code is the concatenation of the <em>LOCAL.sql</em> string that builds our query. You may notice, in looking at these code samples, that there appears to be an additional line added to each. This is to accomodate a bug in ColdFusion's scripted sql parser, in <a href="http://www.cutterscrossing.com/index.cfm/2011/11/14/A-Scripted-Query-Param--Whitespace-Gotcha">dealing with whitespace when using sql parameters</a>. If you look at this code with whitespace characters being shown, you will see the additional spaces required.
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt5.png" />
</div>
And that's it! Now, when you submit search criteria, your grid will be filtered on the information you've sent, and retain your criteria across paging criteria until you clear the search form.
<div style="text-align:center;">
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
<param name="movie" value="/images/jqGridDemo-Search.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#FFFFFF" />
<param name="flashVars" value="width=815&height=700" />
<param name="allowFullScreen" value="true" />
<param name="scale" value="showall" />
<param name="allowScriptAccess" value="always" />
<embed allowFullScreen="true" allowScriptAccess="always" bgcolor="#FFFFFF" quality="high" scale="showall" src="/images/jqGridDemo-Search.swf" type="application/x-shockwave-flash" flashVars="width=815&height=700" height="700" width="815" />
</object>
</div>
That's it for now. In our next post we'll finally get around to those icons in the <em>Action</em> column. Example code for this post may be found in the <b>Download</b> link below.
jqGridDevelopmentJQueryColdFusionFri, 13 Jan 2012 14:20:00 -0000https://www.cutterscrossing.com/index.cfm/2012/1/13/Intro-to-jqGrid-Part-5-Some-Other-StuffIntro to jqGrid Part 4: Event Handlinghttps://www.cutterscrossing.com/index.cfm/2012/1/4/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 <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs" target="_blank">extensive documentation</a>, and started to figure a few things out on your own. In our series, we've <a href="http://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid">created a basic grid</a>, <a href="http://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-Data">populated it with remote data</a>, and <a href="http://www.cutterscrossing.com/index.cfm/2011/12/28/Intro-to-jqGrid-Part-3-Columns">refined our column configuration</a> 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.
<h2>Toolbars</h2>
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:
<h4>jqGridDemo.js - Toolbar Config</h4>
<code>
grid.jqGrid({
...
toolbar:[true,"top"],
...
});
</code>
The <em>toolbar</em> 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:
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt4.png" style="width:810px;height:456px;" />
</div>
Now that we have a toolbar, we need to add some buttons. <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a> 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 <em>buttons</em>.
<h4>jqGridDemo.js - Toolbar Buttons</h4>
<code>
$('#t_gridTest').append('<button id=\"addButton\">Add</button><button id=\"exportButton\">Export</button>').addClass('customToolbar');
</code>
<h4>jqGridDemo.js - JQUI Buttons</h4>
<code>
$('button#addButton').button({icons: {primary: 'add'}}).click(function(ev){
ev.preventDefault();
return false;
});
$('button#exportButton').button({icons: {primary: 'page_white_go'}, disabled: true}).click(function(ev){
ev.preventDefault();
return false;
});
</code>
Here we've created an <em>Add</em> and an <em>Export</em> button on our toolbar, while also adding a new CSS class (customToolbar). Then I made JQUI <em>buttons</em> of our new toolbar buttons, applying appropriate icon classes (defined in our css file). I've also <em>disabled</em> the <em>Export</em> 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 <em>tableToGrid</em> method to make a simple view table. But, chances are you're writing a Data Grid implementation to <em>interact</em> with data. And a common action is to <em>select</em> a record (or records) to interact with.
<h2>Multiselect</h2>
There are, essentially, three types of selection actions with a Data Grid: <em>individual</em> record selection, <em>multiple</em> record selection, and specific <em>cell</em> selection. The act of any of these selections is an <em>event</em>, and jqGrid includes the ability to add custom <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events" target="_blank">Event Handlers</a> 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:
<h4>jqGridDemo.js - Multiselect Configuration</h4>
<code>
grid.jqGrid({
...
multiselect: gridMultiSelect > 0,
multiselectWidth: 25,
...
});
</code>
These are direct grid configuration options. The first, <em>multiselect</em>, is a boolean value that, if true, will automatically create a column of checkboxes on our grid. The second option, <em>multiselectWidth</em>, defines the width of that checkbox column. Notice that I've tied the <em>multiselect</em> attribute to the <em>gridMultiSelect</em> 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.
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt4b.png" style="width:809px;height:466px;" />
</div>
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 (<em><strong>NOTE</strong>: this only selects all currently loaded records in the grid</em>). That <em>gridMultiSelect</em> 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 <em>selarrrow</em>. You can programmatically access those at any time by grabbing that variable from jqGrid.
<code>
grid.jqGrid('getGridParam','selarrrow');
</code>
<h2>Id's</h2>
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 <em>ID</em> of our records, getting a generic rowcount for our id instead. This is because we've used the <em>function</em> datatype, and remapped our JSON to our column configuration, so the <em>id</em> attribute of the <em>jsonReader</em> 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 <em>key</em> in our column model. This will setup an internal <em>keyIndex</em> variable that jqGrid uses in it's processes. Under normal circumstances, the <em>key</em> attribute would be defined on your <em>ID</em> 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 <em>ID</em> column in the response, then apply that <em>key</em> attribute to that column index in the column model configuration. In our example the <em>ID</em> 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.
<h4>jqGridDemo.js - Column Model Configuration</h4>
<code>
colModel: [
{name: 'ID', hidden: true, key: true},
...
],
</code>
The importance in this can be viewed in the output. Here's a sample of the before and after difference in what jqGrid generates:
<h4>Grid Row Output - Before Key Definition</h4>
<code>
<tr id="1" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">
</code>
<h4>Grid Row Output - After Key Definition</h4>
<code>
<tr id="FF318BAB-3048-71C2-17E1634637074ECF" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">
</code>
Why is this important? When you go to the next page of records, if you hadn't applied the <em>key</em> then the first record displayed would also show an id of <em>1</em>. And this now brings us to the bit that we're going to address with jqGrid. If you select a few rows, pull the <em>selarrrow</em>, 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.
<h2>Event Handlers</h2>
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 <em>Event Handler</em> for our selections, and programmatically control it. First, let's create another global variable to hold selected id's.
<code>
var gridCols = {set:false},
gridMultiSelect = 1,
selArr = [];
</code>
This is just an array, just like jqGrid uses internally. Next thing we'll do is setup some <em>Event Handlers</em>. Any time a selection (or deselection) is made, then we need to control the contents of our new <em>selArr</em> array. First we'll setup a method to apply to our <em>onSelectRow</em> grid <em>Event Handler</em>.
<h4>jqGridDemo.js - rowSelectionHandler</h4>
<code>
var rowSelectionHandler = function (id, status) {
// process code here
};
</code>
<h4>jqGridDemo.js - Grid Configuration Event Handler</h4>
<code>
grid.jqGrid({
...
onSelectRow: rowSelectionHandler,
...
});
</code>
A review of jqGrid's <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events" target="_blank">Events</a> documentation shows that the <em>onSelectRow</em> attribute will apply an event handler, and passes two arguments: <em>id</em> - the record <em>id</em> of the row being selected, and <em>status</em> - a boolean value noting selection (true) or deselection (false). jqGrid's <em>onSelectAll</em> attribute is similar, passing <em>idArr</em> - an array of selected row <em>id</em>'s, and <em>status</em> - if all were selected or deselected. We'll setup a handler for that as well.
<h4>jqGridDemo.js - selectAllHandler</h4>
<code>
var selectAllHandler = function (id, status) {
// process code here
};
</code>
<h4>jqGridDemo.js - Grid Configuration Event Handler</h4>
<code>
grid.jqGrid({
...
onSelectAll: selectAllHandler,
...
});
</code>
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.
<h4>jqGridDemo.js - Handlers and selectionManager</h4>
<code>
var rowSelectionHandler = function (id, status) {
selectionManager(id, status);
// anything else
};
var selectAllHandler = function (idArr, status) {
selectionManager(idArr, status);
// anything else
};
var selectionManager = function (id, status) {
// was it checked (true) or unchecked (false)
if(status){
// if it's just one id (not array)
if(!$.isArray(id)){
// if it's not already in the array, then add it
if($.inArray(id,selArr) < 0){selArr.push(id)}
} else {
// which id's aren't already in the 'selected' array
var tmp = $.grep(id,function(item,ind){
return $.inArray(item,selArr) < 0;
});
// add only those unique id's to the 'selected' array
$.merge(selArr,tmp);
}
} else {
// if it's just one id (not array)
if(!$.isArray(id)){
// remove that one id
selArr.splice($.inArray(id,selArr),1);
} else {
// give me an array without the 'id's passed
// (resetting the 'selected' array)
selArr = $.grep(selArr,function(item,ind){
return $.inArray(item,id) > -1;
},true);
}
}
$('#t_gridTest button#exportButton').button((selArr.length > 0)?'enable':'disable');
};
</code>
I've tried to comment the code above to explain our new <em>selectionManager</em> method. The only line I haven't explained is the last, which either enables or disables the <em>Export</em> 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 <em>selArr</em> 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 <em>gridComplete</em> attribute.
<h4>jqGridDemo.js - - Grid Configuration Event Handler</h4>
<code>
grid.jqGrid({
...
gridComplete: gridLoadInit,
...
});
</code>
<h4>jqGridDemo.js - gridLoadInit</h4>
<code>
var gridLoadInit = function () {
// if the 'selected' array has length
// then loop current records, and 'check'
// those that should be selected
if(selArr.length > 0){
var tmp = grid.jqGrid('getDataIDs');
$.each(selArr, function(ind, val){
var pos = $.inArray(val, tmp);
if(pos > -1){
grid.jqGrid('setSelection',val);
}
});
}
};
</code>
jqGrid's <em>gridComplete</em> attribute allows us to define an <em>Event Handler</em> that fires once data has loaded into the grid (this is not to be confused with <em>loadComplete</em>, which occurs after every server request.) What we're saying here is, after the data is rendered in the grid we will loop the <em>selArr</em> array and 'check' any id's that match any records displayed in our grid. This <em>gridLoadInit</em> method, that we've created, will now run anytime the grid's data is reloaded (initial load, paging requests, sorting, etc).
<div style="text-align:center;">
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
<param name="movie" value="/images/jqGridDemo-SelectionPersistence.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#FFFFFF" />
<param name="flashVars" value="width=815&height=700" />
<param name="allowFullScreen" value="true" />
<param name="scale" value="showall" />
<param name="allowScriptAccess" value="always" />
<embed allowFullScreen="true" allowScriptAccess="always" bgcolor="#FFFFFF" quality="high" scale="showall" src="/images/jqGridDemo-SelectionPersistence.swf" type="application/x-shockwave-flash" flashVars="width=815&height=700" height="700" width="815" />
</object>
</div>
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 <em>key</em> column, in setting proper row <em>id</em>'s, how to add a checkbox column through the <em>multiselect</em> 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 <em>Action</em> 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 <b>Download</b> link at the bottom of the post.
jqGridDevelopmentJQueryColdFusionWed, 04 Jan 2012 14:26:00 -0000https://www.cutterscrossing.com/index.cfm/2012/1/4/Intro-to-jqGrid-Part-4-Event-Handling2011 In Review, and the View for 2012https://www.cutterscrossing.com/index.cfm/2012/1/2/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 <a href="http://www.cutterscrossing.com/index.cfm/2011/1/3/Out-With-the-Old-In-With-the-New-Welcome-2011">welcoming in 2011</a>, 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 <a href="http://www.raymondcamden.com" target="_blank">Ray Camden</a> or <a href="http://www.bennadel.com" target="_blank">Ben Nadel</a>, 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 creating<a href="http://www.cutterscrossing.com/index.cfm/2011/1/13/Many-Sites-One-Codebase">many sites with one codebase</a>. In the process, the series has evolved to contain a fairly detailed primer in <a href="http://www.adobe.com/products/coldfusion" target="_blank">ColdFusion</a> application architecture (because of it's importance to this process), has currently spanned <a href="http://www.cutterscrossing.com/index.cfm/MSOC">8 separate posts</a>, and was even referenced by <a href="http://corfield.org/blog/" target="_blank">Sean Corfield</a> 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 <a href="http://www.cutterscrossing.com/index.cfm/jqGrid">jqGrid</a> JQuery plugin. jqGrid is another Data Grid visualization tool (I have now written about three, including <a href="http://www.sencha.com/products/extjs" target="_blank">Ext JS</a> and <a href="http://www.datatables.net" target="_blank">DataTables</a>), and is a clear choice for those who must use JQuery. (To be fair, <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a> 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 <a href="http://www.cutterscrossing.com/index.cfm/cfscript">cfscript</a>. 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, <a href="http://www.cutterscrossing.com/index.cfm/2011/8/13/New-Job-New-Home-A-Lot-of-Work">I stopped writing Ext JS books</a>. This is not, in any way, a reflection on my feelings for Ext JS. I still believe that <a href="http://www.sencha.com" target="_blank">Sencha</a> 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 <a href="http://jaxfusion.groups.adobe.com/" target="_blank">Northeast Florida CFUG</a>. 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 <a href="http://www.github.com/cutterbl" target="_blank">GitHub</a>. I've been using Git at work, and I am loving it, and I think combining GitHub with <a href="http://www.riaforge.org" target="_blank">RIAForge</a> 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, <a href="http://www.adobe.com" target="_blank">Adobe</a>. I again had the pleasure of being an <a href="http://www.cutterscrossing.com/index.cfm/2011/1/28/Im-an-ACP-again-for-2011">Adobe Community Professional</a> 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 <em>you</em> have planned for 2012.
MSOCDataTablesHTML5jqGridCFScriptLearning ExtJSSenchaAjaxDevelopmentJQueryColdFusionMy 2 centsThis BlogApplication SetupMy First ExtJS DataGridAdobeWho I AmExtJSMon, 02 Jan 2012 11:01:00 -0000https://www.cutterscrossing.com/index.cfm/2012/1/2/2011-In-Review-and-the-View-for-2012Intro to jqGrid Part 3: Columnshttps://www.cutterscrossing.com/index.cfm/2011/12/28/Intro-to-jqGrid-Part-3-Columns
OK, in our last two posts we <a href="http://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid">built a basic grid</a> and <a href="http://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-Data">populated it with data</a>. As you can see, so far it's a very basic grid.
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt2.png" style="width:810px;height:435px;" />
</div>
Nothing much to it, really. Let's start adding a few important pieces. jqGrid includes a large set of <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options" target="_blank">Column Model Options</a>, but there really aren't a ton that you'll need. Here we'll run through some basics.
First, the <em>id</em> field really isn't something you typically need to show anyone. So, just hide it.
<h4>jqGridDemo.js - Column Model - Hide</h4>
<code>
colModel: [
{name: 'ID', hidden: true},
...
],
</code>
Oh yeah, and the <em>views</em> column is a count of the number of page views. As a number, it should probably be right justified.
<h4>jqGridDemo.js - Column Model - Align</h4>
<code>
colModel: [
...
{name: 'VIEWS', align: 'right'}
],
</code>
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.
<h4>jqGridDemo.js - Column Model - Width</h4>
<code>
colModel: [
...,
{name: 'VIEWS', align: 'right', width: 60, fixed: true}
],
</code>
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: <em>index</em>, <em>label</em> and <em>name</em>. Up until now we've used the <em>name</em> 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 <em>label</em> option.
<h4>jqGridDemo.js - Column Model - Label</h4>
<code>
colModel: [
...,
{name: 'POSTED', label: 'Release Date'}
],
</code>
This changed the <em>label</em> used in the column header, while maintaining a reference used when sorting the grid by the <em>posted</em> field. This is good, until you do something like this:
<h4>jqGridDemo.js - Column Model - Remap</h4>
<code>
grid.jqGrid('setGridParam',{remapColumns:[
gridCols['ID'] + gridMultiSelect,
gridCols['ID'] + gridMultiSelect,
gridCols['TITLE'] + gridMultiSelect,
gridCols['POSTED'] + gridMultiSelect,
gridCols['VIEWS'] + gridMultiSelect
]});
</code>
<h4>jqGridDemo.js - Column Model - Index</h4>
<code>
colModel: [
{name: 'ID', hidden: true},
{name: 'Action', index: 'ID', label: 'Action', width: 80, fixed: true, sortable: false, align: 'center'},
{name: 'Title'},
{name: 'Posted', label: 'Release Date'},
{name: 'Views', align: 'right', width: 60, fixed: true}
],
</code>
"Cutter, What are you doin' ta me!?!" Yeah, now it's confusing. I've added a column. A column that also references the <em>ID</em> field in the return dataset. In this instance the <em>index</em> really isn't truly necessary, but I'll try to explain it for you anyway. Up until now, jqGrid has used the <em>name</em> 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 <em>name</em> 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 <em>name</em>), and the <em>index</em> field reference that jqGrid will send back to the server on sort requests (again, with the <em>sortable: false</em> I've thrown in here, it's really moot for us). So, to recap:
<ul>
<li><em>name</em> - A unique column reference used by jqGrid</li>
<li><em>index</em> - A data field reference used in sort requests. If not present then the <em>name</em> is used.</li>
<li><em>label</em> - If present it will override the <em>name</em> option, for what to display in the column header.</li>
</ul>
You probably noticed that I added a little something to our column remap code.
<code>
gridCols['ID'] + gridMultiSelect,
</code>
This goes along with a new variable I added to our global variable declarations at the very top of our script.
<code>
var gridCols = {set:false},
gridMultiSelect = 0;
</code>
I'll probably not use that on this round, but it will become important, so I'll leave it for now.
<h3>Column Formatting</h3>
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 <em>Action</em> column. Right now, if you ran your template, you'd see a truncated <em>ID</em> value in the cells. We'll need that <em>ID</em> in our output, but the <em>Action</em> 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 <b>edit</b> 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 <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:predefined_formatter" target="_blank">predefined</a> formatters for many things, but you can also create your own <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:custom_formatter" target="_blank">custom</a> 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, <em>cellvalue</em>, <em>options</em>, and <em>rowObject</em>. The <em>cellvalue</em> is the value of the data that jqGrid is trying to apply to the cell (in our case, a record's <em>ID</em>). The <em>options</em> is an object containing the <em>rowId</em> and the <em>colModel</em> of the record being applied. The <em>rowObject</em> 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 <em>actionFormatter</em> function that returns just the first two characters of the <em>ID</em> field, to get started.
<h4>jqGridDemo.js - actionFormatter - figure 1</h4>
<code>
$.extend($.fn.fmatter, {
actionFormatter: function(cellvalue, options, rowObject) {
return cellvalue.substr(0,2);
}
});
</code>
<h4>jqGridDemo.js - Column Model - Custom Formatter</h4>
<code>
colModel: [
...
{name: 'Action', index: 'ID', label: 'Action', width: 80, fixed: true, sortable: false, align: 'center', formatter: 'actionFormatter'},
...
],
</code>
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.
<h4>jqGridDemo.css - icons</h4>
<code>
/* Basic layout of all trigger icons */
.icon-trigger { margin: 2px; vertical-align: middle; display: inline-block; width: 16px; height: 16px; }
.action-trigger { cursor: pointer; }
.disabled-trigger {opacity:0.4;filter:alpha(opacity=40)!important;}
/* delete icon image for trigger */
.delete { background: url('/resources/images/icons/delete.png') no-repeat scroll 0px 0px transparent !important; }
/* pencil icon image for trigger */
.pencil { background: url('/resources/images/icons/pencil.png') no-repeat scroll 0px 0px transparent !important; }
</code>
For our demo, I'm using the highly useful <a href="http://www.famfamfam.com/lab/icons/silk/" target="_blank">FamFamFam Silk</a> 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 <em>actionFormatter</em> to apply the proper output.
<h4>jqGridDemo.js - actionFormatter - figure 2</h4>
<code>
$.extend($.fn.fmatter, {
actionFormatter: function(cellvalue, options, rowObject) {
var retVal = "<span class=\'icon-trigger action-trigger pencil\' rel=\'" + cellvalue + "\' \/>";
retVal += "<span class=\'icon-trigger action-trigger delete\' rel=\'" + cellvalue + "\' \/>";
return retVal;
}
});
</code>
As you can see, now when you re-run your template you have a nice, formatted <em>Action</em> column, with action icons for 'edit' and 'delete'.
<div style="text-align:center;">
<img src="/images/jqGridDemo-pt3.png" style="width:810px;height:436px;" />
</div>
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 <b>Download</b> link at the bottom of the page.
jqGridDevelopmentJQueryColdFusionWed, 28 Dec 2011 16:10:00 -0000https://www.cutterscrossing.com/index.cfm/2011/12/28/Intro-to-jqGrid-Part-3-ColumnsIntro to jqGrid Part 2: Getting the Datahttps://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-Data
As I mentioned in an <a href="http://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid">Intro to jqGrid</a>, sometimes you have to deal with remote data that isn't in the 'standard' JSON recordset format. I also like to <a href="http://www.cutterscrossing.com/index.cfm/2011/9/26/How-I-Do-Things-ColdFusion-and-Ajax-Requests" target="_blank">reuse my server-side code</a>, 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:<h4>'Standard' JSON Recordset</h4>
<code>
{
data: [
{
id: "FF318BAB-3048-71C2-17E1634637074ECF",
title: "The ColdFusion 8 AJAX Components Debate",
posted: "June, 05 2007 23:51:00",
views: 7667
},
{
id: "FD48C350-3048-71C2-17244BE88532DEC9",
title: "ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
posted: "June, 08 2007 15:06:00",
views: 0
}
]
}
</code>
It's not uncommon to get recordsets in differing formats though. This is one of the reasons we're using jqGrid, because it provides function for using something different. Using ColdFusion for our backend, the JSON serialization of a native query object returns a slimmer, trimmer format for a recordset:
<h4>JSON Serialized ColdFusion Query Object</h4>
<code>
{
"COLUMNS": [
"ID",
"TITLE",
"POSTED",
"VIEWS"
],
"DATA": [
[
"FF318BAB-3048-71C2-17E1634637074ECF",
"The ColdFusion 8 AJAX Components Debate",
"June, 05 2007 23:51:00",
7667
],
[
"FD48C350-3048-71C2-17244BE88532DEC9",
"ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
"June, 08 2007 15:06:00",
0
],
...
]
}
</code>
I know, that example doesn't look much trimmer. But when you're returning 50+ records at a time, having those column names only listed once really does cut down on the bit traffic. The bottom line, though, is that ColdFusion's format for recordsets is different. You may see similar differences in calls to restful webservices. Because of these differences, the standard jqGrid <em>json</em> or <em>jsonstring</em> datatypes will not be sufficient, so a custom datatype <em>function</em> is in order.
Building a <em>function</em> datatype is really very easy. What you're telling jqGrid is that you want to manually handle the ajax request and result mapping. You may recall, in our <a href="">last post</a>, that we created the function stub for our datatype <em>function</em>:
<code>
/*
* FUNCTION populateGrid
* Used as the 'datatype' attribute of the jqGrid config object, this method
* is used to handle the ajax calls and data manipulation needed to populate
* data within our jqGrid instance.
* @postdata (object) - this is the object passed as the 'postData' attribute
* of our jqGrid instance.
*/
var populateGrid = function (postdata) {
// request and parsing code will go here
};
</code>
The meat of our actions will occur inside this method. jqGrid will automatically pass it's <em>postData</em> option to this method:
<h4>From the jqGridDemo configuration</h4>
<code>
grid.jqGrid({
...
postData: {method: "GetEntries", returnFormat: "JSON"},
datatype: populateGrid,
...
});
</code>
In our demo code, we've created a default <em>postData</em> object, defining the name of our remote method as <b>getEntries()</b>, and asking that the return value be formatted as JSON. This object is what is typically passed as the <em>data</em> configuration of a JQuery ajax request, and are required paramaters when making a remote call to a ColdFusion cfc. (If <em>returnFormat</em> is not passed, and is not specified in the ColdFusion function's description meta, then the return value will be formatted as <a href="http://en.wikipedia.org/wiki/WDDX" target="_blank">WDDX</a> by default.) Before we can write our remote call though, we need a remote method to which we can make our data request.
First, let's create a basic component for our Blog (this demo is written to work against BlogCFC).
<h4>Entries.cfc</h4>
<code>
/**
* @name Entries
* @displayName Blog Entries
* @output false
*/
component {
/*
* Creating some constants, for validity checking
*/
VARIABLES._COLUMNARRAY = ["id","title","posted","views"];
VARIABLES._DIRARRAY = ["asc","desc"];
}
</code>
Nothing special here. Just a basic component. I have included some constants that we can use to verify that some passed data is actual valid in our calls, but we still need our function.
<h4>GetEntries Method</h4>
<code>
/**
* FUNCTION GetEntries
* A function to get paging query of blog entries for layout in jqGrid
*
* @access remote
* @returnType struct
* @output false
*/
function GetEntries(numeric pageIndex = 1, numeric pageSize = 50, string sortCol = "ID", string sortDir = "desc") {
LOCAL.retVal = {"success" = true, "pageIndex" = ARGUMENTS.pageIndex, "pageCount" = 0, "recordCount" = 0, "message" = "", "data" = ""};
return LOCAL.retVal;
}
</code>
Basic function structure. Setup for remote access, with a returnType of 'struct'. Our arguments are defined and defaulted, with none of them as 'required'. We also create our default return value (<em>retVal</em>) variable. The meat of our process will adjust this variable prior to it's return in the closing line, but this defaults all of the keys, so that we only change what we need to in process.
So, the first thing I want to do is do any server-side validation that might be needed. We're going to dynamically build our query's ORDER BY clause. You can't queryparam the ORDER BY, so this is something you want to validate prior, to avoid potential SQL injection type issues.
<h4>GetEntries Method - Argument Validation</h4>
<code>
if(ArrayFindNoCase(VARIABLES._COLUMNARRAY, ARGUMENTS.sortCol) AND ArrayFindNoCase(VARIABLES._DIRARRAY, ARGUMENTS.sortDir)){
LOCAL.orderby = ARGUMENTS.sortCol & " " & ARGUMENTS.sortDir;
} else {
StructAppend(LOCAL.retVal,{"success" = false, "message" = "Your sort criteria is not valid."},true);
return LOCAL.retVal;
}
</code>
This says, if both of these are valid then build our ORDER BY string, otherwise return an error to the application.
Our next step is to put our query together. Remember that we're writing for paging datasets. I'm using MySQL on the backend, so my paging query looks like this
<h4>GetEntries Method - Data Query</h4>
<code>
LOCAL.sql = "SELECT SQL_CALC_FOUND_ROWS id,
title,
posted,
views
FROM tblblogentries
ORDER BY #LOCAL.orderby#
LIMIT :start,:numRec";
LOCAL.q = new Query(sql = LOCAL.sql);
LOCAL.q.addParam(name = "start", value = (ARGUMENTS.pageIndex-1) * ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
LOCAL.q.addParam(name = "numRec", value = ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
</code>
My app includes the datasource name, but you can add it in the <b>new Query()</b> statement, if it's needed. You can see the ORDER BY statement we built a moment ago, as well as our queryparams. Our <b>start</b> param is a computed value, because MySQL's LIMIT statement requires the number of the record itself (i.e.: 0,50 or 50,50 or 100,50 and so on). The <em>addParam()</em> method is the scripted way of doing <em><cfqueryparam></em>.
But we haven't run the query yet, but only set it up. Let's run the query, do some validation, and get some more data if we need it.
<h4>GetEntries Method - Get The Data</h4>
<code>
try {
LOCAL.retVal.data = LOCAL.q.execute().getResult();
if(LOCAL.retVal.data.recordCount){
/*
* The next statement is used to provide a TotalCount of all matched records.
*/
LOCAL.q.setSql("SELECT FOUND_ROWS() as totalCount");
LOCAL.totResult = LOCAL.q.execute().getResult();
if(LOCAL.totResult.recordCount){
LOCAL.retVal.recordCount = LOCAL.totResult.totalCount; // total number of records
LOCAL.retVal.pageCount = Ceiling(LOCAL.totResult.TotalCount / ARGUMENTS.pageSize); // total number of pages by pageSize
}
}
} catch (any excpt) {
LOCAL.retVal.success = false;
LOCAL.retVal.message = excpt.message;
}
</code>
We execute the query, applying the result to our return <em>data</em> value. If records are returned (and we hope they are), then we run another query to find out just how many records there are in all so we can set our total <em>recordCount</em> and <em>pageCount</em> variables. If no records were returned, then the return value defaults will work. The complete method looks like this:
<h4>Complete GetEntries</h4>
<code>
/**
* FUNCTION GetEntries
* A function to get paging query of blog entries for layout in jqGrid
*
* @access remote
* @returnType struct
* @output false
*/
function GetEntries(numeric pageIndex = 1, numeric pageSize = 50, string sortCol = "ID", string sortDir = "desc") {
LOCAL.retVal = {"success" = true, "pageIndex" = ARGUMENTS.pageIndex, "pageCount" = 0, "recordCount" = 0, "message" = "", "data" = ""};
if(ArrayFindNoCase(VARIABLES._COLUMNARRAY, ARGUMENTS.sortCol) AND ArrayFindNoCase(VARIABLES._DIRARRAY, ARGUMENTS.sortDir)){
LOCAL.orderby = ARGUMENTS.sortCol & " " & ARGUMENTS.sortDir;
} else {
StructAppend(LOCAL.retVal,{"success" = false, "message" = "Your sort criteria is not valid."},true);
return LOCAL.retVal;
}
LOCAL.sql = "SELECT SQL_CALC_FOUND_ROWS id,
title,
posted,
views
FROM tblblogentries
ORDER BY #LOCAL.orderby#
LIMIT :start,:numRec";
LOCAL.q = new Query(sql = LOCAL.sql);
LOCAL.q.addParam(name = "start", value = (ARGUMENTS.pageIndex-1) * ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
LOCAL.q.addParam(name = "numRec", value = ARGUMENTS.pageSize, cfsqltype = "cf_sql_integer");
try {
LOCAL.retVal.data = LOCAL.q.execute().getResult();
if(LOCAL.retVal.data.recordCount){
LOCAL.q.setSql("SELECT FOUND_ROWS() as totalCount");
LOCAL.totResult = LOCAL.q.execute().getResult();
if(LOCAL.totResult.recordCount){
LOCAL.retVal.recordCount = LOCAL.totResult.totalCount; // total number of records
LOCAL.retVal.pageCount = Ceiling(LOCAL.totResult.TotalCount / ARGUMENTS.pageSize); // total number of pages by pageSize
}
}
} catch (any excpt) {
LOCAL.retVal.success = false;
LOCAL.retVal.message = excpt.message;
}
return LOCAL.retVal;
}
</code>
If you run a quick test harness on this, even without arguments, you should now get a structure back with records (though it does require BlogCFC and some data for the demo). Now that we have our remote method we need to fill out our datatype <em>function</em> in our script. Let's begin with the base ajax call:
<h4>jqGridDemo.js populateGrid Method</h4>
<code>
var populateGrid = function (postdata) {
$.ajax({
url: '/com/cc/Blog/Entries.cfc',
data:postdata,
method:'POST',
dataType:"json",
success: function(d,r,o){
}
});
};
</code>
Here we define the basic request, identifying our endpoint (the Entries.cfc we've been working on), passing the jqGrid <em>postData</em> as our data params, and saying that our return value is expected to be JSON. If you loaded the demo page right now you wouldn't see anything, unless you looked at a JavaScript console. If you were looking at a console, you would see the remote request being made, where you could inspect the post parameters (jqGrid's <em>postData</em>, passed into the populateGrid() method as the <em>postdata</em> argument) and the returned JSON. Here is where we begin to process the data into jqGrid. Let's look at our <em>success</em> function. First, we included a success flag in our return structure. Let's key off of that:
<h4>jqGridDemo.js - populateGrid Ajax 'success' Method</h4>
<code>
success: function(d,r,o){
if(d.success){
// On 'success' do this
} else {
// If not, then do this
}
}
</code>
You'll remember that we created a <b>gridCols</b> object as a global variable. Let's loop our return, on only the first request, and map our column positions:
<h4>jqGridDemo.js - populateGrid Ajax 'success' Method - figure 2</h4>
<code>
success: function(d,r,o){
if(d.success){
if(!gridCols.set){
for(var i in d.data.COLUMNS){
gridCols[d.data.COLUMNS[i]] = parseInt(i);
}
gridCols.set = true;
}
...
} else {
// If not, then do this
}
}
</code>
If you dumped the <em>gridCols</em> object to the console after this, you would find a key for each column with a value of it's column position. All we did was loop the COLUMNS array, and set the key and position. We're going to need this let outside of these requests, but let's go ahead and use this object to remap the return data to our jqGrid columns:
<h4>jqGridDemo.js - populateGrid Ajax 'success' Method - figure 3</h4>
<code>
success: function(d,r,o){
if(d.success){
// If loading for the first time, let's find out to which
// array positions our columns map.
if(!gridCols.set){
for(var i in d.data.COLUMNS){
gridCols[d.data.COLUMNS[i]] = parseInt(i);
}
gridCols.set = true;
}
grid.jqGrid('setGridParam',{remapColumns:[
gridCols['ID'],
gridCols['TITLE'],
gridCols['POSTED'],
gridCols['VIEWS']
]});
grid[0].addJSONData(d);
} else {
// If not, then do this
}
}
</code>
The <em>remapColumns</em> array is just a list of data positions, for which we used our new <em>gridCols</em> object. The <em>addJSONData()</em> method is then used to apply the return data to the grid. Run your demo template now to see the end result.
<div style="position:relative;text-align:center;">
<img src="/images/jqGridDemo-pt2.png" style="width:810px;height:435px;" />
</div>
Voila! Data! You've just filled jqGrid with data from a remote ColdFusion request, and didn't even jump through hoops to do it. This is only the beginning. In our next post we'll talk in depth on the Column Model options, and about creating custom column formatting. You can find demo code in the <b>Download</b> link at the bottom of this post.
jqGridAjaxDevelopmentJQueryColdFusionTue, 20 Dec 2011 15:37:00 -0000https://www.cutterscrossing.com/index.cfm/2011/12/20/Intro-to-jqGrid-Part-2-Getting-the-DataIntro to jqGridhttps://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGrid
While there are better choices (<a href="http://www.sencha.com/products/extjs/" target="_blank">ExtJS</a> for instance) for component and architecture libraries, some time ago someone entrenched our current system in <a href="http://www.jquery.com" target="_new">JQuery</a> and <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a>. 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...When writing "applications", there are several key components missing from JQueryUI (Trees, Menus, Charts, etc), but one of the most important is a robust Data Grid. Luckily, the JQuery Plugin system is large, so there are options. On the other side of the coin, we need something that at least appears to be like it's JQueryUI counterparts (skinned by ThemeRoller), even if it's architecture is inconsistent. In an effort to provide a consistent user experience, and create visual and usability "standards" within our system, we undertook evaluating components that would work with JQueryUI while also meeting our application's standards and needs. For the Data Grid, we finally decided on <a href="http://www.trirand.com/jqgridwiki/doku.php" target="_blank">jqGrid</a>.
jqGrid is a free and open source JQuery plugin for the dynamic representation of tabular data. The primary goals of it's developer are two fold: speed, and independence from server-side technology and database backend. In jqGrid, we gain a component that is capable of integrating with our backend systems, automatically appears like our other interface components, allows for heavy conditional customization of output, and has a robust API for data manipulation, as well as hooks for working with other aspects of JQueryUI (like Drag and Drop and Sorting).
First things first, you need some files. There are a few dependencies, specifically JQuery and JQueryUI. Since every page of our application uses these, they are already included in our page header custom tag. After this, we need to jqGrid files. These are already in our codebase, but they can be built from the <a href="http://www.trirand.com/blog/?page_id=6" target="_blank">jqGrid Download Page</a>. Our files include all of the jqGrid packages, but it is possible to build a file that only includes the pieces you need for your implementation. For instance, in our example we use a custom jqGrid build excluding all of the features of the following subclasses: Editing, Subgrid, Grouping, and TreeGrid. Should you require any of those packages, it is easy to select only the options needed to trim your js files. (One item of note: It looks like the custom build only trims the minified file of the unneeded packages, whereas the 'src' file maintains all of the packages.)
<h4>Script and Style Includes</h4>
<code>
<link type="text/css" rel="stylesheet" href="/resources/scripts/jquery-plugins/UI/css/ui-lightness/jquery-ui-1.8.16.custom.css" /><!-- JQueryUI CSS -->
<link type="text/css" rel="stylesheet" href="/resources/scripts/jquery-plugins/jqgrid-4.3.0-custom/css/ui.jqgrid.css" /><!-- jqGrid CSS -->
<link type="text/css" rel="stylesheet" href="/resources/styles/custom/jqGridDemo.css" /><!-- Custom CSS for our app -->
<script type="text/javascript" src="/resources/scripts/jquery/jquery-1.4.4.js"></script><!-- JQuery -->
<script type="text/javascript" src="/resources/scripts/jquery-plugins/UI/js/jquery-ui-1.8.16.custom.min.js"></script><!-- JQueryUI -->
<script type="text/javascript" src="/resources/scripts/jquery-plugins/jqGrid-4.3.0-custom/js/i18n/grid.locale-en.js"></script><!-- jqGrid Localization File -->
<script type="text/javascript" src="/resources/scripts/jquery-plugins/jqGrid-4.3.0-custom/js/jquery.jqGrid.min.js"></script><!-- jqGrid -->
<script type="text/javascript" src="/resources/scripts/custom/jqGridDemo.js"></script><!-- Custom application script -->
</code>
You can always combine and compress files as needed, but for this we'll show our stylesheets and scripts in their load order. A very important piece here is the jqGrid Localization File. jqGrid includes multiple localization files, and it's important to include the one needed for your application, as this will set the text used in controls of the grid, date formatting, and more. Your grid <b>will not work</b> if you do not include a localization file.
Our next step is to add the few bits of html needed for our grid. jqGrid includes a package to convert an existing <em>Table to Grid</em>, but we'll be pulling all of our data from the server in the form of JSON, so we only need a few minor bits of html to get ready:
<code>
<div id="gridCont1" class="gridCont">
<table id="gridTest" class="gTable"></table>
<div id="pager" class="gTable"></div>
</div>
</code>
What I've included here are basically three key pieces. First, I have an overall container object for our grid, to which I've given a class of <em>gridCont</em>. I'm not going to use this container right away, but I'm including it here for future revision. Next I include the <b>table</b> element that will be the actual grid object, giving it an <b>id</b> of <em>gridTest</em>, so I have a hard reference for my JQuery object selector. Last, our grid will be paged, so I include a <b>div</b> element to be used as the grid's pager bar.
Now that we have our html it's time to start building our custom script. Most of what we need will go inside a document ready statement, but first I want to define a global variable for our script:
<h4>jqGridDemo.js - figure 1</h4>
<code>
var gridCols = {set:false};
</code>
I'm defining this outside the document ready because later I'll need access to it outside the document ready statement. Next I'm going to create a var reference to our grid's primary html object that will be available throughout our document ready statement:
<h4>jqGridDemo.js - figure 2</h4>
<code>
var gridCols = {set:false};
$(document).ready(function(){
var grid = $('#gridTest'); // JQuery object reference of table used for our grid display
...
});
</code>
OK, now we get down to the nitty gritty. It's time to start defining the grid itself. Like most other JQuery plugins, jqGrid requires a configuration object to define it's appearance and general actions. You can find all of the available configuration options on the <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options" target="_blank">Options</a> page of the jqGrid Wiki. Here we'll start with a very basic configuration, which we'll explain in blocks. First, we'll start with some of the basic display options:
<h4>jqGrid Config - basic display options</h4>
<code>
grid.jqGrid({
width: 800,
height: 350,
blockui: true,
altRows: true,
deepemtpy: true,
...
});
</code>
These are just a few basic options for a minimal display. We've included the <em>width</em> and <em>height</em> of our grid, set it to block any user interaction during record load (during ajax calls), asked that we use alternating row colors for even and odd rows, and set it to use JQuery's <em>empty()</em> method when resetting cell contents.
After this, we want to setup some of the options related to our paging bar. You'll remember I said we wanted to use paged datasets. This is very important when working with a large amount of records. By paging our requests, we restrict our returned data to small, managable chuncks, which (hopefully) speeds query processing, reduces individual data transfer, and limits DOM writes for record rendering to a predefined number of records at a time.
<h4>jqGrid Config - pager options</h4>
<code>
grid.jqGrid({
...
pager: '#pager',
toppager: true,
pagerpos: 'left',
rowNum: 50,
rowList: [10,20,30,40,50],
viewrecords: true,
...
});
</code>
Here we've created a pager, and set it to the <b>pager</b> div element we put in the html. Normally the pager is only at the bottom, but by adding the <em>toppager</em> option we've told jqGrid to also replicate the pager at the top of the grid. We've also set the position of the main pager controls to the left, said that we want it to 50 records at a time, and used the option that creates a dropdown where the user can change the number of records returned on requests. The last bit creates the 'View 1 - 50 of x' bit on the pager bar.
Next we look at a few options for defining column display and sorting:
<h4>jqGrid Config - column and sorting options</h4>
<code>
grid.jqGrid({
...
sortname: 'ID',
sortorder: 'desc',
colModel: [
{name: 'ID'},
{name: 'TITLE'},
{name: 'POSTED'},
{name: 'VIEWS'}
],
...
});
</code>
There are only a few pieces here. I've identified which column is sorted first, and in which order. I've also set the absolute minimum column model. Since I'm using ColdFusion on the backend, which returns serialized query results with capitalized column names, I use capitalized names here. We'll override this later when we begin refining our <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options" target="_blank">Column Model</a>.
The last thing we're going to define are those options dealing with data. We're going to populate our demo grid with data from the server, which we'll return in JSON format. jqGrid has support for dealing with a number of different datatypes (json, xml, javascript, etc). Since ColdFusion's JSON format for it's native query object is a bit different than the standard, we're going to use the <em>function</em> datatype. The 'standard' JSON recordset return looks a little like this:
<h4>'Standard' JSON Recordset</h4>
<code>
{
data: [
{
id: "FF318BAB-3048-71C2-17E1634637074ECF",
title: "The ColdFusion 8 AJAX Components Debate",
posted: "June, 05 2007 23:51:00",
views: 7667
},
{
id: "FD48C350-3048-71C2-17244BE88532DEC9",
title: "ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
posted: "June, 08 2007 15:06:00",
views: 0
}
]
}
</code>
ColdFusion's JSON return, of it's native query object, is quite a bit slimmer by not repeating the column names with each record and having each record as a simple array rather than an object. We also need to add in some keys for working with paged recordsets. Our server side process will generate a JSON object similar to this:
<h4>Server Side JSON Return</h4>
<code>
{
"totalCount": 4.0,
"message": "",
"success": true,
"recordCount": 169,
"pageIndex": 1,
"data": {
"COLUMNS": [
"ID",
"TITLE",
"POSTED",
"VIEWS"
],
"DATA": [
[
"FF318BAB-3048-71C2-17E1634637074ECF",
"The ColdFusion 8 AJAX Components Debate",
"June, 05 2007 23:51:00",
7667
],
[
"FD48C350-3048-71C2-17244BE88532DEC9",
"ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
"June, 08 2007 15:06:00",
0
],
...
]
}
}
</code>
You can see the difference. Because of this difference we can not use jqGrid's standard <em>json</em> or <em>jsonstring</em> datatypes, so we'll have to write a <em>function</em> to parse the return data. For now, we'll just put a placer in our file:
<h4>jqGridDemo.js - in our document ready</h4>
<code>
/*
* FUNCTION populateGrid
* Used as the 'datatype' attribute of the jqGrid config object, this method
* is used to handle the ajax calls and data manipulation needed to populate
* data within our jqGrid instance.
* @postdata (object) - this is the object passed as the 'postData' attribute
* of our jqGrid instance.
*/
var populateGrid = function (postdata) {
// request and parsing code will go here
};
</code>
Then we start putting together our config options:
<h4>jqGrid Config - remote data options</h4>
<code>
grid.jqGrid({
...
prmNames: {page: "pageIndex", sort: "sortCol", order: "sortDir", rows: "pageSize"},
postData: {method: "getEntries", returnFormat: "JSON"},
datatype: populateGrid,
jsonReader: {
id: "ID",
root: function(obj){return obj.data.DATA;},
page: "pageIndex",
total: function(obj){return parseInt(obj.totalCount);},
records: function(obj){return parseInt(obj.recordCount);},
cell:""
}
});
</code>
There are a few things going on here, so we'll cover them one by one. First, we're changing the names of the params that the grid will send to the server on each request. This isn't necessary (you can always write your methods to use the default jqGrid field names) but I wanted certain param names for certain things. The <em>postData</em> option is for additional data to send with each request. We use this option to tell ColdFusion what method to call for our data, and that we want our data automatically serialized into a JSON object. For out <em>datatype</em> we are using the <b>populateGrid</b> function we previously defined. When using a <em>function</em> datatype, the <em>postData</em> is automatically sent to any function you define.
The last option here is a configuration object for the <em>jsonReader</em>. The data we will retrieve from the server is still JSON, and the jsonReader is how we define how jqGrid interprets the data returned from the server. The <em>id</em> and <em>page</em> options reference specific key names within our JSON, with the <em>id</em> referencing a specific <em>index</em> in the Column Model, and the <em>page</em> referencing a key in the base JSON. The <em>root</em>, <em>total</em> and <em>records</em> options are set (for us) to use functions that return the object location of their specific bits. Each function takes the JSON as it's only argument, with a return of the specific key containing that bit of data. Notice, in those options that must return a numeric value, that we are using the <em>parseInt()</em> JavaScript method. Different dot revisions of the ColdFusion server will treat numeric values differently, and we are doing this to automatically convert, in the event that a string representation of the number was returned. The <em>cell</em> option needs to be present, but empty. This is something that will ultimately be defined by the function we're using for the <em>datatype</em> option. All of this is layed out in the jqGrid documentation of <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data" target="_blank">working with JSON Data</a>.
Loading our page right now will give us a basic grid.
<div style="text-align:center;">
<img src="https://www.cutterscrossing.com/images/jqGridDemo-pt1.png" style="width:816px;height:442px;" />
</div>
You'll notice that we have a grid, but no data. No, we haven't written our data model for our grid yet. In our next post on this, we'll write our initial data model, and fill out our <em>datatype</em> function. You can find sample code in the <b>Download</b> link at the bottom of this post.
jqGridAjaxDevelopmentJQueryColdFusionMon, 19 Dec 2011 17:14:00 -0000https://www.cutterscrossing.com/index.cfm/2011/12/19/Intro-to-jqGridHow I Do Things: ColdFusion and Ajax Requestshttps://www.cutterscrossing.com/index.cfm/2011/9/26/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 <a href="http://www.adobe.com/products/coldfusion/" target="_blank">ColdFusion</a>? Those sort of questions. We (the ColdFusion community) have embraced <a href="http://en.wikipedia.org/wiki/Ajax_%28programming%29" target="_blank">Ajax</a>. 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 (<a href="http://en.wikipedia.org/wiki/Wddx" target="_blank">WDDX</a> 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 <em>access</em> attribute to <b>remote</b>.
<code>
/*
* COMPONENT SomeComponent
* Just some example component
*
* @name SomeComponent
* @displayName SomeComponent
* @output false
*/
component {
/*
* FUNCTION SomeFunction
* This is just some function you've written
*
* @access remote
* @returnType struct
* @output false
*/
function SomeFunction(required string arg1, required numeric arg2) {
var retVal = {"success" = true, "message" = "", "data" = ""};
// some function stuff here
return retVal
}
}
</code>
That's it. Nothing to it. Did you see the required line? The one you need for making a remote request of this function?
<code>
* @access remote
</code>
Ok, we're off and running. Sort of. "Hey, Cutter, what's with returning a <em>struct</em>? 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 <a href="http://getfirebug.com/" target="_blank">Firebug</a>, 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 <a href="http://www.peanuts.com/comics/" target="_blank">Charlie Brown</a>: 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 <a href="http://www.coldfusionjedi.com/index.cfm/2007/12/5/The-Complete-Guide-to-Adding-Error-Handling-to-Your-ColdFusion-Application" target="_blank">error handling</a> 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.
<code>
/*
* FUNCTION SomeFunction
* This is just some function you've written
*
* @access remote
* @returnType struct
* @output false
*/
function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
var retVal = {"success" = true, "message" = "", "data" = ""};
var q = new Query();
try {
// do your query stuff, setting the result to the "data" key
if (!retVal["data"].recordCount) {
throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
}
} catch (any excpt) {
LogTheError(excpt); // Custom error logging for us
retVal["success"] = false;
if (!ARGUMENTS.debug) {
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.
if (excpt.type eq "ourCustom") {
retVal["message"] = excpt.message;
retVal["errorCode"] = excpt.errorCode;
} else {
retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
}
} else {
retVal["message"] = excpt.message;
// and anything else you might want
}
}
return retVal;
}
</code>
Now, there are probably better ways to write your <em>catch</em> 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. "<a href="http://www.codeproject.com/KB/architecture/ExceptionErrorCode.aspx" target="_blank">ErrorCode</a>?" 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 <b>success</b> key, denoting whether the requested ColdFusion function did what we wanted. We have a <b>data</b> key that we use for returning the data generated by the request. And, we have a <b>message</b> key that we may, or may not, use only in the event of an error. We've also used and <b>errorCode</b> in this scenario, only in the event of an error. "Cutter, something doesn't look right?"
<code>
// You use the quotes and this struct access notation to preserve the casing of key names, which is important in JavaScript
var retVal = {"success" = true, "message" = "", "data" = ""};
...
retVal["message"] = excpt.message;
</code>
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.
<code>
/*
* COMPONENT SomeComponent
* Just some example component
* This example sits outside the webroot, in a 'com' directory that's mapped in the CFIDE
*
* @name SomeComponent
* @displayName SomeComponent
* @output false
*/
component {
/*
* FUNCTION SomeFunction
* This is just some function you've written
*
* @access public
* @returnType struct
* @output false
*/
function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
var retVal = {"success" = true, "message" = "", "data" = ""};
var q = new Query();
try {
// do your query stuff, setting the result to the "data" key
if (!retVal["data"].recordCount) {
throw(type = "ourCustom", errorCode = "RC_000", message = "No records were returned matching your criteria.");
}
} catch (any excpt) {
LogTheError(excpt); // Custom error logging for us
retVal["success"] = false;
if (!ARGUMENTS.debug) {
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.
if (excpt.type eq "ourCustom") {
retVal["message"] = excpt.message;
retVal["errorCode"] = excpt.errorCode;
} else {
retVal["message"] = "There was an error in making your request, and our administrators have been notified.";
retVal["errorCode"] = "UN_001"; // Internal error code for an undefined error
}
} else {
retVal["message"] = excpt.message;
// and anything else you might want
}
}
return retVal;
}
}
</code>
And the remoting proxy...
<code>
/*
* COMPONENT SomeComponent_Remote
* Just some example proxy component
*
* @name SomeComponent_Remote
* @displayName SomeComponent_Remote
* @output false
* @extends "com.cc.Examples.SomeComponent"
*/
component {
/*
* FUNCTION SomeFunction
* This is just some function you've written
*
* @access remote
* @returnType struct
* @output false
*/
function SomeFunction (boolean debug = false, required string arg1, required numeric arg2) {
return Super.SomeFunction(argumentCollection = ARGUMENTS);
}
}
</code>
"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 <a href="http://www.jquery.com" target="_blank">JQuery</a> Ajax call. First, let's set a global JS variable in the page.
<code>
<cfscript>
param type = "boolean" name = "URL.debug" default = false;
savecontent variable = "REQUEST.adder" {
WriteOutput('<script type="text/javascript">var debug = #URL.debug#;</script>');
}
REQUEST.addHeaderOutput &= REQUEST.adder;
</cfscript>
...
<cfhtmlhead text="#REQUEST.addHeaderOutput#" />
</code>
Next we'll setup our Ajax call.
<code>
$.ajax({
url: '/my/cfc/location/SomeComponentRemote.cfc'
dataType: 'json',
data: {
method: 'SomeFunction', // The method we're calling
returnFormat: 'JSON', // Give us the return as JSON
debug: debug, // the global default 'debug' set earlier by ColdFusion
arg1: 'This is my argument',
arg2: 42
},
success: function (response, status, options) {
if (response.data) {
// Do something with the data
} else {
// This means the request failed, and response.data should contain
// a 'message' key, to present to the user, and an 'errorCode' key
// that you're application might act on
}
}
});
</code>
Here's the same basic Ajax request, using Sencha's <a href="http://www.sencha.com/products/extjs/" target="_blank">Ext JS</a>.
<code>
Ext.Ajax.request({
url: '/my/cfc/location/SomeComponentRemote.cfc',
params: {
method: 'SomeFunction', // The method we're calling
returnFormat: 'JSON', // Give us the return as JSON
debug: debug, // the global default 'debug' set earlier by ColdFusion
arg1: 'This is my argument',
arg2: 42
},
success: function (response, options) {
var response = Ext.util.JSON.decode(response.responseText)
if (response.data) {
// Do something with the data
} else {
// This means the request failed, and response.data should contain
// a 'message' key, to present to the user, and an 'errorCode' key
// that you're application might act on
}
}
});
</code>
I'm always surprised at how few CFers know <a href="http://www.coldfusionjedi.com/index.cfm/2007/7/5/ColdFusion-8--Return-Format-for-ColdFusion-Components" target="_blank">this little bit</a>. In case you missed it, the key piece to your request parameters comes down to one important argument.
<code>
returnType: 'JSON'
</code>
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 <em>returnType</em> 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?
AjaxDevelopmentJQueryColdFusionExtJSMon, 26 Sep 2011 07:54:00 -0000https://www.cutterscrossing.com/index.cfm/2011/9/26/How-I-Do-Things-ColdFusion-and-Ajax-RequestsNew Job, New Home, A Lot of Workhttps://www.cutterscrossing.com/index.cfm/2011/8/13/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 <a href="http://www.goodreads.com/cutterbl" target="_blank">catch up on my reading</a>). 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 <a href="/index.cfm/2011/2/7/Using-The-DataTables-JQuery-Plugin">already started</a> with some exposure to the <a href="http://www.datatables.net/" target="_blank">DataTables</a> JQuery plugin, but I'm also lining up posts for <a href="http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs" target="_blank">jqGrid</a>, <a href="http://www.jstree.com/" target="_blank">jsTree</a>, and the <a href="http://cfuniform.riaforge.org/" target="_blank">cfUniForm</a> project. <a href="http://www.evernote.com" target="_blank">Evernote</a> 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 <a href="http://www.sencha.com/products/extjs/" target="_blank">Ext JS</a> day-to-day. <a href="http://jqueryui.com/" target="_blank">JQuery UI</a> is a good project, but lacks the maturity of Ext JS, and is missing too many key components for writing web <em>applications</em> (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, <a href="http://www.packtpub.com" target="_blank">Packt Publishing</a> 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 <a href="https://www.packtpub.com/learning-ext-js-3-2-for-building-dynamic-desktop-style-user-interfaces/book" target="_blank">last book</a>), 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. <a href="http://www.sencha.com" target="_blank">Sencha</a> has already announced <a href="http://www.sencha.com/blog/what-to-expect-in-ext-js-4-1/" target="_blank">What to Expect in Ext JS 4.1</a>, and recently put <a href="http://www.sencha.com/blog/ext-designer-1-2-beta/" target="_blank">Ext Designer 1.2 in Beta</a>, so there's a lot to talk about here.
Last, but definitely not least, I'm following all the buzz about the upcoming <a href="http://blogs.adobe.com/coldfusion/2011/06/08/next-version-of-coldfusion-is-codenamed-zeus/" target="_blank">ColdFusion "Zeus"</a>. A quick <a href="http://www.google.com/search?q=coldfusion+zeus" target="_blank">Google Search</a> already brings up a ton of info that <a href="http://www.adobe.com" target="_blank">Adobe</a> 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).
Learning ExtJSSenchaDevelopmentJQueryColdFusionThis BlogAdobeWho I AmSat, 13 Aug 2011 12:54:00 -0000https://www.cutterscrossing.com/index.cfm/2011/8/13/New-Job-New-Home-A-Lot-of-WorkUsing The DataTables JQuery Pluginhttps://www.cutterscrossing.com/index.cfm/2011/2/7/Using-The-DataTables-JQuery-Plugin
For adminstrative applications, most of my readers know I'm a huge proponent of the <a href="http://www.sencha.com/products/extjs" target="_blank">Ext JS</a> library. But for front-end, consumer facing sites, I'm often pushed to use <a href="http://www.jquery.com" target="_blank">JQuery</a>. 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 <a href="http://www.jqueryui.com" target="_blank">JQueryUI</a>. Unfortunately, JQueryUI doesn't yet have a grid component (though <a href="http://blog.jqueryui.com/2011/02/unleash-the-grid/" target="_blank">they are working on it</a>). 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 <a href="http://jqueryui.com/themeroller/" target="_blank">ThemeRoller</a>. That's when I came upon the <a href="http://www.datatables.net" target="_blank">DataTables</a> 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:
<ul>
<li>iDisplayStart - The number of the first record in the requested set.</li>
<li>iDisplayLength - The number of records to return in the requested set.</li>
<li>sEcho - A DataTables request identifier, to be echoed back with the return.</li>
<li>aoSort - If present, this will be a JSON string representation of sort columns and orders. It's an array of objects:
<ul>
<li>colName - the column name</li>
<li>sortDir - the sort direction (asc|desc)</li>
</ul></li>
</ul>
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). <a href="http://www.petefreitag.com/" target="_blank">Pete Frietag</a> 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:
<ul>
<li>success - A boolean to denote success or failure of the request.</li>
<li>message - Only returned if the request fails, a message to state why the failure occurred.</li>
<li>totalCount - The total number of records available for the filters applied.</li>
<li>result - The paging query.</li>
</ul>
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 <a href="http://www.sprymedia.co.uk/" target="_blank">Allan Jardine</a>, 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 <a href="http://examples.cutterscrossing.com/DataTables" target="_blank">example</a>, 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.
DataTablesDevelopmentJQueryColdFusionMon, 07 Feb 2011 10:24:00 -0000https://www.cutterscrossing.com/index.cfm/2011/2/7/Using-The-DataTables-JQuery-PluginNew Custom Tag for Google Maps: CFGMaphttps://www.cutterscrossing.com/index.cfm/2011/1/18/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 <a href="http://www.scottmebberson.com" target="_blank">Scott Mebberson's</a> implementation, the <a href="http://www.googlemapstag.riaforge.org" target="_blank">Google Maps Tag</a>. 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 <a href="http://www.adobe.com/products/coldfusion" target="_blank">ColdFusion 9's</a> 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 <a href="" target="_blank">Google Maps Javascript API</a> is very nice, and has one very significant change.
<quote>
The JavaScript Maps API V3 is a free service, available for any web site that is free to consumers.
</quote>
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 <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">license</a> the same, which allows me to put it back out to the community at large (and with the approval of my old boss).
<a href="http://cfgmap.riaforge.org" target="_blank">CFGMap</a> is now available on <a href="http://www.riaforge.org" target="_blank">RIAForge</a> for download. My <a href="http://examples.cutterscrossing.com/cfgmap" target="_blank">simple example</a> source code is included with the download, and all code is heavily documented. My example uses <a href="http://www.jquery.com" target="_blank">JQuery</a> 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 <a href="http://examples.cutterscrossing.com/CFGMap/scripts/testmap.js" target="_blank">testmap.js</a> 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.
DevelopmentJQueryColdFusionCFGMapTue, 18 Jan 2011 19:40:00 -0000https://www.cutterscrossing.com/index.cfm/2011/1/18/New-Custom-Tag-for-Google-Maps-CFGMapDevelopment Tieshttps://www.cutterscrossing.com/index.cfm/2009/2/19/Development-Ties
On the last day of CFUnited 2005, a group of us were out on the patio having a final drink together. I got into a conversation with <a href="http://www.clarkvalberg.com" target="_blank">Clark Valberg</a> about linguistics. I was a translator in latter half of my time in the military, and Clark was asking if I thought my experience with learning another language had helped me in learning to be a better developer?
I absolutely agreed. I have an aptitude for languages, and always have. It's something I've picked up, and I can generally get to a point where I can effectively communicate (at least on the simplest of terms) within a very short time. Programming isn't much different, if you think about it. When I first got into computing again, after leaving the Army, I was teaching myself ten different programming languages at the same time. I had a lot of catching up to do, being out of the game for so long, so I picked up some books, found online resources, and took to the task of getting up to speed.
Maybe that's why there are so many talented developers outside the US. In the US, we aren't required to learn another language out of necessity, whereas in most other countries of the world (not all, but most) it is very commonplace for people to speak two or more languages.
You can kind of apply this in the reverse, to some degree, as well. Those who only learn one development platform may be limiting themselves. Knowing one programming language inside and out can be a good thing, but learning others can also open a developer to new ways of approaching a challenge. I've known many developers who knew a server-side language (ColdFusion, ASP, PHP, whatever), but never bothered to learn JavaScript, or how to write well formed XHTML. To me, that's limiting. Even crippling.
What are your thoughts?
ColdFusion 8DevelopmentJQueryColdFusionMy 2 centsWho I AmExtJSThu, 19 Feb 2009 10:53:00 -0000https://www.cutterscrossing.com/index.cfm/2009/2/19/Development-Ties