原文:Converting an Ext 5 Grid to Excel Spreadsheet

稍微迟来的礼物——Ext JS Grid转为Excel代码,现在支持Ext JS 5!

功能包括:
- 支持分组
- 数字的处理 VS 字符串数据类型
- 对于不支持客户端下载的浏览器会提交回服务器

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. [email protected]
    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(            '',            '',            '' + theTitle + '',            '',            '',            '' + worksheet.height + '',            '' + worksheet.width + '',            'False',            'False',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',            '',
            worksheet.xml,            ''
        );
    },    /*

        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 = '{1}';        var visibleCols = 0;        // rowXml += ''

        for (var j = 0; j < colCount; j++) {            if (cm[j].xtype != 'actioncolumn' && (cm[j].dataIndex != '') && (includeHidden || !cm[j].hidden)) {                // rowXml += '';
                visibleCols++;
            }
        }        // rowXml += "";

        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 += '';
                    headerXml += '' +                        '' + cm[i].text.replace("
"," ") + '' +                        '';                    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(            '',            '',            '',            '',            '',             colXml,            '',            '',            '',            '' + theTitle + '',            '',            '',            '',             headerXml +            ''         );        // 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 += '';            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 += '';                        if (cellType[k] == 'DateTime') {                             t += Ext.Date.format(v, 'Y-m-d');                         } else {                             t += Ext.String.htmlEncode(v);                         }                         t += '';                     }                     k++;                 }             }             t += '';         }         result.xml = t.concat(            '',            '',            '0',            '',            '',            '',            '3',            '2',            '',            '',            'False',            'False',            '',            ''         );        return result;     } });123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438

附:在原文底部有Ext JS 4版本的链接