Sencha Ext JS 5: Supporting Different Themes for Different Devices

March 31, 2015 Leave a comment

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": "${workspace.build.dir}/${build.environment}/${app.name}/${build.id}",
    "page": "./index.html",
    "manifest": "../${build.id}.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": "${build.id}.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(location.search);
    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

Categories: Ext JS 5 Tags:

Sample Node.JS REST API Implementation with MySQL

March 10, 2015 1 comment

Here’s a simple example of a RESTful API implementation that I cobbled together for one of the students in our Node.JS Fundamentals course. It’s illustrates how to use the Express framework, along with a mysql datasource to implement create/read/update/destroy functionality on records.

Enjoy!

var express = require('express');
var router = express.Router();

var mysql = require('mysql');
var pool = mysql.createPool({
	host: 'localhost',
	user: 'root',
	password: '',
	database: 'NodeJSExamples'
});

/* GET users listing. */
router.get('/', function(req, res) {

	console.log(req);

	pool.getConnection(function(err, connection) {

		if (err) {
			console.error("An error occurred: " + err);
		}

		connection.query('select * from Person', function(err, rows) {
			if (err) {
				throw err;
			} else {
				res.writeHead(200, {
					"Content-Type": "application/json"
				});
				var result = {
					success: true,
					rows: rows.length,

				}
				res.write(JSON.stringify(rows));
				res.end();
			}

			connection.release();
		});

	});

});


router.delete('/:id', function(req, res) {

	console.log(req);

	pool.getConnection(function(err, connection) {

		if (err) {
			console.error("An error occurred: " + err);
		}

		connection.query('delete from Person where id=?', [req.params.id], function(err, rows) {

			if (err) {
				throw err;
			} else {

				res.writeHead(200, {
					"Content-Type": "application/json"
				});
				var result = {
					success: true,
					rows: rows.length,
					detail: rows

				}
				res.write(JSON.stringify(result));
				res.end();

			}

			connection.release();
		});

	});

});

router.put('/:id', function(req, res) {

	console.log(req.body);

	req.assert('lastName', 'Last Name is required').notEmpty();
	var errors = req.validationErrors();

	pool.getConnection(function(err, connection) {

		if (err) {
			console.error("An error occurred: " + err);
		}

		connection.query('update Person set ? where id = ?', [req.body, req.params.id],
			function(err, rows) {
				if (err) {
					throw err;
				} else {
					res.writeHead(200, {
						"Content-Type": "application/json"
					});
					var result = {
						success: true,
						detail: rows

					}
					res.write(JSON.stringify(result));
					res.end();
				}

				connection.release();
			});

	});

});


router.post('/', function(req, res) {

	console.log(req.body);

	// req.assert('lastName', 'Last Name is required').notEmpty();
	// var errors = req.validationErrors();

	pool.getConnection(function(err, connection) {

		if (err) {
			console.error("An error occurred: " + err);
		}

		connection.query('insert into Person set ?', req.body,
			function(err, rows) {
				if (err) {
					throw err;
				} else {
					res.writeHead(200, {
						"Content-Type": "application/json"
					});
					var result = {
						success: true,
						detail: rows,
						id: rows.insertId

					}
					res.write(JSON.stringify(result));
					res.end();
				}

				connection.release();
			});

	});

});

module.exports = router;
Categories: JavaScript

Ext JS: Generating a Checkbox Group from a Store

February 12, 2015 Leave a comment

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) {
        
        Ext.suspendLayouts();
        this.removeAll();
        
        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);
           
            this.add({
                xtype: 'checkbox',
                inputValue: rec.get(vField),
                boxLabel: rec.get(lField),
                checked: rec.get(cField),
                name: fName
            });
        }
        
        Ext.resumeLayouts(true);
        
    }, 
    initComponent: function() {
        this.callParent(arguments);
        this.on('afterrender', this.onAfterRender);
    },
    onAfterRender: function() {   
        if (this.getStore().totalCount) {
            this.onStoreChange(this.getStore);
        }
    }
});

You can test and play around with the code here:
https://fiddle.sencha.com/#fiddle/i51

Categories: Ext JS 4, Ext JS 5

Using Brackets for Ext JS Development

February 12, 2015 3 comments

Recently I evaluated using Brackets, a free, open-source, multiplatform-editor as a potential replacement for Sublime Text in our Javascript training courses.
brackets1
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": [
            "Ext"
        ],
        "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 http://brackets.io/

Happy coding!

Categories: Ext JS 5, JavaScript

Fig Leaf Software’s Ext JS 5 Fiddles

February 9, 2015 Leave a comment
Categories: Ext JS 5 Tags: ,

I Can’t Fight This Framework Anymore…

January 26, 2015 4 comments

I can’t fight this framework any longer.
It seems like such a really great gizmo
What started out as a small ZIP
Has grown larger.
And its learning curve will make my work go slow

