ColdFusion 8 Grid Custom Toolbars

Word Count: 231

Many people have asked how they can add custom toolbars to their grid components. In Ext 2.0 they are built in to the grid panel but in 1.1 they are built in to the grid view. It is the same concept but just tackled a little different. As we all know ColdFusion 8 ships with 1.1 so we will focus on that version. So why the need for custom toolbars? Well our example makes a case for both a header and footer toolbar. We will use the header to provide some nice buttons for adding and deleting records and our footer is a nice place for a status bar.

If you have been following my grid examples lately some of this will look familiar. I will be using the cfartgallery data source that ships with ColdFusion 8. The first thing we need to do is write a query to grab some data from our artists table. Next we setup some grid attributes and pass them in using attribute collection. Finally we are going to call a method called init when the page loads using the ajaxOnLoad tag.

<cfquery name="getArtists" datasource="cfartgallery">
SELECT artistId, firstname, lastname, address, city, state, postalcode, email
FROM Artists
</cfquery>

<cfset args = structNew()>
<cfset args.name = "ArtistGrid">
<cfset args.format = "html">
<cfset args.query = "getArtists">
<cfset args.stripeRows = true>
<cfset args.selectColor = "##D9E8FB">

<cfform>
   <cfgrid attributeCollection="#args#">
      <cfgridcolumn name="artistid" display="false">
      <cfgridcolumn name="firstname" header="First Name">
      <cfgridcolumn name="lastname" header="Last Name">
      <cfgridcolumn name="email" header="Email Address">
      <cfgridcolumn name="address" header="Address">
      <cfgridcolumn name="city" header="City">
      <cfgridcolumn name="state" header="State">
      <cfgridcolumn name="postalcode" header="Zip">
   </cfgrid>
</cfform>

<cfset ajaxOnLoad("init")>

In our init method we first need to get the grid object and can do so by using the ColdFusion.Grid.getGridObject method. This method is looking for the name of your grid. Remember that this is case sensitive so make sure you provide exact name that you used as the grids name. Now that we have the grid we can call the getView() method which return the grids view. If you look in the documentation the GridView Class has 2 methods we are interested in; getHeaderPanel() & getFooterPanel. These methods will basically get you a panel in the header or footer of the grid that can be used for toolbars etc.

<script type="text/javascript">
function init(){
      grid = ColdFusion.Grid.getGridObject("ArtistGrid");
      var gridFoot = grid.getView().getFooterPanel(true);
      var gridHead = grid.getView().getHeaderPanel(true);
}
</script>
Now that we have our our header and footer panels we need to create some toolbars to add to them. To do this we need to import the classes used by Ext to create toolbars.
<script type="text/javascript" src="/CFIDE/scripts/ajax/ext/package/toolbar/toolbar.js"></script>
Now we are ready to make some toolbars. If you not sure how toolbars work or the properties / methods available to them I suggest you check out the documentation.
var bbar = new Ext.Toolbar(gridFoot);
var tbar = new Ext.Toolbar(gridHead);
Now I can start adding items to my toolbar. In the footer toolbar I am going to add a text item that will display the number of records used by the grids datasource. In the header I am going to add a couple of buttons that mix text and icons. You will notice for that each button their is a handler attribute. This is the method that will be called when the buttons click event is activated.
bbar.add(new Ext.Toolbar.TextItem('Total Artists: ' + grid.getDataSource().totalLength));
      
      tbar.addButton({
       text:"Add New Artist",
       cls:"x-btn-text-icon",
       icon:"add.png",
       handler:onAdd
      });
      tbar.addSeparator()
      tbar.addButton({
       text:"Delete Artist",
       cls:"x-btn-text-icon",
       icon:"delete.png",
       handler:onDelete
      });
Now we can add our button methods. These methods do not do much right now but you see how easy it would be to add some real functionality.
function onAdd(button,event){
alert("Row Added");
}
function onDelete(){
var grid = ColdFusion.Grid.getGridObject("ArtistGrid");
var record = grid.getSelections();
alert("Artist " + record[0].data.FIRSTNAME + " " + record[0].data.LASTNAME + " deleted");
}

That is really all there is to it. Customizing the AJAX Components that ship with ColdFusion are easy. All you need to do is dig into the Ext documentation and find out what is available to you. As always please leave me any questions / comments.

Example Source Code
<html>
<head>
<title>Custom Toolbars Example</title>

<script type="text/javascript" src="/CFIDE/scripts/ajax/ext/package/toolbar/toolbar.js"></script>

