I have seen many requests for example ExtJS grids working with ColdFusion. We are all familiar with Cutter Blades examples throughout Sencha and on various ColdFusion blogs. If you do not have a link to his site here it is: http://www.cutterscrossing.com/. Our other savior has been Kumar who has posted a number of articles about grids. Here is his blogging site: http://www.coldfusion-ria.com/Blog/index.cfm/2009/7/13/ExtJS-30-Grid-with-ColdFusion-8
Both of them post excellent examples. My Best Examples came from the ExtJS CookBook by Jorges Ramon and Learning ExtJS and Learning ExtJS 3.2 by Shea Frederick, Colin Ramsay, Steve 'Cutter' Blades and Nigel White.
After you have seen the examples of the Sencha site you know there are many variations of Grids available. My two favorite grids are the cell editor grid and the row editor grid.
This article presents the cell editor grid. The cell editor grid allows you to edit one cell in a grid at a time and it fires an event that calls your cfc and updates that particular column for that particular record.
The following example presents some other nice treats including a paging grid, the row numberer, and the checkbox model.
First I start out with my cfm page. I include a large number of style tages to support word wrapping in the columns and other items too lengthly to mention here. Feel free to disregard them. The main thing you need are the correct extjs files. I also included scriptalicous and lightbox tags because I love grids that use lightbox to expand the image. You do not need these. You just need the extjs files and the javascript file you will be using. In this case I am calling the file editgridcellMovie.js.
Blogger removes the hmtl tags. So create a new cfm page, set up the html opening and closing tags, insert the code below inside the opening and closing header tags. Put the divs at the end inside the body tags and you will be good to go.
In case you are not familiar with this version of syntax highlighter, you just double click the code and it copies it into note pad for you. You will not see the old toolbar that allows you to select for copying.
Ext JS Grid with ColdFusion
Now to the Javascript File. I am sure after all of your reading and searching it should make sense. If not, I will follow up with explanations of each piece.
Ext.onReady(function(){
var Movie = Ext.data.Record.create([
{name:'ID',allowBlank:false},
{name:'COVERTHUMB',type:'string'},
{name:'TITLE',type:'string'},
{name:'DIRECTOR',type:'string'},
{name:'RELEASED',type:'date'},
{name:'RUNTIME',type:'string'},
{name:'GENRE'},
{name:'TAGLINE',type:'string'},
{name:'PRICE',type:'float'},
{name:'AVAILABLE',type:'boolean'}
])
//image path
var IMG_LOC = 'images/';
//renderer function
function cover_image(val) {
/*return '
';*/
return '
';
/*return '
';*/
}
var genres = new Ext.data.SimpleStore({
fields: ['id', 'GENRE'],
data : [
['0','New Genre'],
['1','Comedy'],
['2','Drama'],
['3','Action'],
['4','Horror'],
['5','Adult']
]
});
/*retrieves current date*/
var currentDate = '10/27/1955';
/* new Date().format('Y-m-d')new Date().format('Y-m-d')*/
/* THIS CODE SECTION SHOWS HOW TO CALL THE GENRES DATABASE TABLE WHEN YOU CHOOSE NOT TO USE THE SIMPLE STORE
var genres = new Ext.data.Store({
reader: new Ext.data.JsonReader({
fields: ['ID', 'genre_name'],
root: 'ROWS'
}),
proxy: new Ext.data.HttpProxy({
url: 'genres3.cfc?method=getGenres'
}),
autoLoad: true
});
genres.load();*/
var genre_edit = new Ext.form.ComboBox({
typeAhead: true,
forceSelection: true,
triggerAction: 'all',
mode: 'local',
store: genres,
displayField:'GENRE',
valueField: 'id',
listeners: {
//select: function(field, rec, selIndex){
// if (selIndex == 0){
// Ext.Msg.prompt('New Genre', 'Name', Ext.emptyFn);
select: function(field, rec, idx){
if (i === 0){
Ext.Msg.prompt('New Genre','Name',Ext.emptyFn);
}
}
}
});
function genre_name(val){
return genres.queryBy(function(rec){
return rec.data.id == val;
}).itemAt(0).data.GENRE;
}
// create the Data Store
var store = new Ext.data.Store({
url: 'movies.cfc?method=getMovies',
reader: new Ext.data.JsonReader({
root:'ROWS',
totalProperty:'DATASET',
id:'ID'
}, Movie),
autoLoad: true,
fields: [
'ID', 'COVERTHUMB', 'TITLE', 'DIRECTOR','RELEASED', 'RUNTIME', 'GENRE', 'TAGLINE', 'PRICE', 'AVAILABLE']
});
// Our Form Variable to be used for editor grid
var varForm = Ext.form;
// the check column is created using a custom plugin
var checkColumn = new Ext.grid.CheckColumn({
header: 'Available?',
dataIndex: 'AVAILABLE',
width: 55,
editor: new varForm.Checkbox({
})
});
//We setup the Grid
var cm = new Ext.grid.ColumnModel({
columns:[
new Ext.grid.RowNumberer(),//This will do numbering on the grid for us
{
header: "ID",
dataIndex:'ID',
editable: false,
width:40
},
{
header: "Cover Thumb",
dataIndex: 'COVERTHUMB',
width: 100,
hidden: false,
sortable: true,
/*renderer: imageCellRenderer,*/
renderer: cover_image,
editor: new varForm.TextField({
allowBlank: false
})
/*renderer: renderIMG,*/
/*editor: new varForm.TextField({
allowBlank: false*/
/*}) */
},
{
header: "Title",
dataIndex: 'TITLE',
width: 150,
hidden:false,
sortable: true,
editor: new varForm.TextField({
allowBlank: false
})
}
,{
header: "Director",
dataIndex: 'DIRECTOR',
width: 150,
hidden: false,
sortable: true,
editor: new varForm.TextField({
allowBlank: false
})
},
{
header: "Released",
dataIndex: 'RELEASED',
width: 50,
type: 'date',
hidden: false,
sortable: 'true',
dateFormat: 'm/d/Y',
/*release_edit = new Ext.form.DateField({
format: 'm/d/Y'
}),*/
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
editor: new varForm.DateField({
format: 'm/d/Y'
})
},
{
header: "Run Time",
dataIndex: 'RUNTIME',
width: 50,
hidden: false,
sortable: true,
editor: new varForm.TextField({
allowBlank: false
})
},
/*SELECT id, coverthumb, title, director, runtime, released, genre, tagline, price, available*/
{
header: "Genre",
dataIndex: 'GENRE',
width: 50,
hidden: false,
editor: genre_edit,
renderer: genre_name
/*editor: new varForm.TextField
({
allowBlank: false
})*/
},
{
header: "Tagline",
dataIndex:'TAGLINE',
width: 250,
hidden: false,
sortable: true,
editor: new varForm.TextField({
allowBlank: false
})
},
{
header: "Price",
dataIndex: 'PRICE',
width: 50,
hidden: false,
sortable: true,
renderer: 'usMoney',
editor: new varForm.NumberField({
allowBlank: true,
allowNegative: false,
maxValue: 100000
})
}
,
checkColumn
// the plugin instance
// header: "Available",
// dataIndex: 'AVAILABLE',
// width: 100,
// /* xtype: 'checkbox',*/
// hidden: false,
// sortable: true,
// editor: new varForm.Checkbox({
// })
]
})
sm: new Ext.grid.RowSelectionModel({
singleSelect: true,
listeners: {
rowselect: function(smObj, rowIndex, record) {
selRecordStore = record;
}
}
})
// create the editor grid
var grid = new Ext.grid.EditorGridPanel({
width:1200,
height:500,
title:'Browse through the Items in Donnas Shop',
store: store,
trackMouseOver:true,
disableSelection:false,
loadMask: true,
stripeRows: true,
collapsible: true,
columnLines: true,
iconcls:'icon-grid',
cm:cm,//Our column model
frame:true,//Make it more nicer looking (?)
clicksToEdit: 1,//One click on row data will bring on edit box
renderTo:'editgrid',
region: 'center',
// inline buttons
buttons:[{text:'Save'},{xtype:'tbfill'},{text:'Cancel'},{xtype:'tbfill'}],
buttonAlign:'center',
// paging bar on the bottom
bbar: new Ext.PagingToolbar({
pageSize: 50,
store: store,
displayInfo: true,
displayMsg: 'Displaying Records {0} - {1} of {2}',
emptyMsg: "No Records to display",
items:[
'-', {
pressed: true,
enableToggle:true,
text: 'Show Preview',
iconCls: 'icons/user_suit.gif'
//cls: 'x-tbar-loading',
},'-', {
pressed: true,
enableToggle:true,
text: 'Show Preview',
icon: 'icons/user_female.gif'
//cls: 'x-tbar-loading'
}]
}) ,
sm: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
listeners: {
afteredit: function(e){
Ext.Ajax.request({
url: 'movies.cfc?method=editMovieCell',
params: {
action: 'update',
id: e.record.id,
field: e.field,
value: e.value
},
success: function(resp,opt) {
e.record.commit();
var success_message = Ext.util.JSON.decode(
response.message
).response.message;
Ext.alert('success', success_message);
},
failure: function(resp,opt) {
e.record.reject();
}
});
}
},
keys: [{
key: 46,
fn: function(key,e){
var sm = grid.getSelectionModel(),
sel = sm.getSelected();
if (sm.hasSelection()){
Ext.Msg.show({
title: 'Remove Movie',
buttons: Ext.MessageBox.YESNOCANCEL,
msg: 'Remove ' + sel.data.TITLE + '?',
fn: function(btn){
if (btn == 'yes'){
Ext.Ajax.request({
url: 'movies.cfc?method=removeMovies',
params: {
action: 'destroy',
id: sel.data.ID
},
success: function(resp,opt) {
grid.getStore().remove(sel);
},
failure: function(resp,opt) {
Ext.Msg.alert('Error','Unable to delete movie');
}
});
}
}
});
}
},
ctrl: false,
stopEvent: true
}],
tbar: [{
text: 'Add Movie',
/* icon: 'images/table_add.png',*/
icon: 'icons/fam/add.gif',
cls: 'x-btn-text-icon',
handler: function() {
Ext.Ajax.request({
url: 'movies.cfc?method=addMovie',
params: {
action: 'create',
TITLE: 'New Movie'
},
success: function(resp,opt) {
var INSERT_ID = Ext.util.JSON.decode(
resp.responseText
).INSERT_ID;
grid.getStore().insert(0, new Movie({
ID: INSERT_ID,
COVERTHUMB: 'blankmovie.jpg',
TITLE: 'New Movie',
DIRECTOR: 'Bud Hines',
RUNTIME: '2',
RELEASED: '10/27/2002',
GENRE: '1',
TAGLINE: 'What a Great Movie',
PRICE: 22.99,
AVAILABLE: 1
}, INSERT_ID)
);
grid.startEditing(0,0);
},
failure: function(resp,opt) {
Ext.Msg.alert('Error','Unable to add movie');
}
});
}
},{
text: 'Remove Movie',
/* icon: 'images/table_delete.png',*/
icon: 'icons/fam/delete.gif',
cls: 'x-btn-text-icon',
handler: function() {
var sm = grid.getSelectionModel(),
sel = sm.getSelected();
if (sm.hasSelection()){
Ext.Msg.show({
TITLE: 'Remove Movie',
buttons: Ext.MessageBox.YESNOCANCEL,
msg: 'Remove ' + sel.data.TITLE + '?',
fn: function(btn){
if (btn == 'yes'){
Ext.Ajax.request({
url: 'movies.cfc?method=removeMovies',
params: {
action: 'destroy',
id: sel.data.ID
},
success: function(resp,opt) {
grid.getStore().remove(sel);
},
failure: function(resp,opt) {
Ext.Msg.alert('Error','Unable to delete movie');
}
});
}
}
});
}
}
}]
});
});
And here is the cfc with all the functions to handle the get movies, edit movie cell, remove movie and add movie functionality.
SELECT ID as id, coverthumb, title, director, runtime, released, genre, tagline, price, available FROM movies // Define the local scope. var LOCAL = StructNew(); // Get the column names as an array. LOCAL.Columns = ListToArray( ARGUMENTS.Data.ColumnList ); // Create a structure that will hold the Store. LOCAL.Store.columns = LOCAL.Columns; // Create a second array for the data LOCAL.DataArray = ArrayNew(1); // Loop over the query. for (LOCAL.RowIndex = 1 ; LOCAL.RowIndex LTE ARGUMENTS.Data.RecordCount ; LOCAL.RowIndex = (LOCAL.RowIndex + 1)){ // Create an array for this row LOCAL.Row = ArrayNew(1); // Loop over the columns in this row. for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ArrayLen( LOCAL.Columns ) ; LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){ // Get a reference to the query column. LOCAL.ColumnName = LOCAL.Columns[ LOCAL.ColumnIndex ]; // Store the query cell value into the array by key. LOCAL.Row[ LOCAL.ColumnIndex ] = ARGUMENTS.Data[ LOCAL.ColumnName ][ LOCAL.RowIndex ]; } // Add the row array to the data array. ArrayAppend( LOCAL.DataArray, LOCAL.Row ); } // Add the Data array to the query array LOCAL.Store.data = LOCAL.DataArray ; // Return the store return( LOCAL.Store ); arrRecords = arrayNew(1); if(arguments.start==0) { counter = 1; } else { counter = arguments.start; } for(i=1;i<=arguments.limit;i++) { strResults = structNew(); for(x=1;x<=listLen(clist);x++) { strResults[ucase(listGetAt(clist,x))] = query[listGetAt(clist,x)][counter]; } arrRecords[i] = strResults; counter = counter+1; } return arrRecords; UPDATE movies SET #PreserveSingleQuotes(strUpdQuery)# WHERE id = #Arguments.id# UPDATE movies SET Price = #Arguments.value# WHERE id = #Arguments.id# Delete from movies WHERE id = #Arguments.id# Insert into Movies ( TITLE, DIRECTOR, RELEASED, GENRE, TAGLINE, COVERTHUMB, AVAILABLE, RUNTIME, PRICE ) Values ( 'New TITLE', 'DIRECTOR', 03/01/2011, '1', 'Great Movie', 'blankmovie.jpg', 1, 1, 24.99 ) Select max(ID) as myID from movies where TITLE = 'New TITLE'
No comments:
Post a Comment