My Examples of Using ExtJS 3.2 and ColdFusion. There do not seem to be many examples out there so I thought I would start contributing to help my fellow ColdFusion coders.
Friday, March 22, 2013
How to format your html for syntax highlighting
ColdFusion Loading and Saving The Coolest ExjtJS Form
I read through the ExtJS Cookbook by Jorge Ramon and he presented the coolest form I have seen. But, I needed a way to populate the form with ColdFusion. Well I checked the Sencha blogs and found a cool example by Murray Hopkins. Here is the link to the discussion: http://www.sencha.com/forum/showthread.php?20172-Example-FormPanel-using-JSON-to-load-and-submit-with-Coldfusion-or-PHP
I worked through the example where he provides a window to enter a record number. I had just been experimenting with linked combo boxes and decided to use a seperate search form using linked combo boxes to drive or populate the form. The result looks like this (click on the picture and you will get a better view):
Here is the picture of the form with the picture upload:
I think this is wonderful. It even includes an upload window to upload a new picture.
Sweet.So how do we do it? Well first I will give you the entire code base and then I will explain it.
HTML Files are here:
<!DOCTYPE HTML>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css">
<style type="text/css">
.x-fieldset
{
border-style: solid none none none;
border-width: 1px;
border-color: #B5B8C8;
padding: 10px;
margin-bottom: 10px;
margin-right: 20px;
display: block;
}
.left-right-buttons .x-panel-btns {
text-align: 'center'
}
.left-right-buttons .x-panel-btns table {
width: 100%;
}
.left-right-buttons .x-panel-btns .x-btn-left {
float: left;
}
</style>
<script src="adapter/ext/ext-base.js"></script>
<script src="js/ext-all-debug.js"></script>
<script type="text/javascript">
// Loading form data from the server.
Ext.BLANK_IMAGE_URL = 'images/default/s.gif';
// Required if showing validation messages
Ext.QuickTips.init();
var picBox1 = {
columnWidth: '100 px',
bodyStyle: 'padding:10px',
items: [
{ xtype: 'box',
autoEl: { tag: 'div',
html: '<img id="mypic" src="img/" + Ext.BLANK_IMAGE_URL + class="img-contact" width="100" height="150"/>'
}
},
{xtype: 'textfield',
name: 'pic2',
id: 'pic2',
label: 'File Name'
}
]
}
var picFiles = {
columnWidth: .65,
layout: 'form',
labelAlign:'top',
items: [
// {
// xtype: 'textfield',
// fieldLabel: 'Current',
// labelSeparator: '',
// name: 'currPic',
// id:'currPic',
// readOnly: true,
// disabled:true,
// anchor:'100%'
// },
{
xtype: 'textfield',
fieldLabel: 'New (JPG or PNG only)',
labelSeparator: '',
name: 'newPic',
id:'newPic',
style:'width: 300px',
inputType: 'file',
allowBlank: false
}
]
}
/* get the contact id from the contact form and store it in an itermediary value*/
var myholdid = Ext.getCmp('id');
var pictUploadForm = new Ext.FormPanel({
id: 'pictUploadForm',
frame: true,
title: 'Change Picture For',
bodyStyle: 'padding:5px',
width: 620,
height: 300,
layout: 'column',
/*url: 'contact-picture.aspx',*/
/*url: 'fileuploadtest.cfm',*/
/*url: 'fileuploadaction.cfm',*/
url: 'fileuploadaction2.cfc?method=uploadPic',
params: {
action: 'upload',
/* pass the itermediary value to the cfc so the pic can be updated*/
id: 'contactid'
},
method: 'POST',
fileUpload: true,
enctype: 'multipart/form-data',
/*width: 900,
height: 600,*/
items: [picBox1, picFiles]
});
var LastNameStore = new Ext.data.JsonStore({
url: 'contacts.cfc?method=getLastName',
baseParams:{cmd:'LastName'},
root: 'ROWS',
fields: [ 'LASTNAME']
});
var FirstNameStore = new Ext.data.JsonStore({
url: 'contacts.cfc?method=getFirstName',
baseParams: { cmd:'FirstName'},
root: 'ROWS',
fields: ['FIRSTNAME', 'RECORDID' ]
});
var LastNameCombo = {
xtype: 'combo',
id: 'LastNameCombo',
store: LastNameStore,
displayField: 'LASTNAME',
valueField: 'LASTNAME',
typeAhead: true,
editable:false,
mode: 'remote',
forceSelection: true,
triggerAction: 'all',
fieldLabel: 'LastName',
emptyText: 'Select a name...',
selectOnFocus: true,
anchor:'95%',
listeners: {
'select': function(cmb, rec, idx) {
FirstNameCbx = Ext.getCmp('FirstName-combo');
FirstNameCbx.clearValue();
FirstNameCbx.store.load({
params: { 'LastNameCombo': this.getValue() }
});
FirstNameCbx.enable();
}
}
}
var FirstNameCombo = {
xtype: 'combo',
id:'FirstName-combo',
store: FirstNameStore,
displayField: 'FIRSTNAME',
valueField: 'RECORDID',
typeAhead: true,
editable: false,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
fieldLabel: 'FirstName',
emptyText: 'Select a name...',
selectOnFocus: true,
disabled: true,
anchor: '95%',
listeners: {
'select': function(cmb, rec, idx) {
LastNameCombo = Ext.getCmp('LastName-combo');
Ext.getCmp('contactForm').getForm().load({ url: 'contactform1.cfc?method=getContacts', params:{'RECORDID': this.getValue() },waitMsg: 'Loading'});
}
}
}
var nameAndCompany = { columnWidth: .5,
layout: 'form',
bodyStyle:'padding-top:1px',
items: [
{ xtype: 'textfield',
fieldLabel: 'First Name',
name: 'firstname',
anchor: '95%'
}, {
xtype: 'textfield',
fieldLabel: 'Last Name',
name: 'lastname',
anchor: '95%'
}, {
xtype: 'textfield',
fieldLabel: 'Company',
name: 'company',
anchor: '95%'
}, {
xtype: 'textfield',
fieldLabel: 'Title',
name: 'title',
anchor: '95%'
},
{
xtype: 'textfield',
fieldLabel: 'ID',
name: 'id',
id: 'contactid',
anchor: '95%'
}
]
}
var picBox = {
columnWidth: .5,
bodyStyle: 'padding:0px 0px 0px 40px',
items: [
{ xtype: 'box',
autoEl: { tag: 'div', style: 'padding-bottom:20px',
html: '<img id="pic1" src="img/ + contact.pic+" width="100" height="150" />'
}
},
{xtype: 'textfield',
name: 'pic',
id: 'pic',
label: 'File Name'
},
{ xtype: 'button',
text: 'Change Picture',
handler: function(){
Ext.getDom('pic1').src = "img/" + Ext.getDom('pic').value;
/*attempting to load the picture */
var mywin = new Ext.Window({
cls: 'left-right-buttons'
,title: 'Change Picture For'
/* ,id : 'mywin'*/
,width : 650
,height : 325
,plain : true
,modal : true
,closeAction : 'hide'
,closeable : true
,items: [
pictUploadForm
],
buttons: [{
text: 'Close',
name: 'Closebtn',
id: 'Closebtn',
handler: function(){
mywin.hide();
}
},
{
text: 'Upload Picture',
handler: function() {
var theForm = pictUploadForm.getForm();
if (!theForm.isValid()) {
Ext.MessageBox.alert('Change Picture', 'Please select a picture');
return;
}
if (!validateFileExtension(Ext.getDom('newPic').value)) {
Ext.MessageBox.alert('Change Picture', 'Only JPG or PNG, please.');
return;
}
theForm.submit({
params: { act: 'setPicture', 'RECORDID': (Ext.getDom('RECORDID'.value)) }
,
waitMsg: 'Uploading picture',
handler: function() {
Ext.MessageBox.alert('Message', 'hello Rick');
}
})
}
},
{xtype:'tbfill'},
{
text: 'Cancel',
handler: function(){
pictUploadForm.getForm().reset();
},
handler: function(){
Ext.getDom('mypic').src = pic1 ;
Ext.getDom('mypic').src = Ext.get('pic');
Ext.getDom('pic2').value = Ext.getDom('pic').src;
mywin.hide();
}
}],
buttonAlign:'center'
});
mywin.show();
Ext.getDom('pic2').value = Ext.getDom('pic').value;
/* Ext.getDom('currPic').value = Ext.getDom('pic').value;*/
Ext.getDom('currPic').value = Ext.getDom('pic').src;
Ext.getDom('mypic').src = Ext.getDom('pic1').src;
alert('Getting Ready for Picture Upload');
}
}
]
}
;
var internet = { columnWidth: .5,
layout: 'form',
items: [{ xtype: 'fieldset',
title: 'Internet',
autoHeight: true,
defaultType: 'textfield',
items: [{
fieldLabel: 'Email',
name: 'email',
vtype: 'email',
anchor: '95%'
}, {
fieldLabel: 'Web page',
name: 'webpage',
vtype: 'url',
anchor: '95%'
}, {
fieldLabel: 'IM',
name: 'imaddress',
anchor: '95%'
}]
}]
}
var phones = { columnWidth: .5,
layout: 'form',
items: [{ xtype: 'fieldset',
title: 'Phone Numbers',
autoHeight: true,
defaultType: 'textfield',
items: [{
fieldLabel: 'Home',
name: 'homephone',
anchor: '95%'
}, {
fieldLabel: 'Business',
name: 'busphone',
anchor: '95%'
}, {
fieldLabel: 'Mobile',
name: 'mobphone',
anchor: '95%'
}, {
fieldLabel: 'Fax',
name: 'fax',
anchor: '95%'
}]
}]
}
var busaddress = { columnWidth: .5,
layout: 'form',
labelAlign: 'top',
defaultType: 'textarea',
items: [{
fieldLabel: 'Business',
labelSeparator:'',
name: 'baddress',
anchor: '95%'
}
]
}
var homeaddress = { columnWidth: .5,
layout: 'form',
labelAlign: 'top',
defaultType: 'textarea',
items: [{
fieldLabel: 'Home',
labelSeparator:'',
name: 'mailingaddress',
id: 'mailingaddress',
anchor: '95%'
}
]
}
var mailtostuff = { columnWidth: 1,
layout: 'form',
labelAlign: 'top',
defaultType: "radiogroup",
items: [{
fieldLabel: "Mail To",
id: "mailto",
defaults: {xtype: "radio",name: "mailto"},
items: [
{
boxLabel: "Business",
inputValue: "Business",
},
{
boxLabel: "Home",
inputValue: "Home",
}
]
}
]
}
function hideWindow()
{
Ext.getCmp('mywin').hide();
//Even this hide methof has got same three optional parameters like animateTarget, callback, Object scope
//getCmp method is to retrieve the reference to the component
}
function validateFileExtension(fileName) {
var exp = /^.*\.(jpg|JPG|png|PNG)$/;
return exp.test(fileName);
}
Ext.onReady(function() {
var searchForm = new Ext.FormPanel({
frame: true,
title: 'Find Contacts',
bodyStyle: 'padding:5px',
width: 650,
id: 'name-search-frm',
url: 'get-contacts.cfc',
items: [
LastNameCombo, FirstNameCombo
],
buttons: [{
text: 'Go',
region: 'center',
/*buttonAlign:'center',*/
handler: function() {
Ext.getCmp('searchForm').getForm().submit();
}
},
{xtype:'tbfill'},
{xtype:'tbfill'},
{xtype:'tbfill'}, {
text: 'Cancel',
handler: function() {
win.close();
searchForm.getForm().reset();
}
}],
buttonAlign:'right'
});
contactForm = new Ext.FormPanel({
id: 'contactForm',
frame: true,
title: 'Contact Form',
bodyStyle: 'padding:5px',
width: 650,
items: [{
bodyStyle: {
margin: '0px 0px 15px 0px'
},
items: [{
layout: 'column',
items: [nameAndCompany, picBox]
}]
}, {
items: [{
layout: 'column',
items: [phones, internet]
}]
}, {
xtype: 'fieldset',
title: 'Addresses',
autoHeight: true,
hideBorders: true,
layout: 'column',
items: [busaddress, homeaddress, mailtostuff]
}],
buttons: [{
text: 'Save',
disabled: false,
handler: function(){
contactForm.form.submit({
url: 'json-form-load-server-combo10-submit.cfm', // Coldfusion
//url:'json-form-submit.php', // PHP
//url:'json-form-submit.asp', // ASP
waitMsg: 'Saving Data...'
/* Alternatively, instead of using actionfailed / complete (below) you could use these functions: */
,
success: function (form, action) {
Ext.MessageBox.alert('Message', 'Saved OK 3');
/*mywin.hide();*/
},
failure:function(form, action) {
Ext.MessageBox.alert('Message', 'Save failed');
}
});
}
//}];
}, {xtype:'tbfill'},{
text: 'Cancel',
buttonAlign:'right',
handler: function() {
contactForm.getForm().reset();
}
}],
buttonAlign:'right'
});
contactForm.on({
actioncomplete: function(form, action){
if(action.type == 'load'){
var contact = action.result.data;
contactForm.setTitle(contact.firstname + ' ' + contact.lastname);
Ext.getDom('pic1').src = "img/" + contact.pic ;
pictUploadForm.setTitle(contact.firstname + ' ' + contact.lastname);
mypic=document.getElementById(pic1);
}
}
});
searchForm.render(document.body);
contactForm.render(document.body);
contactForm.getForm().load({ url: 'contactform.cfc?method=getContacts', params:{id:'contact1'},waitMsg: 'Loading'});
// Define the Submit button and the action required. This will be enabled if the Load is successful.
var submit = contactForm.addButton({
text: 'Submit',
disabled: true,
handler: function(){
contactForm.form.submit({
url:'json-form-submit.cfm', // Coldfusion
waitMsg:'Saving Data...'
});
}
});
var addpic = contactForm.addButton({
text: 'addpic',
disabled: false,
handler: function(){
contactForm.form.submit({
url:'form-file-upload.cfm', // Coldfusion
waitMsg:'Saving Data...'
});
}
});
contactForm.render('form-ct');
pictUploadForm.on({
actioncomplete: function(form, action) {
if(action.type == 'load'){
submit.enable();
Ext.getDom('pic2').val = Ext.getDom('pic').val;
Ext.get('mypic').dom.src = Ext.get('pic1').dom.src;
/* Ext.get(id).dom.src = 'newsrc.jpg'*/
Ext.MessageBox.alert('pictUploadForm.actioncomplete', 'upload ok.'); // Optional. Just for testing
}
var result = Ext.decode(action.response.responseText);
/*Ext.MessageBox.alert('Picture Upload Successful', + file );*/
var responseObj = Ext.decode(action.response.responseText);
if (responseObj.success){
var file = responseObj.data.file; }
/*Ext.getDom('pic1').src = 'img/'+file+'"'+ ' width="100" height="150"';*/
Ext.getDom('pic1').src = 'img/'+file;
Ext.getDom('mypic').src = 'img/'+file;
Ext.getDom('currPic').value = file;
Ext.getDom('pic2').value = file;
Ext.getDom('pic').value = file;
console.log('file name', file);
Ext.MessageBox.alert('Picture was successfully uploaded!', + responseObj.data.file );
Ext.getCmp("Closebtn").handler.call(Ext.getCmp("Closebtn").scope);
Ext.Msg.show({
title: 'Picture Upload',
msg: 'Are you finished?',
buttons: {
yes: true,
no: true,
cancel: true
}
});
if (responseObj,!!success){
Ext.MessageBox.alert('file upload failed' );
}
}
});
contactForm.on({
actioncomplete: function(form, action){
// Only enable the submit button if the load worked
if(action.type == 'load'){
submit.enable();
Ext.MessageBox.alert('contactForm.actioncomplete', 'All OK 1.'); // Optional. Just for testing
}
if(action.type == 'submit'){
// If the responseText is a null string, Ext doesnt raise an error so trap it here
// as an error because it should be some json.
Ext.getDom('pic').src = pic.file;
Ext.getDom('currPic').src = pic.file;
console.log('pic');
if(action.response.responseText == '') {
Ext.MessageBox.alert('contactForm.actioncomplete error', 'Form submit returned an empty string instead of json');
} else Ext.MessageBox.alert('contactForm.actioncomplete', 'All OK 2.'); // Optional. Just for testing
}
},
// NOTE: the line var result = Ext.decode(action.response.responseText); must be
// called AFTER you have made sure there wasnt a failureType == "connect" or you
// will probably get a JS error in the Ext library.
actionfailed: function(form,action){
//alert('actionfailed');
if(action.type == 'load') { // Handle the LOAD errors
if (action.failureType == "connect") {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form load failed. Could not connect to server.');
} else {
if (action.response.responseText != '') {
var result = Ext.decode(action.response.responseText);
if(result && result.msg) {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form load failed with error: ' + action.result.msg);
} else {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form load failed with unknown error (possibly missing the "success" field in the json). Action type='+action.type+', failure type='+action.failureType);
}
} else {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form load returned an empty string instead of json');
}
}
}
if(action.type == 'submit') { // Handle the SUBMIT errors
if (action.failureType == "connect") {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form submit failed. Could not connect to server.');
} else
if (action.failureType == "server") {
// These arent "errors" as such, they are validation issues trapped by the server script and passed back for the user to correct
} else {
var result = Ext.decode(action.response.responseText);
if(result && result.msg) {
Ext.MessageBox.alert('contactForm.actionfailed error', 'Form submit failed with error: ' + action.result.msg);
} else {
Ext.MessageBox.alert('actionfailed Error', 'Form submit returned unknown error. Action type='+action.type+', failure type='+action.failureType);
}
}
}
} // end actionfailed listener
}); // end contactForm.on
//});
});
</script>
</head>
<body style="padding: 20px">
<div id="yourwin" class="x-hidden" style="padding:10px">
<input type="button" value="Close" onClick="hideWindow();"/>
<br/>
</div>
</body>
</html>
Here is the code for the CFC:
And here is the code for the form submit:SELECT id , firstName , lastName , company , title , pic , email , webPage , imaddress , homePhone , busPhone , mobPhone , fax , mailto , bAddress , hAddress , mailingAddress FROM contactform where id = #form.RECORDID# where id = 1 thestruct = StructNew(); rtnStruct = StructNew(); thestruct['success'] = StructNew(); thestruct['success'] = true; thestruct['data'] = StructNew(); thestruct['data']['id'] = getContacts.id; thestruct['data']['company'] = Replace(getContacts.company, chr(13) & chr(10), ' ','ALL'); thestruct['data']['title'] = getContacts.title; thestruct['data']['firstname'] = getContacts.firstName; thestruct['data']['lastname'] = getContacts.lastName; thestruct['data']['pic'] = Replace(getContacts.pic, '\', '','ALL'); thestruct['data']['email'] = Replace(getContacts.email, chr(13) & chr(10), ' ','ALL'); thestruct['data']['webpage'] = getContacts.webPage; thestruct['data']['imaddress'] = Replace(getContacts.imAddress, chr(13) & chr(10), ' ','ALL'); thestruct['data']['homephone'] = Replace(getContacts.homePhone, chr(13) & chr(10), ' ','ALL'); thestruct['data']['busphone'] = Replace(getContacts.busPhone, chr(13) & chr(10), ' ','ALL'); thestruct['data']['mobphone'] = Replace(getContacts.mobPhone, chr(13) & chr(10), ' ','ALL'); thestruct['data']['fax'] = Replace(getContacts.fax, chr(13) & chr(10), ' ','ALL'); thestruct['data']['mailto']= getContacts.mailto; thestruct['data']['baddress'] = getContacts.baddress; thestruct['data']['haddress'] = getContacts.haddress; thestruct['data']['mailingaddress'] = Replace(getContacts.mailingAddress, chr(13) & chr(10), ' ','ALL'); return serializejson(thestruct); rtnStruct.success = true; rtnStruct.data = getContacts; return serializeJSON(rtnStruct,false); SELECT id as RECORDID, first as FIRSTNAME FROM contactform where id = #arguments.RECORDID# //format the query for ExtJS json compObj = CreateObject("component", "cf_extjs"); extjs_jsonVar = compObj.qry2json(q,0,100); SELECT id as RECORDID, last as LASTNAME FROM contactform order by recordid //format the query for ExtJS json compObj = CreateObject("component", "cf_extjs"); extjs_jsonVar = compObj.qry2json(q,0,100);
Here is the code for the file upload:Update contactform Set firstName = ,lastName = '#form.lastname#' ,company = '#form.company#' ,title = '#form.title#' ,pic = '#form.pic#' ,email = '#form.email#' ,webpage = '#form.webpage#' ,imaddress = '#form.imaddress#' ,homephone = '#form.homephone#' ,busphone = '#form.busphone#' ,mobphone = '#form.mobphone#' ,fax = '#form.fax#' ,baddress = '#form.baddress#' ,mailingaddress = '#form.mailingaddress#' ,mailto = '#form.mailto#' Where id = #form.id# INSERT INTO contactform ( firstName , lastName , company , title ,pic , email , webpage , imaddress , homephone , busphone , mobphone , fax , baddress , mailingaddress , mailto ) VALUES ( , , , , , , , , , , , , , , ) #result#
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<cfsetting showdebugoutput="no">
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css">
<style type="text/css">
</style>
<script src="adapter/ext/ext-base.js" type="text/javascript"></script>
<script src="js/ext-all.js" type="text/javascript"></script>
<script type="text/javascript">
// Uploading a file
/* Ext.BLANK_IMAGE_URL = 'images/s.gif';
*/
var picBox = {
columnWidth: '100 px',
bodyStyle: 'padding:10px',
items: [
{ xtype: 'box',
autoEl: { tag: 'div',
html: '<img id="pic" src="' + Ext.BLANK_IMAGE_URL + '" class="img-contact" width="100" heighth="100"/>'
}
}
]
}
var picFiles = {
columnWidth: .65,
layout: 'form',
labelAlign:'top',
items: [
{
xtype: 'textfield',
fieldLabel: 'Current',
labelSeparator: '',
name: 'currPic',
id:'currPic',
readOnly: true,
disabled:true,
anchor:'100%'
},
{
xtype: 'textfield',
fieldLabel: 'New (JPG or PNG only)',
labelSeparator: '',
name: 'newPic',
id:'newPic',
style:'width: 300px',
inputType: 'file',
allowBlank: false
}
]
}
function validateFileExtension(fileName) {
var exp = /^.*\.(jpg|JPG|png|PNG)$/;
return exp.test(fileName);
}
Ext.onReady(function() {
/*Ext.BLANK_IMAGE_URL = 'images/s.gif';*/
var pictUploadForm = new Ext.FormPanel({
frame: true,
title: 'Change Picture',
bodyStyle: 'padding:5px',
width: 420,
layout: 'column',
/*url: 'contact-picture.aspx',*/
/*url: 'fileuploadtest.cfm',*/
/*url: 'fileuploadaction2.cfm',*/
url: 'fileuploadaction2.cfc?method=uploadPic',
method: 'POST',
fileUpload: true,
enctype: 'multipart/form-data',
items: [picBox, picFiles],
buttons: [{
text: 'Upload Picture',
handler: function() {
var theForm = pictUploadForm.getForm();
if (!theForm.isValid()) {
Ext.MessageBox.alert('Change Picture', 'Please select a picture');
return;
}
if (!validateFileExtension(Ext.getDom('newPic').value)) {
Ext.MessageBox.alert('Change Picture', 'Only JPG or PNG, please.');
return;
}
theForm.submit({
params: { act: 'setPicture', id: 'contact1' }
,
waitMsg: 'Uploading picture' ,
success: function(form,action){
Ext.MessageBox.alert('Success', 'Processed file on the server');
},
failure: function(form,action){
Ext.MessageBox.alert('Failure', 'did not process the upload');
}
})
/*pictUploadForm.Reset();*/
}
}, {
text: 'Cancel'
},
{
text: 'Reset'
,id: 'reset-button'
,type: 'reset'
}
]
});
pictUploadForm.on({
/*alert('the load has been completed');*/
actioncomplete: function(form, action) {
var result = Ext.decode(action.response.responseText);
if (action.type == 'load') {
Ext.MessageBox.alert('pictUploadForm.actioncomplete', 'load has been completed');
var pic = action.result.data;
Ext.getDom('pic').src = pic.file;
Ext.getCmp('currPic').setValue(pic.file);
if (action.failureType == "connect") {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form load failed. Could not connect to server.');
} else {
if (action.response.responseText != '') {
var result = Ext.decode(action.response.responseText);
if(result && result.msg) {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form load failed with error: ' + action.result.msg);
} else {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form load failed with unknown error (possibly missing the "success" field in the json). Action type='+action.type+', failure type='+action.failureType);
}
} else {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form load returned an empty string instead of json');
}
}
}
if (action.type == 'submit') {
Ext.MessageBox.alert('pictUploadForm.actioncomplete', 'submit has been completed');
var pic = action.result.data;
Ext.getDom('pic').src = pic.file;
Ext.getDom('currPic').src = pic.file;
/* Ext.getCmp('currPic').setValue(pic.file);*/
Ext.getDom('newPic').value = '';
if (action.failureType == "connect") {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form submit failed. Could not connect to server.');
} else
if (action.failureType == "server") {
// These arent "errors" as such, they are validation issues trapped by the server script and passed back for the user to correct
} else {
var result = Ext.decode(action.response.responseText);
if(result && result.msg) {
Ext.MessageBox.alert('pictUploadForm.actionfailed error', 'Form submit failed with error: ' + action.result.msg);
} else {
Ext.MessageBox.alert('actionfailed Error', 'Form submit returned unknown error. Action type='+action.type+', failure type='+action.failureType);
Ext.MessageBox.alert('pictUploadForm.actioncomplete', + action.response.responseText);
}
}
}
}
});
/*simple.render(document.body);*/
/* Ext.get('reset-button').removeAllListeners();*/
pictUploadForm.render(document.body);
Ext.getDom('newPic').size = 10;
pictUploadForm.getForm().load({ params: { act: 'getPicture', id: 'contact1' }
/*,
waitMsg: 'Loading'*/
});
});
</script>
</head>
<body style="padding: 20px">
</body>
</html>
Last but not least, here if the CFC for the file upload action.
var stcReturn = structnew(); stcReturn['success'] = true; return stcReturn; var stcReturn = structnew(); stcReturn['success'] = true; stcMsg['msg'] ='successfully uploaded file'; stcReturn['data'] = structnew(); stcReturn['data']['Contact Id'] = 'contact1'; stcReturn['data']['file2'] = "img/" & cffile.clientfile; stcReturn['data']['file'] = cffile.clientfile; return stcReturn;
Well it was quite exhausting just uploading these files and making sure the syntax highlighter was working. I will come back later to explain it all. I hope you find it useful.
You might ask me what was hard about this project? Here is my list:- I had difficulty connecting the search form to the contact form because the contact form did not have an ID
- The radio buttons were quite difficult for me to populate and I spent a great deal of time getting them to work
- It took me days to get the image to display properly on the form
- I struggled and struggled with getting the window to show
- I struggled even further populated the fields in the window form
- The file upload took forever to get working and I thought it would be the impossible dream, you have to get the upload form to work and the updload action cfc
- Populating the main picture was challenging
- Closing the window was impossible, window hide did not work but the close button did, I gave up and just let the close button drive the window close
ColdFusion and the ExtJS Editor Grid - One row at a time.
We will start out with the cfm file.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Ext JS Grid with ColdFusion</title>
<cfsetting showdebugoutput="false">
<div style="text-align:center">
<!-- Arquivos utilizados pelo jQuery lightBox plugin -->
<link rel="stylesheet" href="css/lightbox.css" type="text/css" media="screen" />
<script src="js/prototype.js" type="text/javascript"></script>
<script src="js/scriptaculous.js?load=effects,builder" type="text/javascript"></script>
<script src="js/lightbox.js" type="text/javascript"></script>
<style type="text/css">
body{ color: #333; font: 13px 'Lucida Grande', Verdana, sans-serif; }
</style>
<script type="text/javascript">
function cover_image(val){
return '<img src=images/'+val+'>';
}
</script>
<script type="text/javascript">
function renderIMG(val) {
return ('<a href="images/{val}" rel="lightbox" title="my caption" alt="{val}"> <img src="images/{val}" width="100" height="80" border="0" alt=""> </a>');}
</script>
<script>
Ext.ux.Lightbox.register('a[rel^=lightbox]');
Ext.ux.Lightbox.register('a.lb-flower', true); // true to show them as a set
</script>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css">
<link rel="stylesheet" type="text/css" href="ux/css/RowEditor.css" />
<script src="extlightbox.js"></script>
<script src="adapter/ext/ext-base.js"></script>
<script src="js/ext-all.js"></script>
<script src="editgridItemTableMovie4.js"></script>
<script type="text/javascript" src="ux/RowEditor.js"></script>
<script type="text/javascript" src="examples/ux/CheckColumn.js"></script>
<style type="text/css">
body .x-panel {
margin-bottom:20px;
}
.icon-grid {
background-image:url(../shared/icons/fam/grid.png) !important;
}
#button-grid .x-panel-body {
border:1px solid #99bbe8;
border-top:0 none;
}
.add {
background-image:url(../shared/icons/fam/add.gif) !important;
}
.option {
background-image:url(../shared/icons/fam/plugin.gif) !important;
}
.remove {
background-image:url(../shared/icons/fam/delete.gif) !important;
}
.save {
background-image:url(../shared/icons/save.gif) !important;
}
</style>
<style type="text/css">
/*.x-btn-td {padding-left:200;}*/
/*.x-btn button {margin-left:100;}*/
.x-btn-mc {text-align:center;}
.x-btn-mc {div style=text-align:center;}
.x-btn-mc {margin-left:100; }
.ext-gen28 {margin-left:100px; position:relative;}
</style>
<style type="text/css">
.x-grid3-col{text-align:right;}
.x-grid3-col-0{text-align:right; white-space:normal !important;}
.x-grid3-col-1{text-align:left; white-space:normal !important;}
.x-grid3-col-2{text-align:left; white-space:normal !important;}
.x-grid3-col-3{text-align:left; white-space:normal !important;}
.x-grid3-col-4{text-align:right; white-space:normal !important;}
.x-grid3-header-offset {width: auto;}
.x-grid3-row td {white-space:normal !important;}
.x-grid3-hd {white-space:normal !important;}
.x-grid3-header-offset {width: auto;}
.x-grid3-hd-0-0 {white-space:normal !important;}
.x-grid3-hd-0-1 {white-space:normal !important;}
.x-grid3-hd-0-2 {white-space:normal !important;}
.x-grid3-hd-0-3 {white-space:normal !important;}
.x-grid3-td-1{white-space:normal !important;}
.x-grid3-td-2 {white-space:normal !important;}
.x-grid3-td-3 {white-space:normal !important;}
.x-toolbar-td {align: right !important;}
.x-toolbar-left {align:center;}
.x-toolbar-cell {align: center;}
.x-btn {align: center;}
.x-btn-noicon {align: center;}
.x-btn-icon-small-left {align: center;}
.ext-gen26 {align: center;}
.x-panel-bl {align: center;}
.x-btn-t1 {align: center;}
.x-btn-ml {align: center;}
.x-btn-mc {align: center;}
.x-toolbar-left {text-align:center;}
.x-btn-small {align: center;}
<!--.x-btn-small style="align:center;"}-->
.x-btn-left {align: center;}
<!---.x-grid3-td-4 {white-space:normal !important;}
.x-grid3-td-5 {white-space:normal !important;}--->
</style>
</head>
<!--<script type="application/x-javascript">
window.onload=function(){initlightbox');}
</script>-->
<body>
<div class="thumbnail">
<div id="editgrid"></div>
</div>
</body>
</html>
Here is the javascript code for the editor:
Ext.onReady(function(){
var users = 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',type:'string'},
{name:'TAGLINE',type:'string'},
{name:'PRICE',type:'float'},
{name:'AVAILABLE',type:'boolean'}
])
function cover_image(val) {
return '
';
}
//The Proxy object will specify URLs to call for different CRUD operations
var proxy = new Ext.data.HttpProxy({
method:'POST',
api:{
read: 'movies.cfc?method=getMovies',//Our URL for reading the grid data
create: 'movies.cfc?method=addMovies',//For Adding a new User (future implementation)
update: 'movies.cfc?method=editMovieRow'//When a User is updated
},
//the function writeSuccess will be called when the Store finishes a succesful write (add/edit) to the server
listeners: {
write: {
fn: writeSuccess
}
}
})
//Our JSON Writer, configuration used to updating/adding of records
var writer = new Ext.data.JsonWriter({
returnJson: true,
//We will only send the changed fields
writeAllFields: true
})
// create the Data Store
var store = new Ext.data.JsonStore({
totalProperty:'DATASET',//This is how many total records are there in the set.
root:'ROWS',//The Root of the data.
proxy:proxy,
id:'ID',
successProperty:'SUCCESS',//What return string data will be set to true/false to let Ext know that server call was succesful or not
autoSave:false,//we will use event listeners to call save manually
remoteSort:true,//We will sort server side
//Base Params are parameters passed in during every call
baseParams:{
returnFormat: 'JSON',
start: '0',
limit: '50'
},
//We define the JSON Reader for the data. We also need to set the totalProperty, root and idProperty for the dataset here.
reader: new Ext.data.JsonReader({
totalProperty:'DATASET',
root:'ROWS',
idProperty:'ID'
},users
),
//Fields read in
fields: [
'ID', 'COVERTHUMB', 'TITLE', 'DIRECTOR','RELEASED', 'RUNTIME', 'GENRE', 'TAGLINE', 'PRICE', 'AVAILABLE'
],
writer:writer,
//Any Store execeptions will be caught here
listeners: {
exception : function(proxy,type,action,options,res,rs) {
alert(type);
}
}
});
// 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,
/*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: 100,
hidden: false,
sortable: true,
editor: new varForm.TextField({
allowBlank: false
})
},
{
header: "Released",
dataIndex: 'RELEASED',
width: 80,
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: 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;
}
}
})
// var rowEditor = new Ext.ux.grid.RowEditor({
// saveText: 'Update',
// listeners: {
// afteredit: syncStore
// }
// });
// create the editor grid
var grid = new Ext.grid.EditorGridPanel({
width:1100,
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'
/*
,
plugins: [new Ext.ux.grid.RowEditor()
]
*/
,
// 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: {
rowselect: function(smObj, rowIndex, record) {
selRecordStore = record;
}
}
}),
// inline toolbars
tbar: new Ext.Toolbar({
items : [
{
xtype:'tbtext',
text:'Welcome. Click here to edit .',
itemId:'tbartext',
id:'tbartext'
},
{
text:'Add A New Store Item',
tooltip:'Add a new row',
icon:'icons/fam/add.gif',
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 users({
/*var user1 = new users({*/
ID: INSERT_ID,
COVERTHUMB: 'blankmovie.jpg',
TITLE: 'Great Movie',
DIRECTOR: 'Movie Director',
RUNTIME: 2,
RELEASED: '03/15/2011',
GENRE: 1,
TAGLINE: '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');
}
});
store.insert(0, Users);
grid.getView().refresh();
grid.getSelectionModel().selectRow(0);
}
}, '-', {
text:'Options',
tooltip:'Blah blah blah blaht',
icon:'icons/fam/connect.gif'
},'-',{
xtype:'tbbutton',
text:'Remove Movie',
tooltip:'Remove the selected movie',
icon:'icons/fam/delete.gif',
handler: function() {
var sm = grid.getSelectionModel(),
sel = sm.getSelected();
if (sm.hasSelection()){
Ext.Msg.show({
/*Download from Wow! eBook
Editor Grids
[ 136 ]*/
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: {
method: 'removeMovies',
action: 'destroy',
/*id: e.record.ID*/
/*id: e.data.ID*/
id: sel.data.ID
},
success: function(resp,opt) {
grid.getStore().remove(sel);
},
failure: function(resp,opt) {
Ext.Msg.alert('Error',
'Unable to delete movie');
}
});
}
}
});
};
}
/*}*/
/* // Place a reference in the GridPanel
ref: '../removeButton',
disabled: true*/
}
]
}
),
listeners: {
afteredit: { //This listener event function will be called when user finishes editing data box and tabs/enters/clicks out.
fn: editStore
}
}
});
function editStore(e)
{
//Call our store's save event that will fire things off'
this.store.save();
}
function onAdd(e)
{
var u = new this.store.recordType({
ItemID: '',
ItemIdentifier: 'VP1',
ItemDescription: 'My Description',
ItemTitle: 'President'
});
this.stopEditing();
this.store.insert(0, u);
this.startEditing(0, 1);
}
function syncStore(rowEditor, changes, r, rowIndex) {
store.save();
}
/*write(dataproxy,action,data,response,record,options)*/
function writeSuccess(store, action, data, response, rs, options )
/* function writeSuccess(store, action, result, res, rs, resp, opt)*/
{
//Get Toolbar Object
var tbar = grid.getTopToolbar();
//Remove current text
tbar.remove("tbartext");
tbar.addText({
xtype:'tbtext',
text:'Success !! ' + action,
itemId:'tbartext'
}
);
//Redo layout to render new toolbar text
tbar.doLayout();
}
//Default Sort set for the grid load call
store.setDefaultSort('ID','ASC');
// trigger the data store load
store.load();
});
And finally here is the CFC. Take a look at the method to editMovieRow as it is designed to handle one row or an array. If you included all the javascript files mentioned in the html, this should all work for you. Oh, make sure you are running Ext JS 3.2. If you do not have those files you can get them from here: https://code.google.com/p/extjs-public/source/browse/tags/extjs-3.2.1?r=117#extjs-3.2.1
SELECT ID as id, coverthumb, title, director, runtime, released, genre, tagline, price, available FROM movies SELECT ID, COVERTHUMB, TITLE, DIRECTOR, URL, RUNTIME, RELEASED, PRICE, AVAILABLE FILMED_IN, GENRE, TAGLINE, PRICE, AVAILABLE, DESCRIPTION FROM movies Order by GENRE // 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# UPDATE movies SET TITLE = '#stcData[x].TITLE#', DIRECTOR = '#stcData[x].DIRECTOR#', TAGLINE = '#stcData[x].TAGLINE#', GENRE = 1, GENRE = '#stcData[x].GENRE#', RELEASED = '11/15/2010', RELEASED = '#DateFormat(left(stcData[x].RELEASED,10),"mm/dd/yyyy")#', WHERE ID = #stcData[x].ID# RUNTIME = 2 RUNTIME = #stcData[x].RUNTIME# UPDATE movies SET COVERTHUMB = '#stcData.COVERTHUMB#', TITLE = '#stcData.TITLE#', DIRECTOR = '#stcData.DIRECTOR#', GENRE = #stcData.GENRE#, GENRE = 1, GENRE = 1, TAGLINE = '#stcData.TAGLINE#', WHERE ID = #stcData.id# RELEASED = '10/27/2012' RELEASED = '#stcData.RELEASED#' 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'
Enjoy!