<script type="text/javascript">
function init(){
      grid = ColdFusion.Grid.getGridObject("ArtistGrid");
      var gridFoot = grid.getView().getFooterPanel(true);
      var gridHead = grid.getView().getHeaderPanel(true);
      
      var bbar = new Ext.Toolbar(gridFoot);
      var tbar = new Ext.Toolbar(gridHead);
      
      bbar.add(new Ext.Toolbar.TextItem('Total Artists: ' + grid.getDataSource().totalLength));
      
      tbar.addButton({
       text:"Add New Artist",
       cls:"x-btn-text-icon",
       icon:"add.png",
       handler:onAdd
      });
      tbar.addSeparator()
      tbar.addButton({
       text:"Delete Artist",
       cls:"x-btn-text-icon",
       icon:"delete.png",
       handler:onDelete
      });
      
      console.log(tbar);
      console.log(bbar);

}

function onAdd(button,event){
alert("Row Added");

console.log(button);
console.log(event);
}
function onDelete(){
var grid = ColdFusion.Grid.getGridObject("ArtistGrid");
var record = grid.getSelections();
alert("Artist " + record[0].data.FIRSTNAME + " " + record[0].data.LASTNAME + " deleted");
}
</script>
</head>
<body>

<cfquery name="getArtists" datasource="cfartgallery">
SELECT artistId, firstname, lastname, address, city, state, postalcode, email
FROM Artists
</cfquery>

<cfset args = structNew()>
<cfset args.name = "ArtistGrid">
<cfset args.format = "html">
<cfset args.query = "getArtists">
<cfset args.stripeRows = true>
<cfset args.selectColor = "##D9E8FB">

<cfform>
   <cfgrid attributeCollection="#args#">
      <cfgridcolumn name="artistid" display="false">
      <cfgridcolumn name="firstname" header="First Name">
      <cfgridcolumn name="lastname" header="Last Name">
      <cfgridcolumn name="email" header="Email Address">
      <cfgridcolumn name="address" header="Address">
      <cfgridcolumn name="city" header="City">
      <cfgridcolumn name="state" header="State">
      <cfgridcolumn name="postalcode" header="Zip">
   </cfgrid>
</cfform>

<cfset ajaxOnLoad("init")>



<cfsavecontent variable="head">
<link href="/CFIDE/scripts/ajax/ext/resources/css/ytheme-aero.css" rel="stylesheet" type="text/css">
<link href="/CFIDE/scripts/ajax/ext/resources/css/menu.css" type="text/css" rel="stylesheet"/>
</cfsavecontent>

<cfhtmlhead text="#head#">

</body>
</html>

Comments

#1 Posted By: Mike Posted On: 3/5/08 3:31 PM
Hi Dan,
The example works great except that the images don't show. Where do the .png files need to be located? I do not have add.png or delete.png anyhwere in my /CFIDE. I have .gif versions, but I cannot get those to display either.
Thanks!
Mike
#2 Posted By: Dan Vega Posted On: 3/5/08 3:34 PM |
Author Comment
The images in my example where just in my local project folder. Place any image in the same directory as the template or adjust the icon attribute of the button accordingly.
#3 Posted By: Mike Posted On: 3/5/08 4:57 PM
Well, now that you point that out it seems like I should have thought of that myself :>) Since I'm on, I'll throw one more at you...is it possible to turn the column headers into select boxes that can be used for filtering the table data? For example, the State column header could be a drop box that shows the states and filters when selected.
#4 Posted By: Dan Vega Posted On: 3/5/08 5:00 PM |
Author Comment
I could be wrong but I would have to say no. The columns are used for sorting and to me filtering should not be confused with sorting. You could however probably add a combo box to the header panel or right above the grid, and filter the data that way.
#5 Posted By: Trond Ulseth Posted On: 3/6/08 10:00 AM
Do you know if a similar toolbar can be added to the bottom of a cfwindow?
#6 Posted By: Mosey Posted On: 4/8/08 12:33 PM
This may be a dumb question but, how do you get the JS handler to then call a CFC function?

That is add button is nice for the alert but I want it to actually call a cfc function that deletes.
#7 Posted By: Rachael Posted On: 4/12/08 12:13 AM
Dan,
if i put all those codes in the wwwroot under some folder, it worked fine.
when i put all those codes in wwwroot under Project -> Apps -> Monitor. it gets weird. ajaxOnLoad doesnt seem to be working. I get no buttons..
I'm stumped on this thing for whole day.
#8 Posted By: Glyn Posted On: 4/16/08 12:38 PM
thanks for the information. i have the same question as Rachael above. i already have an on bind event to delete records so how do i use your JS handler to call the CFC function?