I tell myself that I can’t hold out forever.
Friends said there is no reason for my fear.
I feel so secure when I read their forums
It should give my work direction,
And make everything so clear.

And even as I ponder,
Converting every app in sight
Its docs make my brow furrow
As I code late into the night
But I’m getting closer than I ever thought I might.

And I can’t fight this framework anymore.
Writing my own code is such a chore.
Gotta learn before my job gets shipped offshore
And stays in Eastern Europe forever

And I can’t fight this framework anymore.
I’ve forgotten what I started fighting for.
It’s time to put my app into the store,
And delete the backdoors, forever.

Cause I can’t fight this framework anymore.
I’ve forgotten what I started fighting for.
Tired of my ux being an eyesore
Gonna show my boss I am hardcore!
Baby, I can’t fight this framework anymore.

Categories: Humor

Converting an Ext 5 Grid to Excel Spreadsheet

December 26, 2014 3 comments

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

Enjoy!

/*

    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:

    e. sdrucker@figleaf.com
    blog: druckit.wordpress.com
    linkedin: www.linkedin.com/in/uberfig
    git: http://github.com/sdruckerfig
    company: Fig Leaf Software (http://www.figleaf.com / http://training.figleaf.com)

    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/vnd.ms-excel;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
            });

            el.click();

            Ext.fly(el).destroy();

        } else {

            var form = this.down('form#uploadForm');
            if (form) {
                form.destroy();
            }
            form = this.add({
                xtype: 'form',
                itemId: 'uploadForm',
                hidden: true,
                standardSubmit: true,
                url: 'http://webapps.figleaf.com/dataservices/Excel.cfc?method=echo&mimetype=application/vnd.ms-excel&filename=' + escape(title + ".xls"),
                items: [{
                    xtype: 'hiddenfield',
                    name: 'data',
                    value: vExportContent
                }]
            });

            form.getForm().submit();

        }
    },

    /*

        Welcome to XML Hell
        See: http://msdn.microsoft.com/en-us/library/office/aa140066(v=office.10).aspx
        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="http://www.w3.org/TR/REC-html40">',
            '<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>',
            '<ProtectStructure>False</ProtectStructure>',
            '<ProtectWindows>False</ProtectWindows>',
            '</ExcelWorkbook>',

            '<Styles>',

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

            '<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>',

            '<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>',


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


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


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

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

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

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

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

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

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


            '</Styles>',
            worksheet.xml,
            '</Workbook>'
        );
    },

    /*

        Support function to return field info from store based on fieldname

    */

    getModelField: function(fieldName) {

        var fields = this.store.model.getFields();
        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"/>';
                visibleCols++;
            }
        }

        // 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 = [];
        console.log(this);
        if (this.columnManager.columns) {
            var cm = this.columnManager.columns;
        } else {
            var cm = this.columns;
        }
        console.log(cm);
        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 === "") {
                    cellType.push("None");
                    cellTypeClass.push("");
                    ++visibleColumnCountReduction;
                } 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 "Ext.data.field.Integer":
                            cellType.push("Number");
                            cellTypeClass.push("int");
                            break;
                        case "Ext.data.field.Number":
                            cellType.push("Number");
                            cellTypeClass.push("float");
                            break;
                        case "Ext.data.field.Boolean":
                            cellType.push("String");
                            cellTypeClass.push("");
                            break;
                        case "Ext.data.field.Date":
                            cellType.push("DateTime");
                            cellTypeClass.push("date");
                            break;
                        default:
                            cellType.push("String");
                            cellTypeClass.push("");
                            break;
                    }
                }
            }
        }
        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 = this.store.getCount() + 2;
        if ((this.store.groupField &&!Ext.isEmpty(this.store.groupField)) || (this.store.groupers && this.store.groupers.items.length > 0)) {
            numGridRows = numGridRows + this.store.getGroups().length;
        }

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

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

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

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

            if (!Ext.isEmpty(groupField)) {
                if (groupVal != this.store.getAt(i).get(groupField)) {
                    groupVal = this.store.getAt(i).get(groupField);
                    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 {
                            t += Ext.String.htmlEncode(v);
                        }
                        t += '</Data></Cell>';
                    }
                    k++;
                }
            }
            t += '</Row>';
        }

        result.xml = t.concat(
            '</Table>',
            '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">',
            '<PageLayoutZoom>0</PageLayoutZoom>',
            '<Selected/>',
            '<Panes>',
            '<Pane>',
            '<Number>3</Number>',
            '<ActiveRow>2</ActiveRow>',
            '</Pane>',
            '</Panes>',
            '<ProtectObjects>False</ProtectObjects>',
            '<ProtectScenarios>False</ProtectScenarios>',
            '</WorksheetOptions>',
            '</Worksheet>'
        );
        return result;
    }
});
Categories: Ext JS 5
Follow

Get every new post delivered to your Inbox.

Join 602 other followers