Category Archives: Ext JS 5

Integrating Ext JS 6, Sencha Architect 4, and TinyMCE

Note: This is a follow-up to my post from 2014

I’ve re-packaged and enhanced the plugin as a Sencha Architect Extension (.aux) file.

New features include:

  • Ext JS 5.x/6.x compatibility
  • Sencha Architect 4.x compatibility
  • The TinyMCE editor now appears directly within the design canvas of Sencha Architect
  • Added support for new TinyMCE4 features (including the Premium Spell Checker plugin)
  • Added support to use smaller buttons in the TinyMCE editor.


By default, the editor will pull from the TinyMCE Cloud. See the usage notes in the file for install instructions.

You can download the extension from my repo here:


Sencha Ext JS 5: Supporting Different Themes for Different Devices

Sencha Ext JS 5 is the first version of the Ext framework to fully support iOS tablets.

Perhaps the most important task  that you’ll need to do in order add tablet support to your application is to automatically switch between desktop and touch-based themes (CSS files), depending on the type of device being used.

This tutorial demonstrates how to add this capability to your application.

Step 1: Create the App

  1. Open a command prompt to your Ext JS 5 folder
  2. Run the following command:
    sencha generate app TestApp ../TestApp

Step 2: Define the Themes

  1. In your command prompt, change directories to the TestApp folder.
  2. Enter the following commands:
    1. sencha generate theme TestApp-Desktop
    2. sencha generate theme TestApp-Tablet
  3. Using your editor, open /TestApp/packages/TestApp-Desktop/package.json
  4. Modify the “extend” property to extend ext-theme-neptune.
  5. Save the file.
  6. Using your editor, open /TestApp/packages/TestApp-Tablet/package.json
  7. Change the “extend” property from ext-theme-classic to ext-theme-neptune-touch.