thanks
#9 Posted By: Dan Vega Posted On: 4/17/08 9:23 AM |
Author Comment
@Rachael - (Sorry for the delay) All of the code above relies on a /CFIDE mapping that is created when ColdFusion is installed. Does the init method run? Are you getting Firebug errors? What exactly is your problem?

@Glyn - I am not sure I understand what you are trying to do. The buttons are creating and they are each given a click handler. This means call this method when I am cliked. Once the action is transfered to that method it is up to you supply the implementation.
#10 Posted By: Glyn Posted On: 4/17/08 10:03 AM
i just wanted to know if possible, how i call the cfc function that deletes my records example run this code the delete is clicked...

<cffunction name="editProductOptions" access="remote">
<cfargument name="gridaction" type="string" required="yes">
<cfargument name="gridrow" type="struct" required="yes">
<cfargument name="gridchanged" type="struct" required="yes">
<!--- Local variables --->
<cfset var colname="">
<cfset var value="">
<!--- Process gridaction --->
<cfswitch expression="#ARGUMENTS.gridaction#">
<!--- Process updates --->
<cfcase value="U">
<!--- Get column name and value --->
<cfset colname=StructKeyList(ARGUMENTS.gridchanged)>
<cfset value=ARGUMENTS.gridchanged[colname]>

</cfcase>
<!--- Process deletes --->
<cfcase value="D">
<!--- Perform actual delete --->
<cfquery datasource="#dbSource#" username="#dbUsername#" password="#dbPassword#">
DELETE FROM product
WHERE productOptionId = #ARGUMENTS.gridrow.productOptionId#
</cfquery>
</cfcase>
</cfswitch>
</cffunction>
#11 Posted By: Dan Vega Posted On: 4/17/08 10:07 AM |
Author Comment
Are you trying to add a custom delete button? You know you can add a delete button to a grid by using delete="true"?
#12 Posted By: Glyn Posted On: 4/17/08 10:12 AM
i cannot get the onDelete() function to run the above code after is fired. the function runs and i get the delete message popup but nothing fires before or after. i think this is what Mosey was asking also not Rachael sorry i typed the wrong name.

<cfgrid name="AllOptionForProduct"
format="html"
pagesize="15"
striperows="yes"
selectmode="edit"
delete="yes"
deletebutton="Delete this Option"
width="auto"
height="495"

bind="cfc:salesmaxx3.components.grids.getProdOption({cfgridpage},
{cfgridpagesize},
{cfgridsortcolumn},
{cfgridsortdirection}, #url.prodid#)"
onchange="cfc:salesmaxx3.components.grids.editProductOptions({cfgridaction},
{cfgridrow},
{cfgridchanged})">
<cfgridcolumn name="productOptionId" header="value" display="no" />
<cfgridcolumn name="value" header="value" width="200" />
<cfgridcolumn name="optionprice" header="Price" width="200" />
</cfgrid>
#13 Posted By: Glyn Posted On: 4/17/08 10:15 AM
i can get it to work Dan with the delete="true" in the tag but i dont have the control on layout that your example gives me. so i would like to use your example but fire my bind event and the popup. does that make sense?
#14 Posted By: Cliff Posted On: 4/18/08 10:17 AM
Dan,
I can't seem to get the buttons to add to the header bar. I'm using HostMySite.com and they are notorious for access restrictions so that might be the case. Here is my code:

<script type="text/javascript" src="/CFIDE/scripts/ajax/ext/package/toolbar/toolbar.js"></script>
<script type="text/javascript">
function init(){
   grid = ColdFusion.Grid.getGridObject("grdSubCategory");

   var gridHead = grid.getView().getHeaderPanel(true);
   
   var tbar = new Ext.Toolbar(gridHead);   
   
   tbar.add(new Ext.Toolbar.TextItem('Total Records: ' + grid.getDataSource().totalLength));
   tbar.addSeparator();
   tbar.addButton({text:"Add New", handler:onAdd });
   tbar.addSeparator();
   tbar.addButton({ text:"Delete", handler:onDelete });
}

</script>

Here is a link to the image of what this produces:
http://www.lmelectronics.com/images/gridResults.jp...
#15 Posted By: Dan Vega Posted On: 4/18/08 10:19 AM |
Author Comment
If you run the code locally does it work ok? If not are you seeing any errors in FireBug?
#16 Posted By: Cliff Posted On: 4/18/08 11:04 AM
Well, if you don't add the onAdd() function or onDelete() functions (Even if there is nothing in them) it won't put the details on the top bar. -- thank you Firebug console :)

