【翻译】将Ext JS Grid转换为Excel表格

原文: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. 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(/
/g, "
");
        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;
    }
});

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

文章导航