Source code snippets (examples)

This is a list of Alvin Alexander's source code snippets (simple source code examples).

A Sencha Touch tab panel example

The following code shows how I created a tab panel in a Sencha Touch application, with a few important parts highlighted:

Ext.define('RadioMobile.view.MainTabPanel', {
    extend: 'Ext.tab.Panel',

    xtype:  'mainTabPanel',
    alias:  'widget.mainTabPanel',

    requires: [
        'RadioMobile.view.TimeControlsPanel',
        'RadioMobile.view.StationsPanel',
        'RadioMobile.view.StreamsPanel',
        'RadioMobile.view.RecordingsPanel',
        'RadioMobile.view.PodcastsPanel'
    ],

    config: {
        fullscreen : true,
        tabBar: {
            docked: 'bottom',
            layout: {
                pack: 'center'
            }
        }
    },

    initialize: function() {
        this.callParent(arguments);
        // the main panels that will show up in the bottom toolbar
        this.add(Ext.create('RadioMobile.view.TimeControlsPanel'));
        this.add(Ext.create('RadioMobile.view.StationsPanel'));
        this.add(Ext.create('RadioMobile.view.StreamsPanel'));
        this.add(Ext.create('RadioMobile.view.RecordingsPanel'));
        this.add(Ext.create('RadioMobile.view.PodcastsPanel'));
        this.setActiveItem(0);
    }

});

This code results in the following mobile UI, in particular the "tabs" shown at the bottom of the image:

Sencha Touch tab panel example

My Sencha Touch utilities (JSON, error message, dump object, REST URLs)

The following code shows my current Sencha Touch utilities. Much of it comes from a Packt book I can’t remember the name of, but several of the JavaScript functions are my own:

Ext.define('VP.util.Util', {

    // most of this code is copied from a Packt book

    statics : {
        decodeJSON : function (text) {
            var result = Ext.JSON.decode(text, true);
            if (!result) {
                result = {};
                result.success = false;
                result.msg = text;
            }
            return result;
        },

        showErrorMsg: function (text) {
            // Ext.Msg.alert('Error!', text);
            Ext.Msg.show({
                title:'Error!',
                msg: text,
                icon: Ext.Msg.ERROR,
                buttons: Ext.Msg.OK
            });
        },

        dumpObject: function(obj) {
            var output, property;
            for (property in obj) {
                output += property + ': ' + obj[property] + '; ';
            }
            console.log(output);
        },

        // `method` is typically 'GET' or 'POST'
        callRestUrl: function(theUrl, theMethod) {
            Ext.Ajax.request({
                url: theUrl,
                method: theMethod,
                success: function(conn, response, options, eOpts) {
                    var result = VP.util.Util.decodeJSON(conn.responseText);
                    if (result.success) {
                        // ignore
                    } else {
                        VP.util.Util.showErrorMsg(result.msg);
                    }
                },
                failure: function(conn, response, options, eOpts) {
                    // TODO get the 'msg' from the json and display it
                    VP.util.Util.showErrorMsg(conn.responseText);
                }
            });
        }
    }
});

A Sencha Touch title bar (titlebar) example

This code shows how to add a title bar (titlebar) to a Sencha Touch panel:

Ext.define('RadioMobile.view.PodcastsPanel', {
    extend: 'Ext.Panel',
    alias: 'widget.podcastsPanel',

    config: {
        xtype: 'podcastsPanel',
        itemId: 'podcastsPanel',
        
        // for the tab config
        title: 'Podcasts',
        iconCls: 'team',

        scrollable: 'vertical',

        items: [
            {
                docked: 'top',
                xtype: 'titlebar',
                title: 'Podcasts'
            }
        ],
    },

});

As shown, the Panel title is “Podcasts”.

How to break out of an ExtJS Store 'each' loop function early

I learned today that you break out of a Sencha ExtJS Store each loop by returning false from your function. This code shows the technique:

// check to see if the task description is already in the store for this project
taskAlreadyExistsInProject: function(projectId, taskName) {
    var tasksStore = this.getTasksStore();
    var matchIsFound = false;
    tasksStore.each(function(record) {
        if ((record.data.description == taskName) && (record.data.projectId == projectId)) {
            matchIsFound = true;  // don't use 'me' or 'this' here
            return false;  // this breaks out of the 'each' loop
        }
    });
    return matchIsFound;
},