I added the following:
------------------
function onAdd(){}
function onDelete(){
   var grid = ColdFusion.Grid.getGridObject("grdSubCategory");
   var record = grid.getSelections();
   alert("Record " + record[0].data.SC_NAME + " deleted. Total Length:" +grid.getDataSource().totalLength);
}
------------------
I'll add something in them in a bit but actually adding them in the <script> was the problem there.

This part of the code still returns "undefined" in the top bar though:
tbar.add(new Ext.Toolbar.TextItem('Total Records: ' + grid.getDataSource().totalLength));

Using the onDelete button I added an alert to tell me the value of:
grid.getDataSource().totalLength

It comes back just fine. Why would this not work in the top bar and work just fine in an alert box?
#17 Posted By: Dan Vega Posted On: 4/18/08 11:11 AM |
Author Comment
When you define a handler for a button you must create the method, if not you will have problems.

I am not sure about the other problem. It works fine for me in the bottom bar. I would start logging some of that info to firebug to see what I come up with.
#18 Posted By: Cliff Posted On: 4/18/08 11:37 AM
Ok - I added the following in the init()

console.log(grid.getDataSource());
console.log("total length: " + grid.getDataSource().totalLength);

When you drill down in the first entry there is a totalLength and it is listed as 1.
The second entry is listed as "undefined"

When I press the delete button I have it log the following:
console.log("total length: " + grid.getDataSource().totalLength);

It shows up as 1.

This is very strange, what am I missing?
#19 Posted By: Dan Vega Posted On: 4/18/08 2:14 PM |
Author Comment
Something else is going on in your code. The following line adds the total to the bottom toolbar.

bbar.add(new Ext.Toolbar.TextItem('Total Artists: ' + grid.getDataSource().totalLength));

If you change it to tbar it works fine.
#20 Posted By: Matt Posted On: 6/2/08 12:08 PM
I really like the look of the custom toolbars (much clean than just HTML buttons), but the problem I'm having is that the Javascript functions that the buttons call are firing when the page loads. This means my edit window pops up even though I have CFWINDOW initshow=false.

Unlike your example above, my query is in a CFC and I'm using the bind parameter for CFGRID, and bindonload defaults to yes. I think that when the page loads, the grid bind loads and anything called in InitGrid fires. Is there any way around this?

Thanks!
#21 Posted By: Dan Vega Posted On: 6/5/08 10:46 AM |
Author Comment
Matt,
So are you saying that you have created a cfwindow using tag syntax? Have you considered creating the window using JavaScript? I would have to see more code to understand your problem but I think I understand you right.
#22 Posted By: Jason Posted On: 6/10/08 5:06 PM
I am getting an IE error which states that 'console' is undefined. The only part which does not work is getting the number of records returned for the bottom bar. Any suggestions?
#23 Posted By: Dan Vega Posted On: 6/11/08 8:36 PM |
Author Comment
Jason
console.log() is a way to trace information to Firebug. If you are running in IE either comment out remove any console.log() lines.
#24 Posted By: Glyn Posted On: 6/20/08 6:05 AM
playing around for hours trying to workout why the following would not work:
record[0].data.firstname

the answer was it HAS to be in uppercase i.e.
record[0].data.FIRSTNAME

is there a reason for this i don't understand?
#25 Posted By: Michael Skinner Posted On: 7/10/08 12:08 PM
I have found that id I put an alert first the undified goes away.

   {
    grid = ColdFusion.Grid.getGridObject("pod3");
var gridHead = grid.getView().getHeaderPanel(true);
var tbar = new Ext.Toolbar(gridHead);
alert('');
tbar.add(new Ext.Toolbar.TextItem('Total Records: ' + grid.getDataSource().totalLength));
tbar.addButton({text: 'Add',icon:'/oxford_portal/images/table_gear.png',cls: 'x-btn-text-icon', tooltip: 'Add a new user', handler: CreateWin_pod3} );
tbar.addButton({text: 'Delete',icon:'/oxford_portal/images/table_gear.png',cls: 'x-btn-text-icon', tooltip: 'Delete this user', handler: onDelete_pod3} );

   }

Not sure what is goung on there
#26 Posted By: Calvert Acklin Posted On: 8/18/08 6:10 PM
Thanks for this example, it really help jump start my development team. One quick question, that I could find in the the above comments or after searching the Ext 1.1.1 forum and viewing the API:

Have you or anyone else figured out how to disable/enable a toolbar button? The API has a setDisabled() and disable() function, but they don't seem to work.

For example:

function onAdd(button,event){
alert("Row Added");

//false = enabled; true = disabled
Ext.getCmp("btnDelete").setDisabled(false); //Doesn't work
//Ext.getCmp("btnDelete").disable(); //Doesn't work

console.log(button);
console.log(event);
}
#27 Posted By: Calvert Acklin Posted On: 8/19/08 12:39 AM
Answer my own question:

