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.