Thursday, July 4, 2013

Adding a Grid into the West Navigation Panel of a Complex ExtJS Layout

I am experimenting with the Navigation Panel of the complex layout. First I loaded a tree into the panel (please see my previous post). Then I said, well let's add a grid too. How to do it? First you define your model and your store:
 Ext.define('Restaurant', {
        extend: 'Ext.data.Model',
        fields: ['name', 'cuisine']
    });

    var Restaurants = Ext.create('Ext.data.Store', {
        storeId: 'restaraunts',
        model: 'Restaurant',
        sorters: ['cuisine','name'],
        groupField: 'cuisine',
        data: [{
            name: 'Cheesecake Factory',
            cuisine: 'American'
        },{
            name: 'University Cafe',
            cuisine: 'American'
        },{
            name: 'Slider Bar',
            cuisine: 'American'
        },{
            name: 'Shokolaat',
            cuisine: 'American'
        },{
            name: 'Gordon Biersch',
            cuisine: 'American'
        },{
            name: 'Crepevine',
            cuisine: 'American'
        },{
            name: 'Creamery',
            cuisine: 'American'
        },{
            name: 'Old Pro',
            cuisine: 'American'
        },{
            name: 'Nola\'s',
            cuisine: 'Cajun'
        },{
            name: 'House of Bagels',
            cuisine: 'Bagels'
        },{
            name: 'The Prolific Oven',
            cuisine: 'Sandwiches'
        },{
            name: 'La Strada',
            cuisine: 'Italian'
        },{
            name: 'Buca di Beppo',
            cuisine: 'Italian'
        },{
            name: 'Pasta?',
            cuisine: 'Italian'
        },{
            name: 'Madame Tam',
            cuisine: 'Asian'
        },{
            name: 'Sprout Cafe',
            cuisine: 'Salad'
        },{
            name: 'Pluto\'s',
            cuisine: 'Salad'
        },{
            name: 'Junoon',
            cuisine: 'Indian'
        },{
            name: 'Bistro Maxine',
            cuisine: 'French'
        },{
            name: 'Three Seasons',
            cuisine: 'Vietnamese'
        },{
            name: 'Sancho\'s Taquira',
            cuisine: 'Mexican'
        },{
            name: 'Reposado',
            cuisine: 'Mexican'
        },{
            name: 'Siam Royal',
            cuisine: 'Thai'
        },{
            name: 'Krung Siam',
            cuisine: 'Thai'
        },{
            name: 'Thaiphoon',
            cuisine: 'Thai'
        },{
            name: 'Tamarine',
            cuisine: 'Vietnamese'
        },{
            name: 'Joya',
            cuisine: 'Tapas'
        },{
            name: 'Jing Jing',
            cuisine: 'Chinese'
        },{
            name: 'Patxi\'s Pizza',
            cuisine: 'Pizza'
        },{
            name: 'Evvia Estiatorio',
            cuisine: 'Mediterranean'
        },{
            name: 'Cafe 220',
            cuisine: 'Mediterranean'
        },{
            name: 'Cafe Renaissance',
            cuisine: 'Mediterranean'
        },{
            name: 'Kan Zeman',
            cuisine: 'Mediterranean'
        },{
            name: 'Gyros-Gyros',
            cuisine: 'Mediterranean'
        },{
            name: 'Mango Caribbean Cafe',
            cuisine: 'Caribbean'
        },{
            name: 'Coconuts Caribbean Restaurant & Bar',
            cuisine: 'Caribbean'
        },{
            name: 'Rose & Crown',
            cuisine: 'English'
        },{
            name: 'Baklava',
            cuisine: 'Mediterranean'
        },{
            name: 'Mandarin Gourmet',
            cuisine: 'Chinese'
        },{
            name: 'Bangkok Cuisine',
            cuisine: 'Thai'
        },{
            name: 'Darbar Indian Cuisine',
            cuisine: 'Indian'
        },{
            name: 'Mantra',
            cuisine: 'Indian'
        },{
            name: 'Janta',
            cuisine: 'Indian'
        },{
            name: 'Hyderabad House',
            cuisine: 'Indian'
        },{
            name: 'Starbucks',
            cuisine: 'Coffee'
        },{
            name: 'Peet\'s Coffee',
            cuisine: 'Coffee'
        },{
            name: 'Coupa Cafe',
            cuisine: 'Coffee'
        },{
            name: 'Lytton Coffee Company',
            cuisine: 'Coffee'
        },{
            name: 'Il Fornaio',
            cuisine: 'Italian'
        },{
            name: 'Lavanda',
            cuisine: 'Mediterranean'
        },{
            name: 'MacArthur Park',
            cuisine: 'American'
        },{
            name: 'St Michael\'s Alley',
            cuisine: 'Californian'
        },{
            name: 'Cafe Renzo',
            cuisine: 'Italian'
        },{
            name: 'Osteria',
            cuisine: 'Italian'
        },{
            name: 'Vero',
            cuisine: 'Italian'
        },{
            name: 'Cafe Renzo',
            cuisine: 'Italian'
        },{
            name: 'Miyake',
            cuisine: 'Sushi'
        },{
            name: 'Sushi Tomo',
            cuisine: 'Sushi'
        },{
            name: 'Kanpai',
            cuisine: 'Sushi'
        },{
            name: 'Pizza My Heart',
            cuisine: 'Pizza'
        },{
            name: 'New York Pizza',
            cuisine: 'Pizza'
        },{
            name: 'California Pizza Kitchen',
            cuisine: 'Pizza'
        },{
            name: 'Round Table',
            cuisine: 'Pizza'
        },{
            name: 'Loving Hut',
            cuisine: 'Vegan'
        },{
            name: 'Garden Fresh',
            cuisine: 'Vegan'
        },{
            name: 'Cafe Epi',
            cuisine: 'French'
        },{
            name: 'Tai Pan',
            cuisine: 'Chinese'
        }]
    });
