I started working with an ExtJS 4.0 Grid linked to a form example and miraculously got in working. I say miraculously because I believed with the changes to version 4 this would not be possible. But it was. The internal workings of ExtJS changed slightly but nonetheless I got it to work.
In my previous cell and row editor examples, after an insert, I check the response, get the insert_id or the id for the new row and insert it into the data before I load the grid. Here is the code:
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');
}
});
}
},
In the CRUD example, you do not need to query the response to load the grid. ExtJS does this automatically by grabbing the row you pass back and inserting it into the grid and form. Pretty cool.
Well what did I change in the writer example to get it to work with ColdFusion?First, I had to add an application.cfc. I kept debugging my code and seeing in my firebug html response that ColdFusion kept connecting to the ColdFusion administrator. Apparently every ColdFusion application requires the application cfc or it dies. I have seen this in numerous other jquery demos where I was using ColdFusion. So first, copy an application CFC, change what is necessary like the application name, and the "#request.dsn#" and paste it in the examples folder.
In this example, I am assuming you are modifying the code in the examples folder and working out of the examples folder. So go to the examples folder in ExtJS 3.2, look for the writer folder and click on that. We will be working with the files inside this folder. We will rename all of the files we need to touch from the file name to the filenamecfm so we can distinguish our changes.
There are four files we need to change in this example:
- The form javascript file: UserForm.js which was renamed to UserFormcfm.js
- The grid javascript file: UserGrid.js which was renamed to UserGridcfm.js
- The writer javascript file: writer.js which was renamed to writercfm.js
- The writer html file: writer.html which was renamed to writer.cfm
Let's take a look at each one and start with the UserForm.js file. Here the only thing I needed to change was the name of the data fields. Since ColdFusion, the way I coded it, returns uppercased names, I changed email to EMAIL, firstname to FIRSTNAME AND lastname to LASTNAME. Those are all the changes needed in this file.
/*!
* Ext JS Library 3.2.0
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
Ext.ns('App', 'App.user');
/**
* @class App.user.FormPanel
* A typical FormPanel extension
*/
App.user.Form = Ext.extend(Ext.form.FormPanel, {
renderTo: 'user-form',
iconCls: 'silk-user',
frame: true,
labelAlign: 'right',
title: 'User -- All fields are required',
frame: true,
width: 500,
defaultType: 'textfield',
defaults: {
anchor: '100%'
},
// private A pointer to the currently loaded record
record : null,
/**
* initComponent
* @protected
*/
initComponent : function() {
// build the form-fields. Always a good idea to defer form-building to a method so that this class can
// be over-ridden to provide different form-fields
this.items = this.buildForm();
// build form-buttons
this.buttons = this.buildUI();
// add a create event for convenience in our application-code.
this.addEvents({
/**
* @event create
* Fires when user clicks [create] button
* @param {FormPanel} this
* @param {Object} values, the Form's values object
*/
create : true
});
// super
App.user.Form.superclass.initComponent.call(this);
},
/**
* buildform
* @private
*/
buildForm : function() {
return [
{fieldLabel: 'Email', name: 'EMAIL', allowBlank: false, vtype: 'email'},
{fieldLabel: 'First', name: 'FIRSTNAME', allowBlank: false},
{fieldLabel: 'Last', name: 'LASTNAME', allowBlank: false}
];
},
/**
* buildUI
* @private
*/
buildUI: function(){
return [{
text: 'Save',
iconCls: 'icon-save',
handler: this.onUpdate,
scope: this
}, {
text: 'Create',
iconCls: 'silk-user-add',
handler: this.onCreate,
scope: this
}, {
text: 'Reset',
handler: function(btn, ev){
this.getForm().reset();
},
scope: this
}];
},
/**
* loadRecord
* @param {Record} rec
*/
loadRecord : function(rec) {
this.record = rec;
this.getForm().loadRecord(rec);
},
/**
* onUpdate
*/
onUpdate : function(btn, ev) {
if (this.record == null) {
return;
}
if (!this.getForm().isValid()) {
App.setAlert(false, "Form is invalid.");
return false;
}
this.getForm().updateRecord(this.record);
},
/**
* onCreate
*/
onCreate : function(btn, ev) {
if (!this.getForm().isValid()) {
App.setAlert(false, "Form is invalid");
return false;
}
this.fireEvent('create', this, this.getForm().getValues());
this.getForm().reset();
},
/**
* onReset
*/
onReset : function(btn, ev) {
this.fireEvent('update', this, this.getForm().getValues());
this.getForm().reset();
}
});
The next file we will change is the UserGrid.js file. Here again I changed email, lastname and firstname to upper case and that is all that was necessary.
/*!
* Ext JS Library 3.2.0
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
Ext.ns('App', 'App.user');
/**
* App.user.Grid
* A typical EditorGridPanel extension.
*/
App.user.Grid = Ext.extend(Ext.grid.EditorGridPanel, {
renderTo: 'user-grid',
iconCls: 'silk-grid',
frame: true,
title: 'Users',
height: 300,
width: 500,
style: 'margin-top: 10px',
initComponent : function() {
// typical viewConfig
this.viewConfig = {
forceFit: true
};
// relay the Store's CRUD events into this grid so these events can be conveniently listened-to in our application-code.
this.relayEvents(this.store, ['destroy', 'save', 'update']);
// build toolbars and buttons.
this.tbar = this.buildTopToolbar();
this.bbar = this.buildBottomToolbar();
this.buttons = this.buildUI();
// super
App.user.Grid.superclass.initComponent.call(this);
},
/**
* buildTopToolbar
*/
buildTopToolbar : function() {
return [{
text: 'Add',
iconCls: 'silk-add',
handler: this.onAdd,
scope: this
}, '-', {
text: 'Delete',
iconCls: 'silk-delete',
handler: this.onDelete,
scope: this
}, '-'];
},
/**
* buildBottomToolbar
*/
buildBottomToolbar : function() {
return ['@cfg:', '-', {
text: 'autoSave',
enableToggle: true,
pressed: true,
tooltip: 'When enabled, Store will execute Ajax requests as soon as a Record becomes dirty.',
toggleHandler: function(btn, pressed) {
this.store.autoSave = pressed;
},
scope: this
}, '-', {
text: 'batch',
enableToggle: true,
pressed: true,
tooltip: 'When enabled, Store will batch all records for each type of CRUD verb into a single Ajax request.',
toggleHandler: function(btn, pressed) {
this.store.batch = pressed;
},
scope: this
}, '-', {
text: 'writeAllFields',
enableToggle: true,
tooltip: 'When enabled, Writer will write *all* fields to the server -- not just those that changed.',
toggleHandler: function(btn, pressed) {
store.writer.writeAllFields = pressed;
},
scope: this
}, '-'];
},
/**
* buildUI
*/
buildUI : function() {
return [{
text: 'Save',
iconCls: 'icon-save',
handler: this.onSave,
scope: this
}];
},
/**
* onSave
*/
onSave : function(btn, ev) {
this.store.save();
},
/**
* onAdd
*/
onAdd : function(btn, ev) {
var u = new this.store.recordType({
FIRSTNAME : '',
LASTNAME: '',
EMAIL : ''
});
this.stopEditing();
this.store.insert(0, u);
this.startEditing(0, 1);
},
/**
* onDelete
*/
onDelete : function(btn, ev) {
var index = this.getSelectionModel().getSelectedCell();
if (!index) {
return false;
}
var rec = this.store.getAt(index[0]);
this.store.remove(rec);
}
});
The next file I changed was the writer.js file. Here I needed to change the calls to the php urls to my urls and add a method=getUsers or method=addUser or method=editUser or method=removeUser depending on the action the action that is requested.Here are the changes I just described:
read: 'UsersExt.cfc?method=getUsers',
//Add a row
create: 'UsersExt.cfc?method=addUser',
//Update a row
update: 'UsersExt.cfc?method=editUser',
//Delete a row
destroy: 'UsersExt.cfc?method=removeUser'
I then needed to change the json reader and change the totalProperty to DATASET, root to ROWS, id to ID, messageProperty to MESSAGE and successProperty to SUCCESS as this is what my CFC returns.
var reader = new Ext.data.JsonReader({
totalProperty:'DATASET',//This is how many total records are there in the set.
root:'ROWS',//The Root of the data.
proxy:proxy,
id:'ID',
messageProperty: 'MESSAGE',
successProperty: 'SUCCESS'
//totalProperty: 'total',
//successProperty: 'success',
// idProperty: 'id',
//root: 'data',
//messageProperty: 'message' // <-- New "messageProperty" meta-data
}, [
{name: 'ID'},
{name: 'EMAIL', allowBlank: false},
{name: 'FIRSTNAME', allowBlank: false},
{name: 'LASTNAME', allowBlank: false}
]);
// The new DataWriter component.
var writer = new Ext.data.JsonWriter({
encode: true,
writeAllFields: false
});
Please pay attention to the uppercasing of these fields. I also changed the email, firstname and lastname fields to uppercase where I define the fields for the reader as well further down in the code where I am defining my userColumns. Notice that i changed the dataIndex names to uppercase as well.
/*!
* Ext JS Library 3.2.0
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
// Application instance for showing user-feedback messages.
var App = new Ext.App({});
// Create HttpProxy instance. Notice new configuration parameter "api" here instead of load. However, you can still use
// the "url" paramater -- All CRUD requests will be directed to your single url instead.
var proxy = new Ext.data.HttpProxy({
api: {
//Read the table and pass back an array of data
read: 'UsersExt.cfc?method=getUsers',
//Add a row
create: 'UsersExt.cfc?method=addUser',
//Update a row
update: 'UsersExt.cfc?method=editUser',
//Delete a row
destroy: 'UsersExt.cfc?method=removeUser'
}
});
// Typical JsonReader. Notice additional meta-data params for defining the core attributes of your json-response
var reader = new Ext.data.JsonReader({
totalProperty:'DATASET',//This is how many total records are there in the set.
root:'ROWS',//The Root of the data.
proxy:proxy,
id:'ID',
messageProperty: 'MESSAGE',
successProperty: 'SUCCESS'
//totalProperty: 'total',
//successProperty: 'success',
// idProperty: 'id',
//root: 'data',
//messageProperty: 'message' // <-- New "messageProperty" meta-data
}, [
{name: 'ID'},
{name: 'EMAIL', allowBlank: false},
{name: 'FIRSTNAME', allowBlank: false},
{name: 'LASTNAME', allowBlank: false}
]);
// The new DataWriter component.
var writer = new Ext.data.JsonWriter({
encode: true,
writeAllFields: false
});
// Typical Store collecting the Proxy, Reader and Writer together.
var store = new Ext.data.Store({
id: 'user',
proxy: proxy,
reader: reader,
writer: writer, // <-- plug a DataWriter into the store just as you would a Reader
autoSave: true // <-- false would delay executing create, update, destroy requests until specifically told to do so with some [save] buton.
});
// load the store immeditately
store.load();
////
// ***New*** centralized listening of DataProxy events "beforewrite", "write" and "writeexception"
// upon Ext.data.DataProxy class. This is handy for centralizing user-feedback messaging into one place rather than
// attaching listenrs to EACH Store.
//
// Listen to all DataProxy beforewrite events
//
Ext.data.DataProxy.addListener('beforewrite', function(proxy, action) {
App.setAlert(App.STATUS_NOTICE, "Before " + action);
});
////
// all write events
//
Ext.data.DataProxy.addListener('write', function(proxy, action, result, res, rs) {
App.setAlert(true, action + ':' + res.message);
});
////
// all exception events
//
Ext.data.DataProxy.addListener('exception', function(proxy, type, action, options, res) {
if (type === 'remote') {
Ext.Msg.show({
title: 'REMOTE EXCEPTION',
msg: res.message,
icon: Ext.MessageBox.ERROR,
buttons: Ext.Msg.OK
});
}
});
// A new generic text field
var textField = new Ext.form.TextField();
// Let's pretend we rendered our grid-columns with meta-data from our ORM framework.
var userColumns = [
{header: "ID", width: 40, sortable: true, dataIndex: 'ID'},
{header: "Email", width: 100, sortable: true, dataIndex: 'EMAIL', editor: textField},
{header: "First", width: 50, sortable: true, dataIndex: 'FIRSTNAME', editor: textField},
{header: "Last", width: 50, sortable: true, dataIndex: 'LASTNAME', editor: textField}
];
Ext.onReady(function() {
Ext.QuickTips.init();
// create user.Form instance (@see UserForm.js)
var userForm = new App.user.Form({
renderTo: 'user-form',
listeners: {
create : function(fpanel, data) { // <-- custom "create" event defined in App.user.Form class
var rec = new userGrid.store.recordType(data);
userGrid.store.insert(0, rec);
}
}
});
// create user.Grid instance (@see UserGrid.js)
var userGrid = new App.user.Grid({
renderTo: 'user-grid',
store: store,
columns : userColumns,
listeners: {
rowclick: function(g, index, ev) {
var rec = g.store.getAt(index);
userForm.loadRecord(rec);
},
destroy : function() {
userForm.getForm().reset();
}
}
});
});
Our next step is to modify the writer.html file and save it as writer.cfm. Here we modify the javascript files to reference the files we have just changed:
- UserFormcfm.js
- UserGridcfm.js
- writercfm.js
And finally we create the CFC. Here is the code for the ExtUsers CFC:Grid with DataWriter Example Ext.data.DataWriter Example
This example shows how to implement a Writer for your Store. A Writer-enabled Store frees you from having to manually compose Ajax requests to perform CRUD actions on a Store.
Note that the js is not minified so it is readable. See writer.js, UserForm.js and UserGrid.js.
The HttpProxy plugged into the store in this example uses the new api configuration instead of an url. A simple MVC-like php backend has been created for this example which simulates a database by storing records in $_SESSION. See the file /remote/app/controllers/users.php. You may have to configure your web-server to allow scripts to be executed in the /examples directory.
var proxy = new Ext.data.HttpProxy({ api: { read: 'UsersExt.cfc?method=getUsers',//Our URL for reading the grid data create: 'UsersExt.cfc?method=addUser',//For Adding a new User (future implementation) update: 'UsersExt.cfc?method=editUser',//When a User is updated destroy: 'UsersExt.cfc?method=removeUser'//When a User is updated } });Take note of the requests being generated in Firebug as you interact with the Grid and Form.
An Error has been simulated on the server-side: Attempting to update a record having ODD-numbered id will generate this errror. Responses from the update action will have successProperty === false along with a message. This error can be handled by listening to the "exception" event upon your Store.
exception : function(proxy, type, action, options, res, arg) { if (type === 'remote') { Ext.Msg.show({ title: 'REMOTE EXCEPTION', msg: res.message, icon: Ext.MessageBox.ERROR }); } }Note: This new "exception" event supercedes the old loadexception event which is now deprecated.
SELECT UserID as ID, Email, FirstName, LastName FROM Users ORDER BY UserID Asc UPDATE Users Set Email = '#stcData.EMAIL#', LastName = '#stcData.LASTNAME#', FirstName = '#stcData.FIRSTNAME#' WHERE UserID = #Arguments.id# SELECT UserID as ID, Email, FirstName, LastName FROM Users WHERE UserID = #Arguments.id# Insert into Users ( Email, FirstName, LastName ) Values ( '#stcData.EMAIL#', '#stcData.FIRSTNAME#', '#stcData.LASTNAME#' ) SELECT UserID AS ID, Email, FirstName, LastName FROM Users WHERE (Email = '#stcData.EMAIL#') AND (LastName = '#stcData.LASTNAME#') AND (FirstName = '#stcData.FIRSTNAME#') Delete from Users WHERE UserID = #Arguments.id#
Now an important part of this application is your database. I am assuming, that you have a SqlServer, Derby, Access or MySql database with a table named Users with the following fields defined:
- UserID defined as Integer or int
- FirstName defined as Charachter 50 or nvarchar(50)
- LastName defined as Charachter 50 or nvarchar(50)
- Email defined as Character 50 or nvarchar(50)
No comments:
Post a Comment