addArtistButton = bbar.addButton({text:"Add Artist", handler:onAdd });
bbar.addSeparator();
deleteArtistButton = bbar.addButton({ text:"Delete Artist", handler:onDelete });
      bbar.addSeparator();
disableDeleteArtistButton = bbar.addButton({ text:"Disable Delete", handler:onDisable });
      bbar.addSeparator();
enableDeleteArtistButton = bbar.addButton({ text:"Enable Delete", handler:onEnable });
      
      function onDisable(){
          deleteArtistButton.setDisabled(true);
      }
      
      function onEnable(){
          deleteArtistButton.setDisabled(false);
      }

function onAdd(button,event){
ColdFusion.Window.show('addArtistWin');
}
function onDelete(){
ColdFusion.Window.show('deleteArtistWin');
}
#28 Posted By: Dan Vega Posted On: 8/26/08 10:00 AM |
Author Comment
Nice find! I will have to look up the API. I think I am getting so use to Flex that I enjoy doing this

button.enabled = false;
#29 Posted By: Jose Alfonso Posted On: 8/29/08 9:03 PM
Hey Dan,
It's all very useful but with all the hacking going on and the very obvious differences between what CF uses of Ext and v2.2. Did you not find yourself choosing to use the newer library instead of the CF related tags?

By the way, a constructive criticism: Information specific about your cfext seems scattered and mixed in with your CF hacks here in the blog. You are such a mentor in several subjects that you seem to have outgrown this "blog format".
I think some of you guys can benefit from a format similar to the ext 2.2 docs (is that awesome or what!)
#30 Posted By: Calvert Acklin Posted On: 9/28/08 12:41 AM
Good example, but what if you want to select and delete multiple "Artists"?

I know the grid can be set to multiselect mode via the following:

// enable multiSelect within Grid Object
ColdFusion.Grid.getGridObject('ArtistGrid').getSelectionModel().singleSelect = false;

But am wondering if you or anyone else knows how to delete the selected Artists.
#31 Posted By: Calvert Acklin Posted On: 9/29/08 9:22 AM
Answered my on question again:

var grid = ColdFusion.Grid.getGridObject('Artist');
var selRecs = grid.getSelectionModel().getSelections();

var sm = grid.getSelectionModel();
var ds = grid.getDataSource();
var selnum = selRecs.length;      
var arrSelTemp =new Array(selnum);
var len=selRecs.length;
var str="";

for(var i=0; i<len;i++){
var id = selRecs[i].id;
   
arrSelTemp[i]= selRecs[i].get("ARTISTID");

//use join() to convert array to a string
str = arrSelTemp.join();

Ext.Msg.alert("Show selections: ","Select: " + str);
      
};
#32 Posted By: knk Posted On: 10/17/08 9:57 AM
Is there a way for me to add paging to the args?
for example <cfset args.pagesize = "10">

How should I go about this?

Thanks
#33 Posted By: Dan Vega Posted On: 10/17/08 10:03 AM |
Author Comment
I have recently fought this same issue. The problem here is you can setup paging but when you go to filter the data loaded into the grid is the only data to filter on. What you are looking for is now a search feature. The filter is more of a give me "x" out of this data set
#34 Posted By: Chris Ulrich Posted On: 9/30/09 3:40 PM
Just a tip (I know this is an old thread). I've been using this and had my buttons disappear. Went nuts for days. Finally found it...

The buttons base used (by me) to open CFWINDOWs. The <cfset ajaxOnLoad("init")> was not being invoked at the bottom of the page. For the heck of it, I moved it above the CFWINDOW statements and boom - it all worked fine.

So, for what its worth, for anyone having trouble getting buttons to show up, try nudging that <cfset ajaxOnLoad("init")> statement higher on the page. It just might do the trick. :^)
#35 Posted By: David Posted On: 10/15/09 11:19 AM
This code no longer works in CF9

grid = ColdFusion.Grid.getGridObject("ArtistGrid");
var gridFoot = grid.getView().getFooterPanel(true);
var gridHead = grid.getView().getHeaderPanel(true);

I just get an errorError: grid.getView().getHeaderPanel is not a function
#36 Posted By: Tim Posted On: 12/30/09 11:08 AM
@David -
For this to work under CF9, see:
http://www.coldfusionjedi.com/index.cfm/2009/10/15...


Post Your Comment

Leave this field empty







Show Captcha

If you subscribe, any new posts to this thread will be sent to your email address.

Copyright © 2007 Dan Vega | BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.