Step 3: Modify your App.json file to support multiple platform builds

  1. Using your editor, open /TestApp/app.json
  2. Add a “builds” property as illustrated here:
    "builds": {
        "testAppDesktop": {
            "theme": "TestApp-Desktop"
        "testAppTouch": {
            "theme": "TestApp-Tablet"

Step 4: Modify your output definition to create multiple application manifests

Replace the output config property in your app.json with the following:

"output": {
    "base": "${}/${build.environment}/${}/${}",
    "page": "./index.html",
    "manifest": "../${}.json",
    "deltas": {
        "enable": false
    "cache": {
        "enable": false

Step 5: Refresh your App

Return to your command prompt and type the following:
sencha app refresh

This will generate two manifest files: testAppDesktop.json and testAppTouch.json

Step 6: Replace the CSS config in the App.json

Replace the css configuration property in your App.json with the following:

"css": [{
    "path": "${build.out.css.dir}/TestApp-all.css",
    "bootstrap": true

Step 7: Replace the bootstrap property to load the appropriate manifest file

"bootstrap": {
   "manifest": "${}.json"

Step 8: Add the following code into a script tag in your index.html file, just above the microloader, to load the appropriate manifest:

var Ext = Ext || {};
Ext.beforeLoad = function(tags){	
    var theme = location.href.match(/theme=([\w-]+)/);
 	theme  = (theme && theme[1]) || (tags.desktop ? 'testAppDesktop' : 'testAppTouch');
    Ext.manifest = theme;
    tags.test = /testMode=true/.test(;
    Ext.microloaderTags = tags;

Step 9: Build the Application

Return to your command prompt and type the following:

sencha app build development

Step 10: Test the Application in a Desktop and Mobile Browser

Ext JS: Generating a Checkbox Group from a Store

Ext JS checkbox groups enable you to group checkboxes into a single logical field. Since these checkboxes are often times dynamically generated from a store, I thought that it might make sense to extend the class with some store binding. Here’s my first, lightly tested attempt:

 Ext.define('Ext.ux.CheckboxStoreGroup', {
    extend: 'Ext.form.CheckboxGroup',
    alias: 'widget.checkboxstoregroup',
    config: {
        store: null,
        labelField: 'label',
        valueField: 'id',
        checkedField: 'checked',
        columns: 3,
        boxFieldName: 'mycheckbox'
    applyStore: function(store) {
        if (Ext.isString(store)) {
            return Ext.getStore(store);
        } else {
            return store;
    updateStore: function(newStore, oldStore) {
        if (oldStore) {
            store.removeEventListener('datachanged', this.onStoreChange, this)
        newStore.on('datachanged', this.onStoreChange, this);
    onStoreChange: function(s) {
        var vField = this.getValueField();
        var lField = this.getLabelField();
        var cField = this.getCheckedField();
        var fName = this.getBoxFieldName();
        var rec = null;
        for (var i=0; i<s.getCount(); i++) {
            rec = s.getAt(i);
                xtype: 'checkbox',
                inputValue: rec.get(vField),
                boxLabel: rec.get(lField),
                checked: rec.get(cField),
                name: fName
    initComponent: function() {
        this.on('afterrender', this.onAfterRender);
    onAfterRender: function() {   
        if (this.getStore().totalCount) {

You can test and play around with the code here:

Using Brackets for Ext JS Development

Recently I evaluated using Brackets, a free, open-source, multiplatform-editor as a potential replacement for Sublime Text in our Javascript training courses.
Brackets has a lot going for it:

  • It’s free
  • Installation is simple
  • It’s open source
  • Projects are directory-based, just like Sublime
  • It has a js linter built in
  • It has some intellisense
  • It does *not* auto-complete array and object syntax (brackets and curly braces)
  • It supports scss (Sassy CSS)
  • Performance doesn’t completely suck
  • It’s supports 3rd Party Plugins
  • It’s available for multiple OS.

And while it doesn’t fully support Sencha’s massive Ext JS library, it should help students quickly identify syntax problems with their json configs. I was able to configure Bracket’s built-in JS Linter to ignore nearly all of its “false-positive” errors and warnings.

The trick is to modify Bracket’s preferences file (Debug > Open Preferences File) as follows:

    "linting.collapsed": false,
    "themes.theme": "light-theme",
    "jslint.options": {
        "plusplus": true,
        "devel": true,
        "predef": [
        "sloppy": true,
        "browser": true,
        "white": true
    "useTabChar": false

You’ll need to restart Brackets for these changes to take effect.

So if you’re looking for a change of pace from using Sublime, or money is tight and you can’t afford JetBrains WebStorm or Sencha Architect, it’s worth giving Brackets a try.

Download Brackets at

Happy coding!

Fig Leaf Software’s Ext JS 5 Fiddles

We’ve been developing a bunch of Sencha Ext JS 5 examples lately, and we decided to share them with the community. Hopefully these will prove helpful to the newbies!

Converting an Ext 5 Grid to Excel Spreadsheet

A slightly belated holiday gift — my Ext JS Grid to Excel code, now updated for Ext JS 5!

Features include:

  • Group support
  • Handling of numeric vs string data types
  • Post back to an app server for browsers that don’t support client-side downloads



    Excel.js - convert an ExtJS 5 grid into an Excel spreadsheet using nothing but
    javascript and good intentions.

    By: Steve Drucker
    Dec 26, 2014
    Original Ext 3 Implementation by: Nige "Animal" White?
    Contact Info:

    company: Fig Leaf Software ( /

    Invocation:  grid.downloadExcelXml(includeHiddenColumns,title)

    Upgraded for ExtJS5 on Dec 26, 2014


var Base64 = (function() {
    // Private property
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    // Private method for UTF-8 encoding

    function utf8Encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
        return utftext;

    // Public method for encoding
    return {
        encode: (typeof btoa == 'function') ? function(input) {
            return btoa(utf8Encode(input));
        } : function(input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;
            input = utf8Encode(input);
            while (i < input.length) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                output = output +
                    keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                    keyStr.charAt(enc3) + keyStr.charAt(enc4);
            return output;

Ext.define('MyApp.overrides.view.Grid', {
    override: 'Ext.grid.GridPanel',
    requires: 'Ext.form.action.StandardSubmit',

        Kick off process

    downloadExcelXml: function(includeHidden, title) {

        if (!title) title = this.title;

        var vExportContent = this.getExcelXml(includeHidden, title);

          dynamically create and anchor tag to force download with suggested filename 
          note: download attribute is Google Chrome specific

        if (Ext.isChrome) {
            var gridEl = this.getEl();
            var location = 'data:application/;base64,' + Base64.encode(vExportContent);

            var el = Ext.DomHelper.append(gridEl, {
                tag: "a",
                download: title + "-" + Ext.Date.format(new Date(), 'Y-m-d Hi') + '.xls',
                href: location



        } else {

            var form = this.down('form#uploadForm');
            if (form) {
            form = this.add({
                xtype: 'form',
                itemId: 'uploadForm',
                hidden: true,
                standardSubmit: true,
                url: '' + escape(title + ".xls"),
                items: [{
                    xtype: 'hiddenfield',
                    name: 'data',
                    value: vExportContent




        Welcome to XML Hell
        for more details

    getExcelXml: function(includeHidden, title) {

        var theTitle = title || this.title;

        var worksheet = this.createWorksheet(includeHidden, theTitle);
        if (this.columnManager.columns) {
            var totalWidth = this.columnManager.columns.length;
        } else {
             var totalWidth = this.columns.length;

        return ''.concat(
            '<?xml version="1.0"?>',
            '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="">',
            '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Title>' + theTitle + '</Title></DocumentProperties>',
            '<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"><AllowPNG/></OfficeDocumentSettings>',
            '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">',
            '<WindowHeight>' + worksheet.height + '</WindowHeight>',
            '<WindowWidth>' + worksheet.width + '</WindowWidth>',


            '<Style ss:ID="Default" ss:Name="Normal">',
            '<Alignment ss:Vertical="Bottom"/>',
            '<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="12" ss:Color="#000000"/>',

            '<Style ss:ID="title">',
            '<Borders />',
            '<Font ss:Bold="1" ss:Size="18" />',
            '<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1" />',
            '<NumberFormat ss:Format="@" />',

            '<Style ss:ID="headercell">',
            '<Font ss:Bold="1" ss:Size="10" />',
            '<Alignment ss:Horizontal="Center" ss:WrapText="1" />',
            '<Interior ss:Color="#A3C9F1" ss:Pattern="Solid" />',

            '<Style ss:ID="even">',
            '<Interior ss:Color="#CCFFFF" ss:Pattern="Solid" />',

            '<Style ss:ID="evendate" ss:Parent="even">',
            '<NumberFormat ss:Format="yyyy-mm-dd" />',

            '<Style ss:ID="evenint" ss:Parent="even">',
            '<Numberformat ss:Format="0" />',

            '<Style ss:ID="evenfloat" ss:Parent="even">',
            '<Numberformat ss:Format="0.00" />',

            '<Style ss:ID="odd">',
            '<Interior ss:Color="#CCCCFF" ss:Pattern="Solid" />',

            '<Style ss:ID="groupSeparator">',
            '<Interior ss:Color="#D3D3D3" ss:Pattern="Solid" />',

            '<Style ss:ID="odddate" ss:Parent="odd">',
            '<NumberFormat ss:Format="yyyy-mm-dd" />',

            '<Style ss:ID="oddint" ss:Parent="odd">',
            '<NumberFormat Format="0" />',

            '<Style ss:ID="oddfloat" ss:Parent="odd">',
            '<NumberFormat Format="0.00" />',



        Support function to return field info from store based on fieldname


    getModelField: function(fieldName) {

        var fields =;
        for (var i = 0; i < fields.length; i++) {
            if (fields[i].name === fieldName) {
                return fields[i];

        Convert store into Excel Worksheet

    generateEmptyGroupRow: function(dataIndex, value, cellTypes, includeHidden) {

        var cm = this.columnManager.columns;
        var colCount = cm.length;
        var rowTpl = '<Row ss:AutoFitHeight="0"><Cell ss:StyleID="groupSeparator" ss:MergeAcross="{0}"><Data ss:Type="String"><html:b>{1}</html:b></Data></Cell></Row>';
        var visibleCols = 0;

        // rowXml += '<Cell ss:StyleID="groupSeparator">'

        for (var j = 0; j < colCount; j++) {
            if (cm[j].xtype != 'actioncolumn' && (cm[j].dataIndex != '') && (includeHidden || !cm[j].hidden)) {
                // rowXml += '<Cell ss:StyleID="groupSeparator"/>';

        // rowXml += "</Row>";

        return Ext.String.format(rowTpl, visibleCols - 1, Ext.String.htmlEncode(value));

    createWorksheet: function(includeHidden, theTitle) {
        // Calculate cell data types and extra class names which affect formatting
        var cellType = [];
        var cellTypeClass = [];
        if (this.columnManager.columns) {
            var cm = this.columnManager.columns;
        } else {
            var cm = this.columns;
        var colCount = cm.length;
        var totalWidthInPixels = 0;
        var colXml = '';
        var headerXml = '';
        var visibleColumnCountReduction = 0;

        for (var i = 0; i < cm.length; i++) {
            if (cm[i].xtype != 'actioncolumn' && (cm[i].dataIndex != '') && (includeHidden || !cm[i].hidden)) {
                var w = cm[i].getEl().getWidth();
                totalWidthInPixels += w;

                if (cm[i].text === "") {
                } else {
                    colXml += '<Column ss:AutoFitWidth="1" ss:Width="' + w + '" />';
                    headerXml += '<Cell ss:StyleID="headercell">' +
                        '<Data ss:Type="String">' + cm[i].text.replace("<br>"," ") + '</Data>' +
                        '<NamedCell ss:Name="Print_Titles"></NamedCell></Cell>';

                    var fld = this.getModelField(cm[i].dataIndex);
                    switch (fld.$className) {
                        case "":
                        case "":
                        case "":
                        case "":
        var visibleColumnCount = cellType.length - visibleColumnCountReduction;

        var result = {
            height: 9000,
            width: Math.floor(totalWidthInPixels * 30) + 50

        // Generate worksheet header details.

        // determine number of rows
        var numGridRows = + 2;
        if (( &&!Ext.isEmpty( || ( && > 0)) {
            numGridRows = numGridRows +;

        // create header for worksheet
        var t = ''.concat(
            '<Worksheet ss:Name="' + theTitle + '">',

            '<NamedRange ss:Name="Print_Titles" ss:RefersTo="=\'' + theTitle + '\'!R1:R2">',

            '<Table ss:ExpandedColumnCount="' + (visibleColumnCount + 2),
            '" ss:ExpandedRowCount="' + numGridRows + '" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="15">',
            '<Row ss:Height="38">',
            '<Cell ss:MergeAcross="' + (visibleColumnCount - 1) + '" ss:StyleID="title">',
            '<Data ss:Type="String" xmlns:html="">',
            '<html:b>' + theTitle + '</html:b></Data><NamedCell ss:Name="Print_Titles">',
            '<Row ss:AutoFitHeight="1">',
            headerXml +

        // Generate the data rows from the data in the Store
        var groupVal = "";
        var groupField = "";
        if ( && > 0) {
            groupField =[0];
        } else if ( != '') {
             groupField =;
        for (var i = 0, it =, l = it.length; i < l; i++) {

            if (!Ext.isEmpty(groupField)) {
                if (groupVal != {
                    groupVal =;
                    t += this.generateEmptyGroupRow(groupField, groupVal, cellType, includeHidden);
            t += '<Row>';
            var cellClass = (i & 1) ? 'odd' : 'even';
            r = it[i].data;
            var k = 0;
            for (var j = 0; j < colCount; j++) {
                if (cm[j].xtype != 'actioncolumn' && (cm[j].dataIndex != '') && (includeHidden || !cm[j].hidden)) {
                    var v = r[cm[j].dataIndex];
                    if (cellType[k] !== "None") {
                        t += '<Cell ss:StyleID="' + cellClass + cellTypeClass[k] + '"><Data ss:Type="' + cellType[k] + '">';
                        if (cellType[k] == 'DateTime') {
                            t += Ext.Date.format(v, 'Y-m-d');
                        } else if (!Ext.isEmpty(v)) {
                            t += Ext.String.htmlEncode(v);
                        t += '</Data></Cell>';
            t += '</Row>';

        result.xml = t.concat(
            '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">',
        return result;

Upgrading to Ext JS 5 in five easy steps!

Historically, the upgrade path between different versions of Sencha Ext JS has been challenging for developers and product managers alike. Much to the chagrin of project managers everywhere, migrating apps from Ext JS 3 to Ext JS 4 typically required major refactoring, frequently necessitating a complete rewrite. The complaints of the IT community were clearly heard loud and clear by Sencha which, I’m happy to report, has actually made the Ext 4 to Ext 5 upgrade path nearly seamless.

Top 5 reasons for upgrading to Ext JS 5

  1. New Components
    Ext 5 adds support for a drag & drop dashboard component, an improved combo box with tag selector, and grid widgets.


    Ext JS 5 Grid Widgets

  2. A new charting package based on SVG and Canvas
    The new charting engine is going to be shared with Sencha Touch. It’s a lot simpler for developers to add additional sprites to the drawing layer and floating axis and more complex user interactions are now supported. Sencha has also cleaned up some of the chart config properties (e.g. axis types are now all lower-case) to make it more consistent across the framework. Since the charts are now SVG/Canvas based, client-side methods have been added that enable a user to download the generated chart as a bitmap. You won’t have to rely on the cloud service anymore to convert vectors into bitmaps.


    Ext JS 5 chart with added custom sprites

  3. Viewmodels and Bi-Directional Data Binding
    You now have the option of using an Model-View-ViewModel (MVVM) architecture instead of the Ext 4.x MVC framework. MVVM enables you to use views expressly for layout while compartmentalizing event handling into a separate, view-specific file. ViewModels enable you to easily implement bi-directional databinding with your views, which significantly cuts down on the amount of code that you’ll need to write (and debug!).

    Combining MVVM with MVC

    Combining MVVM with MVC

  4. Chained Stores and other Improvements to the Data Package
    Most apps are going to require multiple views of the same data store. For instance, you might want to simultaneously output two grids that refer to the same core dataset but have different client-side filters applied to them. In Ext 4 this typically required the instantiation of two separate stores and the duplication of records in memory. Chained stores are analogous database table views.  They link back to a single  “source” store containing your records. The chained store’s  filters, sorters and groupers are defined independently of the data “source” Any updates to the source store automatically triggers a refresh in its associated chained stores.
  5. Tablet Compatibility
    I saved the best for last. Ext JS 5 now supports touch gestures (pinch, rotate, longpress, etc) and comes bundled with two touch-compatible themes – “Crisp” and “Neptune Touch.”  You’ll likely need to make some slight changes to your layouts after applying these themes, however, in most cases you should be able to support both desktop and tablet browsers from a single codebase.


    Using the “Crisp” touch-compatible theme

Upgrading to Ext JS 5 in Five Easy Steps

During this tutorial, you’ll upgrade the Congressional Spending Portal – an app that I had initially developed using Sencha Architect in Ext JS 4.1.

Upgrade this app from Ext 4 to Ext 5 in five easy steps!

Upgrade this app from Ext 4 to Ext 5 in five easy steps!Download the tutorial assets

Before you begin

Step 1: Create a Sencha Command Project

Open a command prompt to /extjs5upgrade/ and enter the following statement:

sencha generate app -ext SpendingPortal spendingportal

Note that Sencha Command will automatically download and install the latest version of Ext 5 from the CDN! Cool!

Step 2: Copy the Ext 4 classes into the Ext 5 project

  1. Delete the controller, model, store, and view folders from /extjs5upgrade/spendingportal/app
  2. Copy /extjs5upgrade/before/app/*.* to /extjs5upgrade/spendingportal/app
  3. Copy /extjs5upgrade/before/app.js to /extjs5upgrade/spendingportal/app/Application.js

Step 3: Tweak the Application.js file

Refactor the /extjs5upgrade/spendingportal/app/Application.js file to resemble the following:

Ext.define('SpendingPortal.Application', {
    extend: '',
    name: 'SpendingPortal',
    controllers: [

    launch: function () {
        // TODO - Launch the application

        var pnl = Ext.ComponentQuery.query('#centerpanel')[0];

        Ext.widget('sponsors', {
            constrainTo: pnl.getEl(),
            x: 5,
            y: 20

        Ext.widget('earmarksviewer', {
            constrainTo: pnl.getEl(),
            x: 500,
            y: 20

        Ext.widget('sponsorchart', {
            constrainTo: pnl.getEl(),
            x: 500,
            y: 300

Tweak the /extjs5upgrade/spendingportal/app.js file to set autoCreateViewport to true.

    name: 'SpendingPortal',
    extend: 'SpendingPortal.Application',
    autoCreateViewport: true

Step 4: Invoke Ext 4 Compatibility mode

Open the app/app.json file and add the following code to enable “compatibility” mode:

"compatibility" : {
        "ext" : "4.2"

Modify the requires[] property in the app/app.json file to load the Ext 4 charting package:

"requires": [

Step 5: Add Tablet Support

Modify the theme property in the /extjs5upgrade/spendingportal/app/app.json file to resemble the following:

"theme": "ext-theme-crisp"

Test the App!

Return to your command prompt and change directories to /extjs5upgrade/spendingportal. Then issue the following command to update the bootstrap.json file which indicates the classes to be loaded:

sencha app refresh

Then tell Sencha Command to create a build and make it accessible via http (Sencha Command has a built-in webserver):

sencha app watch

Test the app in your browser by accessing the URL generated by Sencha command (typically http://localhost:1841).

Now wasn’t that easy?

Congrats! Now that you’ve gotten the app up and running on Ext 5, you can start to improve upon it by invoking the broad range of new features (including Sencha Charts!) that have been added to the framework. Check back here often to learn about new Ext 5 tips, traps, and techniques. And consider engaging Fig Leaf Software for your Sencha consulting and training needs!