I don’t think you’re supposed to access the data object as I’ve done here, but the return false part, I know that’s correct.

If you needed to see how to break out of a Store each loop early, I hope this example has been helpful.

Sencha ExtJS alert message dialog

To display an alert message dialog in Sencha Ext JS (ExtJS), use code like the following:

Ext.Msg.alert('Dialog Title', 'Your body text goes here ...');

This displays a simple JavaScript-like message dialog.

Sencha ExtJS - detecting the Enter key on a textfield in a controller function

onStockFormKeyPress: function(textfield, event, options) {
    if(event.getKey() == event.ENTER) {
        Ext.Msg.alert('Keys','You pressed the Enter key');
    }
}

This function is called when the keypress event is handled in the init function of my controller class:

init: function(application) {

    // add the components and events we listen to
    this.control({

        'stockList': {
            render: this.onRender
        },

        'stockList button#delete': {
            click: this.onDeleteStockButtonClicked
        },

        // 'companyName' refers to the itemId
        'stockform textfield#companyName': { //handle Enter on the companyName textfield
            keypress: this.onStockFormKeyPress
        },

    });

    if (!Ext.getStore('Stocks')) {
        Ext.create('Finance.store.Stocks');
    }    
},

Sencha ExtJS - How to dynamically create a form textfield (Ext.form.field.Text)

Sencha ExtJS - How to dynamically create a form textfield (Ext.form.field.Text)

Without much introduction, here’s a large block of Sencha ExtJS controller code. The code needs to be cleaned up, but at the moment it shows:

  • How to dynamically create a Sencha ExtJS form textfield
  • How to dynamically create a Sencha ExtJS form checkboxgroup
  • How to dynamically add checkboxes to the checkboxgroup
  • How to use Ext.Ajax.request POST and GET methods with JSON
  • JSON encoding and decoding
  • Much more, including how to strip blank spaces from a string, capitalize words

Here’s the source code:

Ext.define('Focus.controller.Projects', {
    extend: 'Ext.app.Controller',

    requires: [
        'VP.util.Utils'
    ],

    stores: [
        'Projects',
        'Tasks'
    ],

    views: [
        'MainTabPanel',
        'TaskListPanel'
    ],

    refs: [
        {
            ref: 'mainTabPanel',
            selector: 'mainTabPanel'
        }
    ],

    //
    // TODO this code needs a lot of cleanup/refactoring
    //

    // note: this is called before the user logs in (currently)
    init: function(application) {
        this.control({
            // lookup the 'My Stocks' button on the menu and add a click handler.
            // the name 'basic-panels' is the xtype declared in view.menu.Menu.
            // "basic-panels button#myStocks": {
            //     click: this.onMyStocksButtonClick
            // },
            // the MainTabPanel is rendered when the MainViewport is rendered
            // by the Login controller.
            "mainTabPanel": {
                render: this.onMainTabPanelRender,
                afterrender: this.onMainTabPanelAfterRender,
                tabchange: this.onMainTabPanelTabChange,
            },
            "taskListPanel": {
                activate: this.onTaskListPanelActivate,
                afterrender: this.onTaskListPanelAfterRender,
                beforerender: this.onTaskListPanelBeforeRender,
                beforestaterestore: this.onTaskListPanelBeforeStateRestore,
                enable: this.onTaskListPanelEnable,
                render: this.onTaskListPanelRender,
                show: this.onTaskListPanelShow,
                staterestore: this.onTaskListPanelStateRestore,
            }
        });
    },

    onTaskListPanelActivate: function(panel, options) {
        // console.log("ENTERED onTaskListPanelActivate");
        // // VP.util.Utils.dumpObject(panel);
        // if ('null' == panel) {
        //     console.log('  PANEL WAS NULL');            
        // }
        // //var textfield = panel.down('#taskTextfield');
        // //var textfield = panel.down('textfield[itemId=taskTextfield]');
        // //var textfield = Ext.getCmp('textfield[name=taskTextfield]');
        // //var textfield = panel.getComponent('taskTextfield');
        // var textfield = Ext.ComponentQuery.query('#taskTextfield').el;
        // console.log('  AFTER GETTING THE TEXTFIELD');
        // console.log(textfield);
        // if (null === textfield) {
        //     console.log('  TEXTFIELD WAS NULL');            
        // }
        // //textfield.focus(false, 200);
    },

    onTaskListPanelAfterRender: function(panel, options) {
        console.log("ENTERED onTaskListPanelAfterRender");
    },

    onTaskListPanelBeforeRender: function() {
        console.log("ENTERED onTaskListPanelBeforeRender");
    },

    onTaskListPanelBeforeStateRestore: function() {
        console.log("ENTERED onTaskListPanelBeforeStateRestore");
    },

    onTaskListPanelEnable: function() {
        console.log("ENTERED onTaskListPanelEnable");
    },

    onTaskListPanelRender: function() {
        console.log("ENTERED onTaskListPanelRender");
    },

    onTaskListPanelShow: function() {
        console.log("ENTERED onTaskListPanelShow");
    },

    onTaskListPanelStateRestore: function() {
        console.log("ENTERED onTaskListPanelStateRestore");
    },

    onLaunch: function() {
        console.log('ENTERED onLaunch');
        // i don't know why, but this needs to be here for the store
        // to *really* be loaded
    },

    // info on tab panel listeners:
    // http://blog.jardalu.com/2013/6/10/simple-tab-dynamic-content-extjs-sencha
    // http://goo.gl/llbcqn (long appfoundation.com url)
    onMainTabPanelTabChange: function(mainTabPanel, panel) {
        // panel (title: Finance; alias: widget.finance, etc.)
        console.log(panel.title);
        this.handleTabChange(panel.title, panel);
    },

    addTextfieldToPanel: function(textField, panel) {
        this.addTextfieldListener(textField);
        panel.add(textField);
    },

    // store loading urls:
    // http://www.curiousm.com/labs/2012/11/13/sencha-touch-2-dynamically-loadi...
    handleTabChange: function(tabName, panel) {
        console.log('ENTERED handleTabChange');
        var me = this;  // need this to access `this` inside `each` loop below

        if (!Ext.getStore('Tasks')) {
            console.log('handleTabChange: Tasks store did not exist');
            Ext.create('Tasks');
        }

        // STORE: http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.Store
        //Ext.getStore('Tasks').load();
        var tasksStore = this.getTasksStore();
        //var tasksStore = Ext.getStore('Tasks');

        // TODO probably want a urlencoded tabname here
        // these add the params as cgi parameters to the GET url
        tasksStore.getProxy().extraParams.projectId = me.getProjectId(tabName);
        //tasksStore.getProxy().extraParams.projectName = tabName;

        // it's important to do these actions inside the load() method,
        // because the load process can fail, and you need to handle that failure
        tasksStore.load({
            callback: function(records, operation, success) {
                console.log('tasksStore.load called');
                console.log('    success:   ' + success);

                // clear the panel
                panel.removeAll();

                // add the textfield
                me.addTextfieldToPanel(me.createTextField(), panel);

                // add hidden fields with the projectId and status.
                // TODO can handle the status as a simple form param
                panel.add(me.createHiddenProjectIdField(tabName));
                panel.add(me.createHiddenStatusField());

                if (success == true) {
                    // MAKE THE CHECKBOXES
                    var groupItemId = me.makeGroupItemIdName(tabName);
                    var group = me.createCheckboxGroup(groupItemId);
                    //panel.body.update(group);
                    var count = 1;
                    // TODO i don't know how to reference a method in this class/object
                    // inside a block like this; a 'this' reference does not work by itself;
                    // if i remember right, i need to pass something else to the function.
                    console.log('ABOUT TO LOOP OVER TASK STORE ITEMS');
                    console.log('TASK STORE COUNT: ' + tasksStore.count());
                    tasksStore.each(function(record) {
                        var task = record.data.description;
                        console.log('TASK: ' + task);
                        var checkbox = me.createCheckbox(task, count++);
                        me.addCheckboxToGroup(group, checkbox);
                    });
                    panel.add(group);
                    panel.doLayout();
                } else {
                    // the store didn't load, anything to do?
                }
            }
            // scope: this,
        });
        console.log('COUNT = ' + tasksStore.count());
    },

    // get the projectId from the projectName
    getProjectId: function(projectName) {
        var projectsStore = this.getProjectsStore();
        var project = projectsStore.findRecord('name', projectName);
        return project.get('id');
    },

    // each checkbox group must have a different itemId. make the itemId from the
    // projectName, which must be unique per user.
    makeGroupItemIdName: function(projectName) {
        return VP.util.Utils.removeSpaces(VP.util.Utils.capitalizeEachWord(projectName));
    },

    createLegalIdNameFromString: function(s) {
        var x = VP.util.Utils.capitalizeEachWord(s);
        var y = VP.util.Utils.stripNonWordCharacters(x);
        return VP.util.Utils.removeSpaces(y);
    },

    createHiddenProjectIdField: function(projectName) {
        return Ext.form.Field({
            xtype: 'hiddenfield',
            name: 'projectId',
            value: this.getProjectId(projectName)
        });
    },

    createHiddenStatusField: function() {
        return Ext.form.Field({
            xtype: 'hiddenfield',
            name: 'status',
            value: 'c'
        });
    },

    createTextField: function() {
        return Ext.create('Ext.form.field.Text', {
            fieldLabel: 'Task:',
            name: 'task',
            itemId: 'taskTextfield',
            autofocus: true,
            enableKeyEvents: true,
            labelAlign: 'left',
            labelWidth: 50,
            labelStyle: 'font-size: 16px;',
            width: 500,
            listeners: {
                // this works, putting focus in the textfield
                afterrender: function(field) {
                    field.focus(false, 250);
                },
                // ADDOption1
                specialkey: function(field, event, options) {
                    console.log('ENTERED specialkey');
                    if (event.getKey() == event.ENTER) {
                        // var saveBtn = field.up('researchLinkForm').down('button#save');
                        // saveBtn.fireEvent('click', saveBtn, event, options);
                    }                    
                }
            }
        });
    },

    // ADDOption2
    addTextfieldListener: function(textfield) {
        var me = this;
        textfield.on('keyup', function(field, event, options) {
            if (event.getCharCode() === event.ENTER) {
                var formPanel = textfield.up('form');
                var tasksStore = me.getTasksStore();
                // if the form is valid, send the data
                if (formPanel.getForm().isValid()) {
                    Ext.Ajax.request({
                        url: 'server/tasks/add',
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        params : Ext.JSON.encode(formPanel.getValues()),
                        success: function(conn, response, options, eOpts) {
                            var result = Packt.util.Util.decodeJSON(conn.responseText);
                            if (result.success) {
                                Packt.util.Alert.msg('Success!', 'Task was saved.');
                                // TODO do whatever is needed to add the checkbox to the list of checkboxes
                                // TODO should also sync up the store
                                // tasksStore.load();
                                var description = textfield.getValue();
                                var checkbox = me.createCheckbox(description, me.createLegalIdNameFromString(description));
                                // get the checkboxgroup and add the checkbox
                                var group = formPanel.down('checkboxgroup');
                                me.insertCheckboxIntoGroup(group, checkbox);
                                textfield.setValue(); //clear
                                formPanel.doLayout();
                            } else {
                                Packt.util.Util.showErrorMsg(result.msg);
                            }
                        },
                        failure: function(conn, response, options, eOpts) {
                            // TODO get the 'msg' from the json and display it
                            Packt.util.Util.showErrorMsg(conn.responseText);
                        }
                    });
                }
            }
        });
    },
    
    createCheckboxGroup: function(nameOfId) {
        var grp = new Ext.form.CheckboxGroup({
            //fieldLabel: 'CheckboxGroup',
            //hideLabel: true,
            columns: 1,
            id: nameOfId,
            itemId: nameOfId,
            formBind: true,  // NIGHT
            listeners: {
                // may only be available after the 'load' event; see
                // http://pritomkumar.blogspot.com/2013/08/extjs-add-checkbox-group-to-pane...
                check: {
                    element: 'el', //bind to the underlying el property on the panel
                    foo: function (clickedObject, isChecked){
                        console.log(clickedObject);
                        console.log(isChecked);
                        console.log("CHECKBOXGROUP: Check box check status changed.");
                        // var group = Ext.getCmp (nameOfId);
                        // var length = group.items.getCount ();
                        // var isCheck = group.items.items[0].checked;
                    }
                },
                change: {
                    element: 'el', //bind to the underlying el property on the panel
                    foo: function (field, newVal, oldVal){
                        console.log('CHECKBOXGROUP: CHANGE!');
                        console.log(field);
                    }
                },
                // works
                afterrender: function(foo) {
                    console.log('afterrender');
                },
                // change: {
                //     element: 'el', //bind to the underlying el property on the panel
                //     fn: function(cb, checked) {
                //         //Ext.getCmp('myTextField').setDisabled(!checked);
                //         Ext.Msg.alert('You changed something'); 
                //         console.log('CHECKED: ' + checked);
                //     },
                //     fn2: function(cb, newVal, oldVal) {
                //         //Ext.getCmp('myTextField').setDisabled(!checked);
                //         console.log('CHECKED: ' + newVal);
                //     }
                // },
                click: {
                    element: 'el', //bind to the underlying el property on the panel
                    fn: function(){ 
                        //Ext.Msg.alert('You clicked something');
                        console.log('CLICKED: (something)');
                    }
                }
             }
        });
        return grp;
    },

    // TODO add a listener to each box
    createCheckbox: function(taskName, uniqueIdentifier) {
        var me = this;
        var checkboxFieldClass = 'checkboxFieldClass';
        var checkbox = new Ext.form.field.Checkbox({
            boxLabel: ' <a href="#" class="taskName">' + taskName + '</a>',
            boxLabelCls: checkboxFieldClass,
            value: taskName,
            name: 'task',
            //itemId: me.createLegalIdNameFromString(taskName) + '_box_' + uniqueIdentifier,
            itemId: 'box' + uniqueIdentifier,
            inputValue: '1', // TODO
            checked: false,
            // found: http://stackoverflow.com/questions/15160466/enable-disable-text-field-on...
            listeners: {
                // NIGHT: this really affects `this.boxLabel`
                //scope: this,

                change: function(checkbox, newValue, oldValue, eOpts) {
                    if (newValue === true) {
                        //
                        // 
                        // TODO leaving off here
                        //
                        //
                        console.log('TRYING TO DUMP CHECKBOX');
                        // VP.util.Utils.dumpObject(checkbox);
                        console.log('this.boxLabel: ' + this.boxLabel);
                        me.handleCheckboxClickedEvent(checkbox, this.boxLabel);
                    }
                },
                render: function(component) {
                    component.getEl().on('click', function(event) {
                        //VP.util.Utils.dumpObject(component.getEl().down('.boxLabelCls').dom.innerText);
                        event.stopEvent();
                        var taskText = component.getEl().down('.'+checkboxFieldClass).dom.innerText;
                        // a hack to figure out whether the user clicked the hyperlink or checkbox
                        var objectType = Object.prototype.toString.call(event.target);
                        if (objectType.indexOf("HTMLAnchorElement") > -1) {
                            // user clicked on the hyperlink
                            Ext.Msg.alert('Now Working On', taskText); 
                        } else {
                            // user clicked the checkbox (HTMLInputElement)
                        }
                    });    
                }
            }
        });
        return checkbox;
    },

    // expects something like '<a href="#" class="taskName">Get tabs working</a>'
    getTextFromHyperlink: function(linkText) {
        return linkBody = linkText.match(/<a [^>]+>([^<]+)<\/a>/)[1];  // returns an array
    },

    handleCheckboxClickedEvent: function(checkbox, checkboxHyperlinkText) {
        
        // NIGHT
        var linkText = this.getTextFromHyperlink(checkboxHyperlinkText);
        // var linkText = 'caca';
        var formPanel = checkbox.up('form');

        //VP.util.Utils.dumpObject(formPanel);

        console.log('formPanel: ' + formPanel);
        console.log(formPanel.getForm().getValues());
        console.log('projectId: ' + formPanel.getForm().findField('projectId').getSubmitValue());
        checkbox.hide();
        // TODO remove from store
        Ext.Ajax.request({
            url: 'server/tasks/updateStatus',
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            // formPanel.getForm().findField('NamePropertyValue').getSubmitValue(
            params: Ext.JSON.encode({
                // "projectId" : formPanel.projectId.getValue(),
                "projectId" : formPanel.getForm().findField('projectId').getSubmitValue(),
                "task": linkText,
                "status": "f"
            }),
            success: function(conn, response, options, eOpts) {
                var result = Packt.util.Util.decodeJSON(conn.responseText);
                if (result.success) {
                    Packt.util.Alert.msg('Success!', 'Task was removed from the server.');
                    // formPanel.doLayout();
                } else {
                    Packt.util.Util.showErrorMsg(result.msg);
                }
            },
            failure: function(conn, response, options, eOpts) {
                // TODO get the 'msg' from the json and display it
                Packt.util.Util.showErrorMsg(conn.responseText);
            }
        });
    },

    insertCheckboxIntoGroup: function(group, checkbox) {
        group.items.insert(0, checkbox);
    },

    addCheckboxToGroup: function(group, checkbox) {
        //var col = group.panel.items.get(group.items.getCount() % group.panel.items.getCount());
        
        // OLD, WORKED (LATE NIGHT)
        group.items.add(checkbox);

        group.add(checkbox);

        //col.add(checkbox);
        //group.panel.doLayout();
    },

    // Usage: var string2 = stringWithoutSpaces(string1);
    stringWithoutSpaces: function(string) {
        // str.replace(/\s/g, '');
        return string.replace(/ /g,'');
    },

    capitalizeAllWords: function(str) {
        return str.replace(/\w\S*/g, function(txt){
            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
        });
    },

    /**
     * Handle the rendering of the MainTabPanel.
     * As it's rendered, dynamically add one tab for each project.
     * from 'mastering', p. 109
     */
    onMainTabPanelRender: function(component, options) {
        console.log('ENTERED onMainTabPanelRender, trying to get ProjectsStore');
        var mainTabPanel = this.getMainTabPanel();
        //var tabsArray = new Array();
        var projectsStore = this.getProjectsStore();
        projectsStore.load({
            callback: function(records, operation, success) {
                console.log('projectsStore.load called');
                console.log('    success:      ' + success);
                if (success == true) {
                    projectsStore.each(function(record) {
                        //tabsArray.push(record.data.name);
                        var tab = mainTabPanel.add({
                            xtype: 'taskListPanel',
                            closable: false,
                            title: record.data.name, // the text on the tab
                            alias: 'widget.' + record.data.name.toLowerCase(),
                            iconCls: record.data.name.toLowerCase(),  // renders 'class="finance"'
                            listeners : {
                                click: function () { 
                                    console.log("YOU CLICKED A TAB !!!");
                                },
                                // beforeclose : function(tab) {
                                //     tab.removeAll();
                                //     tab.destroy();
                                //     return true;
                                // }
                            }
                        });
                    });
                } else {
                    console.log('    success was false (bad)');
                }
            }
        });

        mainTabPanel.setActiveTab(0);
        mainTabPanel.doLayout();
        // TODO i may need to create these tabs as an array so i can
        // access them, and also know which one is in the foreground
    },

    onMainTabPanelAfterRender: function(tabPanel, options) {
        console.log('ENTERED onMainTabPanelAfterRender');
        tabPanel.setActiveTab(0);
    }

});