Next you define your grid. I am using a grouping grid.
  var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
        groupHeaderTpl: 'Cuisine: {name} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
    });

    var mygrid = Ext.create('Ext.grid.Panel', {
       /* renderTo: Ext.getBody(),*/
  /*renderTo: Ext.getBody(west),*/
   region:'west',
        collapsible: true,
        iconCls: 'icon-grid',
        frame: true,
        store: Restaurants,
        width: 600,
        height: 400,
        title: 'Restaurants',
        features: [groupingFeature],
        columns: [{
            text: 'Name',
            flex: 1,
            dataIndex: 'name'
        },{
            text: 'Cuisine',
            flex: 1,
            dataIndex: 'cuisine'
        }],
        fbar  : ['->', {
            text:'Clear Grouping',
            iconCls: 'icon-clear-group',
            handler : function(){
                groupingFeature.disable();
            }
        }]
    });
Then load it into the west region of your layout:
 {
    region: 'west', // a center region is ALWAYS required for border layout
                stateId: 'navigation-panel',
                id: 'west-panel', // see Ext.getCmp() below
                title: 'West',
                split: true,
                width: 200,
                minWidth: 175,
                maxWidth: 400,
                collapsible: true,
                animCollapse: true,
                margins: '0 0 0 5',
                layout: 'accordion',
                items: [{
                    contentEl: 'west',
                    title: 'Navigation',
                    iconCls: 'nav',
     // see the HEAD section for style used
                }, {
                    title: 'Tree',
                    html: '

Some settings in here.

', items: treePanel, iconCls: 'settings' }, { title: 'Grid', html: '

The grid goes here.

', items: mygrid, iconCls: 'settings' }, { title: 'Settings', html: '

Some settings in here.

', iconCls: 'settings' }, { title: 'Information', html: '

Some info in here.

', iconCls: 'info'
And there you have it!

Loading a Tree into the West Navigational Panel of a Complex ExtJS 4.0 Layout

I really like the complex layout example in ExtJS. I was looking at the accordian panel and thought it would be great to load a tree there. So I did. How? First you must define a store for your tree:
var store = Ext.create('Ext.data.TreeStore', {
        root: {
            expanded: true
        },
        proxy: {
            type: 'ajax',
            url: 'tree-data.json'
        }
    });
Then you must define your treePanel:
 var treePanel = Ext.create('Ext.tree.Panel', {
        id: 'tree-panel',
        title: 'Tree Layout',
        region:'west',
        split: true,
        height: 360,
        minSize: 150,
        rootVisible: false,
        autoScroll: true,
        store: store
    });
Then insert your tree into the west panel of the example:
{
    region: 'west', // a center region is ALWAYS required for border layout
                stateId: 'navigation-panel',
                id: 'west-panel', // see Ext.getCmp() below
                title: 'West',
                split: true,
                width: 200,
                minWidth: 175,
                maxWidth: 400,
                collapsible: true,
                animCollapse: true,
                margins: '0 0 0 5',
                layout: 'accordion',
                items: [{
                    contentEl: 'west',
                    title: 'Navigation',
                    iconCls: 'nav',
     // see the HEAD section for style used
                }, {
                    title: 'Tree',
                    html: '

Some settings in here.

', items: treePanel, iconCls: 'settings' }, { }, { title: 'Settings', html: '

Some settings in here.

', iconCls: 'settings' }, { title: 'Information', html: '

Some info in here.

', iconCls: 'info'
You insert the code above right where the west region is defined. Now please note you will need to copy the tree-data.json from the layout-browser folder in examples to the layout folder. And you have a tree in the accordion navigation panel of your complex layout!

Sunday, June 16, 2013

Populating an ExtJS Tree using ColdFusion

You do not see many ColdFusion examples populating an ExtJS tree. The big challenge is providing the data to the tree. How do yo do it? First of all, extjs is expecting an array of structures. How do you build it? First you define your array:

Then as you loop through your query, your build and populate a structure:








Then add the structure to the array. It should look like this:

" showing here because the code is not displaying properly."



Finally return the array back to the calling program:
 

  
This is done within a CFC defined as such:
cffunction name="getDepartmentsForExtTree" access="remote" returnformat="JSON">
That in a nutshell is how to provide data for an ExtJS tree.

Sunday, April 7, 2013

Using the Ext.Msg.Prompt to Add a Row to Your Reference Table

Suppose you have a listing of movies and you have a combo box where you can choose the genre to which that movie belongs. If you decide to add a new genre you do not want to have to log into the database and add a new genre record into your genre database. Why - because you are a developer and you are lazy right? And if someone else is doing the administration you do not want them to be in a position of needing to contact you to do this. Unless you can charge them for it. If that is the case, do not read this article.:)

Almost anytime you have a combo box you are pointing to a reference table where you may want to add entries. The only exception to this would be a states database because it is unlikely we will add any new states. But for combo boxes that list reference information that is subject to change, it would be great to add the "genre", "category" or whatever on the fly.

How do you do this?

Well first, add a row to your table through your database editor. Here just insert the genre and do not define the value of the id. If it works great. If not, it is because the id is not defined as autoincrement. I am assuming every table you create has a column called id or something similar which is defined as integer so you can reference this table by that identifier or id. As a side note, I recommend that every table also include a created_timestamp, created_userid, modified_timestamp and modified_userid. This is just my recommendation after 20 plus years of experience. I will discuss why in a later blog post.

The goal is to browse through the list of entries in a combo box, have the first entry defined as new genre, new category, or new whatever you are listing in your combo box. When you select this new item, your code will see it is the new entry (from the hardcoded id for the new item) provide a message prompt to enter a new genre, category, etc., capture that message text, and fire off an ajax request to insert that value into your reference table, then reload your store so when you click on the combo again your entry is there. Life is Wonderful.

What steps are involved?

1. Your application should be using a remote store rather than a simple store so you are querying your database.

2. Your application must be set up to provide the message prompt and react to that prompt.

3. You must define a cfc to insert a record into your database. But first, you must determine whether you set your table to auto increment or not. If it is not defined to auto increment then you must first query your database, select max(id) from your table, save the max id and increment it by one so you can then insert this new id into the table along with your value.

If you have auto increment defined, then you can insert the value right into the table without getting the maxid first. In a busy environment, you always want auto increment turned on. Otherwise, someone else may be inserting at the same time you are, they have already derived the same max id as you, when you go to insert your row you may get a failure as that key already exists because the other transaction inserted the record before you did. So, I recommend turning auto increment on.

Auto increment is a great term but in SqlServer it is actually called the Identity Specification. If you go into SqlServer, check your index, verify Identity Specification is set to YES, IS IDENTIFY = YES, Identity Increment is set to 1 or whatever step you prefer and Identity Seed (starting value) is set to your desired number or 1.

Here is some great information on auto incrementing indexes and it discusses how this is done with SqlServer, Mysql, Access, Oracle, etc. www.w3schools.com/sql/sql_autoincrement.asp.

Let's take a look at the combo box defintion for the form. First we add a listener to the combo box. Then we check the index of the item selected is equal to the first item. Extjs starts at base 0 and the very first record we have stored in our database is defined as New Genre. If this is the item selected we provide a message prompt asking what new genre should be added.

If the ok button is selected, we fire off an Ext.Ajax.request to execute an cfc to insert the row. The new genre is entered as text in the message prompt, we set the data variable equal to text, and then pass the data variable as a param to the cfc. We then check the response and if we were successful we send a message that the insert was successful otherwise we send a message that the update failed.

{
    xtype: 'combo',
    name: 'genre',
    fieldLabel: 'Genre',
    mode: 'local',
    store: genres,
    emptyText: 'Select a genre...',
    displayField:'GENER_NAME',
                valueField: 'ID',
    width: 120,
    triggerAction: 'all',
    listeners: {
      select: function(f,r,i){
      if (i == 0){
      Ext.Msg.prompt('New Genre','Name', function(btn, text){
     if (btn == 'ok'){
     var data = text;
     Ext.Msg.alert('you entered', data);
     Ext.Ajax.request({
       url: 'genres3.cfc?method=addGenres',
       params: {genre: data},
        success: function(resp,opt) {
         genres.load();
                           Ext.Msg.alert(   'Category was added');
                        },
                        failure: function(resp,opt) {
                           Ext.Msg.alert('Category add failed');
                        }
     //}
     });
     }
      }, this, false, '');
      ;
      }
      }
    }
   
    
   },

Here is the addgenres function inside the cfc. Note that I did not need to select max id prior to the insert as the database has auto increment set to 1.

 
        
        
        
  
        

 
            
                
                INSERT 
                INTO genres
                (GENER_NAME)
                Values
                 ('#arguments.genre#')
                  
                          
            
         
            
                
            
        
        
         
        
              
        
        
 

This whole process was made easier by searching through the ExtJS examples, examples shown in Learning ExtJS 3.0 and 3.2 by Shea Frederick and examples in the ExtJS Cookbook by Jorge Ramon. If you do not own them you should go to PacktPub and buy them. They will provide many examples that show you how to do almost anything you want to do with ExtJS 3 series.

Tuesday, April 2, 2013

Linked Grid and Form Using Full CRUD Functionality

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
I also Added the UsersExt.cfc to execute the queries to read, edit, add and remove the data rows.

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
Here is the code:



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.

And finally we create the CFC. Here is the code for the ExtUsers CFC:


 
  
  
  
  
  
  
  
  
  
    
  
          
    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)
Now all you need to do is go into firefox, type in http://127.0.0.1:8500/ext-3.2.0/examples/writer/writer.cfm (be sure to change ext-3.2.0 to whatever you have this file named in your root directory) and it should work. If not, check firebug. Happy coding!

Sunday, March 24, 2013

Debugging Your ColdFusion Code When Using ExtJS

It can be a chore to determine what is going wrong with your application when using ExtJS.

For starters, you are expecting the nice debugging errors you get out of ColdFusion. You do not get that.

Instead you get a blank screen and so you know whatever you changed no longer works.

So what do you do?

First, make sure you are testing with Firefox and have installed Firebug.

Then turn on Firebug.

Select console and select errors. This should tell you what the problem is and it will give you the line number that the bug relates to.

Usually I unsave my last group of changes and it works again! But, now you know what line of functionality to investigate.

The problem could be your call to your form submit.cfm or your cfc.

If you want to know what is happening with your calls go to the net tab. Select xhr and it will show you whether you made any calls to your cfc. If so, look at the post and see what is being sent to your application. Is this what you expected. If there is an error in your code and CF returns back error messages you will see them in the HTML tag.

You may need to dump some of your variables. You are not going to see the cfc page so you need to dump those variables to a page. This is how you do that:


 



I hope this provides some help. You are not alone. See my post about determining what to pass back to ExtJS. It may also provide some help.

Friday, March 22, 2013

How to format your html for syntax highlighting

This is mainly a post for myself. Paste your code into this page and it will convert your html. You can then copy it and post it into blogger. http://formatmysourcecode.blogspot.com/

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:





        
             
        
         
        
        
        
         
        
          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);
            
            
                
            
        
        

        
    
    
    
    
 
 
 
  
  
 
 
  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 
 
  
  
 
 
  
 
 
 
  
 
 
 
  
 
 
 
  
  
   
   
    
   
    
   
  
  
  
 
 
 
  
  
  
   
   
    
   
    
   
  
  
  
 
 
 
  
  
  
  
   
   
   
   
  
   
   
   
   
  
  
  
  
   
   
  
   
  
  
  
  
  
   
   
   
   
    
     
    
    
     
    
    
    
    
     
     
     
     
      
     
     
    
    
    
    
    
   
   
  
  
   
   
    
     
    
    
    
    
     
     
     
      
     
     
     
      
     
     
     
     
    
    
   
   
  
  
  
  
 
 
 
  
 




 
 
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   
  
  
  
   
  
  
  
   
  
  
  
   
  
  
  
   
   
   
    
    
    
    
     
     
      
     
      
      
       
       
       
       
        
       
        
       
      
       
      
     
    
    
    
   
    
   
  
  
  
   
   
   
    
   
    
   
    
   
   
   
   
   
   
   
    
     
    
     
    
   
   
   
   
   
    
    
    
    
    
    
     
    
    
     
     
    
    
     
     
     
     
      
     
     
      
      
      
        
      
        
      
      
      
      
      
       
      
      
      
      
      
      
       
      
      
      
       
        
       
        
        
         
        
       
        
        
        
         
          
          
         
        
       
      
     
     
     
    
    
     
    
    
     
    
    
    
     
    
   
   
   
   
    
   
    
   
  
  
  
   
  
 
 
 
 
 
  
   
   
  
  
   
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

  
  
   
   
  
  
   
  
  
  
   
  
  
  
   
  
  
  
   
   
    
    
     
    
     
    
   
   
   
  
  
  
   
   
   
    
    
     
    
     
    
   
   
   
  
  
  
   
   
   
   
    
    
    
    
   
    
    
    
    
   
   
   
   
    
    
   
    
   
   
   
   
   
    
    
    
    
     
      
     
     
      
     
     
     
     
      
      
      
      
       
      
      
     
     
     
     
     
    
    
   
   
    
    
     
      
     
     
     
     
      
      
      
       
      
      
      
       
      
      
      
      
     
     
    
    
   
   
   
   
  
  
  
   
  
 
 
 
  
  
  
  
  
  
  
  
  
     
  
  
  
  
  
  
  
  
   
    
   
   
   
    
   
   
   
   
  
   
  
   
   
  
  
  
  
   
    
    
     
    
   
    
    
     
      
       
       
        
       
      
     
    
    
    
    
     
      
       
       
        
       
      
     
    
   
  
   
    
    
     
    
   
    
    
    
    
    
     
     
      
     
    
     
     
      
     
    
    
    
    
     
      
      
       
      
     
    
   
  
   
    
    
     
    
   
    
    
     
    
   
    
    
     
    
   
  
   
   
    
   
  
   
    
     
    
    
    
     
    
   
  
   
    
    
     
    
   
    
    
     
    
   
    
    
     
    
   
  
  
  
   
  
   
  
    


And here is the code for the form submit:
 








 



 




 





 
    
    
       
          
                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#
Here is the code for the file upload:
<!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
Long story short, this took me several months to put together. I believe the rest of the community would have found it less daunting. I hope my example makes it even easier for you.