Sencha ExtJS, Element, DOM, down, and textfield

This Sencha ExtJS code snippet shows how to use the Ext.Element down method to get the value from a textfield:

{
    xtype: 'button',
    text: 'Save',
    listeners: {
        click: function () { 
            // fname = Ext.get('first_name').dom.value;
            var fname = myForm.down('textfield[name=first_name]').getValue();    
            alert(fname);
        }
    }
}

I found the code at the link shown. Mostly I want to remember how to use the down method/function with a textfield. (I'm just learning about Sencha selectors, how it shows the DOM, and the Ext.Element class.

Sencha ExtJS code examples - controller, init, ui events, form validate, submit, success, failure

Without any significant introduction, here are some more Sencha ExtJS code examples. I’m just trying to make my example code easier for me to find; if it helps you, that’s cool, too.

The following code shows:

  • A Sencha ExtJS controller
  • A controller init function
  • How to handle a variety of UI events
  • A variety of Sencha form examples, including:
    • validating and submitting forms
    • success and failure functions
    • Ext.Ajax.request example code
    • Using Ext.Msg.show

Here’s the source code:

Ext.define('Finance.controller.Transactions', {
    extend: 'Ext.app.Controller',

    requires: [
        'VP.util.Utils'
    ],

    views: [
        'TransactionList',
    ],

    stores: [
        'Transactions'
    ],

    refs: [
        {
            ref: 'transactionList',
            selector: 'transactionList'
        }
    ],

    init: function(application) {

        // add the components and events we listen to
        this.control({
            'transactionList': {
                render: this.onRender
            },
            'transactionList button#add': {
                click: this.onAddTransactionButtonClicked
            },
            'transactionList button#delete': {
                click: this.onDeleteTransactionButtonClicked
            },
            'transactionform button#cancel': { //TransactionForm cancel button
                click: this.onAddTransactionFormCancelClicked
            },
            'transactionform button#save': { //TransactionForm save button
                click: this.onAddTransactionFormSaveClicked
            },
            'transactionform textfield#symbol': { //handle blur event on the symbol textfield
                blur: this.onAddTransactionFormSymbolFieldBlur
            }
        });

        if (!Ext.getStore('Transactions')) {
            Ext.create('Finance.store.Transactions');
        }    
    },

    /**
     * Transaction Form
     * ----------------
     */
    onAddTransactionFormSymbolFieldBlur: function(textfield, event, options) {
        textfield.setValue(textfield.getValue().toUpperCase());
    },

    onAddTransactionButtonClicked: function(button, event, options) {
        var win = Ext.create('Finance.view.TransactionForm');
        //VP.util.Utils.dumpObject(win);
        win.setTitle('Add Transaction');
        win.show();
    },

    onAddTransactionFormCancelClicked: function(button, event, options) {
        button.up('window').close();
    },

    // add a transaction
    onAddTransactionFormSaveClicked: function(button, event, options) {
        var win = button.up('window'),
            formPanel = win.down('form'),
            store = this.getTransactionList().getStore();

        if (formPanel.getForm().isValid()) {
            formPanel.getForm().submit({
                clientValidation: true,
                url: 'php/transactionsave.php',
                success: function(form, action) {
                    var result = action.result;
                    console.log(result);
                    if (result.success) {
                        Packt.util.Alert.msg('Success!', 'Transaction was saved.');
                        store.load();
                        win.close();                      
                    } else {
                        Packt.util.Util.showErrorMsg(result.msg);
                    }
                },
                failure: function(form, action) {
                    switch (action.failureType) {
                        case Ext.form.action.Action.CLIENT_INVALID:
                            Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
                            break;
                        case Ext.form.action.Action.CONNECT_FAILURE:
                            Ext.Msg.alert('Failure', 'Ajax communication failed');
                            break;
                        case Ext.form.action.Action.SERVER_INVALID:
                            Ext.Msg.alert('Failure', action.result.msg);
                   }
                }
            });
        }
    },

    // delete one or more stocks
    onDeleteTransactionButtonClicked: function (button, e, options) {
        var grid = this.getTransactionList(),
            record = grid.getSelectionModel().getSelection(), 
            store = grid.getStore();

        if (store.getCount() >= 1 && record[0]) {
            var idToDelete = record[0].get('id');
            Ext.Msg.show({
                 title:'Delete?',
                 msg: 'Are you sure you want to delete ID(' + idToDelete + ')?',
                 buttons: Ext.Msg.YESNO,
                 icon: Ext.Msg.QUESTION,
                 fn: function (buttonId){
                    if (buttonId == 'yes'){
                        Ext.Ajax.request({
                            url: 'php/deletetransaction.php',
                            params: {
                                id: idToDelete
                            },
                            success: function(conn, response, options, eOpts) {
                                var result = Packt.util.Util.decodeJSON(conn.responseText);
                                if (result.success) {
                                    Packt.util.Alert.msg('Success', 'The transaction was deleted.');
                                    store.load();
                                } else {
                                    Packt.util.Util.showErrorMsg(conn.responseText);
                                }
                            },
                            failure: function(conn, response, options, eOpts) {
                                Packt.util.Util.showErrorMsg(conn.responseText);
                            }
                        });
                    }
                 }
            });
        } else {
            Ext.Msg.show({
                title:'Dude',
                msg: 'Dude, you need to select at least one transaction.',
                buttons: Ext.Msg.OK,
                icon: Ext.Msg.WARNING
            });
        }
    },

    onRender: function(component, options) {
        component.getStore().load();
    }

});

A good Sencha ExtJS form example (VTypes, password, submit on enter, buttons, submit)

A good Sencha ExtJS form example.

Ext.require([    
    'Ext.form.*',
    'Ext.tip.*'
]);

Ext.onReady(function() {    
    // Helpers
    
    // Very basic bassword validation.
    // Note that length validation is managed by ExtJs itself --
    // scroll down to see how in the field properties
    Ext.apply(Ext.form.field.VTypes, {
        password: function(val, field) {
            if (/^[a-z0-9]+$/i.test(val)) {
                return true;
            }
        },
        passwordText: 'Password may only contain letters and numbers.'
    });
    
    Ext.QuickTips.init();
    
    function submitOnEnter(field, event) {
        if (event.getKey() == event.ENTER) {
            var form = field.up('form').getForm();
            form.submit();
        }
    }
    
    // From bit.ly/Bvvv8
    function password(length, special) {
        var iteration = 0;
        var password = '';
        var randomNumber;

        if (special == undefined) {
            var special = false;
        }

        while (iteration < length) {
            randomNumber = (Math.floor((Math.random() * 100)) % 94) + 33;
            if (!special) {
                if ((randomNumber >=33) && (randomNumber <=47)) { continue; }
                if ((randomNumber >=58) && (randomNumber <=64)) { continue; }
                if ((randomNumber >=91) && (randomNumber <=96)) { continue; }
                if ((randomNumber >=123) && (randomNumber <=126)) { continue; }
            }
            iteration++;
            password += String.fromCharCode(randomNumber);
        }
        return password;
    }

    // Form
    // -----------------------------------------------------------------------
    var addUserForm = Ext.create('Ext.form.Panel', {
        renderTo: Ext.getBody(),
        bodyStyle: 'padding: 5px 5px 0 5px;',
        defaults: {
            xtype: 'textfield',
            anchor: '100%',
         },
        items: [{
            fieldLabel: 'Email',
            name: 'email',
            vtype: 'email',
            maxLength: 64,            
            allowBlank: false,
            listeners: {
                specialkey: submitOnEnter
            }
        },{
            xtype: 'fieldcontainer',
            fieldLabel: 'Password',
            layout: 'hbox',
            items: [{
                xtype: 'textfield',
                name: 'password',
                flex: 1,
                vtype: 'password',
                minLength: 4,
                maxLength: 32,
                allowBlank: false,
                listeners: {
                    specialkey: submitOnEnter
                }
            }, {
                xtype: 'button',
                text: 'Random',
                tooltip: 'Generate a random password',
                style: 'margin-left: 4px;',
                flex: 0,
                handler: function() {
                    this.prev().setValue(password(8, false));
                    this.prev().focus()
                }
            }]
        }],
        buttons: [{
            id: 'saveBtn',
            itemId: 'saveBtn',
            text: 'Submit',
            handler: function() {
                this.up('form').getForm().submit();
            }
        },{
            text: 'Cancel',
            handler: function() {
                this.up('form').getForm().reset();
            }
        }],
        submit: function() {
            var currentForm = this.owner.form;
            if (currentForm.isValid()) {
                // var newSomething = Ext.create('Something', currentForm.getFieldValues());
            }
        }
    });
});