diff --git a/README.md b/README.md index 37a8320..095ee3c 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,25 @@ bower install ng-table-export ## Example -* [ngTable export to CSV](http://bazalt-cms.com/ng-table/example/15) \ No newline at end of file +* [ngTable export to CSV](http://bazalt-cms.com/ng-table/example/15) + +### This version fixes ng-table-export for Internet Explorer 9++ (maybe even 8) +### Additional Features (compared to original ng-table-export) + +* **Add possibility to specify encoding of the generated csv file.** +For example: +```html +Export to CSV + +``` +Alternatively (it's just a matter of taste): +```html +Export to CSV +
+``` +If you omit `ng-export-encoding`, you will get the default (`UTF-8`). Right now, only UTF-8 and latin1 (i.e. ISO-8859-1 is supported). + +* In case you provide a short view in a table cell (e.g. using manual truncation of a string when it is too long) but still want + the original contents to be exported, then you can do so by setting the attribute **data-fulltext** on the `
` or `` element. + If this attribute is present, its content is exported instead of the text() contents. + diff --git a/bower.json b/bower.json index 2786abc..b8bb9ba 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ng-table-export", - "version": "0.1.0", + "version": "0.2.0", "main": [ "ng-table-export.js" ], diff --git a/ng-table-export.js b/ng-table-export.js index 394e800..8dd60d6 100644 --- a/ng-table-export.js +++ b/ng-table-export.js @@ -1,3 +1,4 @@ /*! ngTableExport v0.1.0 by Vitalii Savchuk(esvit666@gmail.com) - https://github.com/esvit/ng-table-export - New BSD License */ -angular.module("ngTableExport",[]).config(["$compileProvider",function(a){a.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|data):/)}]).directive("exportCsv",["$parse",function(a){return{restrict:"A",scope:!1,link:function(b,c,d){var e="",f={stringify:function(a){return'"'+a.replace(/^\s\s*/,"").replace(/\s*\s$/,"").replace(/"/g,'""')+'"'},generate:function(){e="";var a=c.find("tr");angular.forEach(a,function(a,b){var c=angular.element(a),d=c.find("th"),g="";c.hasClass("ng-table-filters")||(0==d.length&&(d=c.find("td")),1!=b&&(angular.forEach(d,function(a){g+=f.stringify(angular.element(a).text())+";"}),g=g.slice(0,g.length-1)),e+=g+"\n")})},link:function(){return"data:text/csv;charset=UTF-8,"+encodeURIComponent(e)}};a(d.exportCsv).assign(b.$parent,f)}}}]); + +angular.module("ngTableExport",[]).config(["$compileProvider",function(a){a.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|data):/)}]).directive("exportCsv",["$parse",function(a){return{restrict:"A",scope:!1,link:function(b,c,d){function e(){console.error("ng table export: invalid encoding requested. only 'UTF-8' and 'ISO-8859-1' are supported")}function f(){var a=k?k:d.exportCsvEncoding;return a?a.match(/utf/)?"UTF-8":"ISO-8859-1":"UTF-8"}function g(){for(var a=j,b=new ArrayBuffer(a.length),c=new Uint8Array(b),d=0;d256&&(e=182),c[d]=e}var f=new Blob([c]);return f}function h(){return new Blob([j])}var i={stringify:function(a){return'"'+a.replace(/^\s\s*/,"").replace(/\s*\s$/,"").replace(/"/g,'""')+'"'},extractData:function(){j="";var a=c.find("tr");angular.forEach(a,function(a,b){var c=angular.element(a),d=c.find("th"),e="";c.hasClass("ng-table-filters")||(0==d.length&&(d=c.find("td")),1!=b&&(angular.forEach(d,function(a,b){e+=a.hasAttribute("data-fulltext")?i.stringify(a.getAttribute("data-fulltext"))+";":i.stringify(angular.element(a).text())+";"}),e=e.slice(0,e.length-1)),j+=e+"\n")})},generate:function(a,b){k=b,i.extractData(),i.generateIE(a)},generateIE:function(a){if(window.navigator.msSaveOrOpenBlob){var b,c,d=f();"UTF-8"===d?b=h():"ISO-8859-1"===d?b=g():e(),a&&a.preventDefault?(c=a.target.attributes.download.value,window.navigator.msSaveOrOpenBlob(b,c),a.preventDefault()):console.warn('ng table export: you should pass over the $event in your expression for proper IE support, e.g. via ng-click="csv.generate($event)" --- otherwise the href will be followed')}},link:function(){var a=f();return"UTF-8"===a?"data:text/csv;charset=UTF-8,"+encodeURIComponent(j):"ISO-8859-1"===a?"data:text/csv;charset=ISO-8859-1,"+escape(j):void e()}};a(d.exportCsv).assign(b.$parent,i);var j="",k=""}}}]); //# sourceMappingURL=ng-table-export.map \ No newline at end of file diff --git a/ng-table-export.map b/ng-table-export.map index 3f37888..87c9241 100644 --- a/ng-table-export.map +++ b/ng-table-export.map @@ -1 +1 @@ -{"version":3,"file":"ng-table-export.js","sources":["ng-table-export.src.js"],"names":["angular","module","config","$compileProvider","aHrefSanitizationWhitelist","directive","$parse","restrict","scope","link","element","attrs","data","csv","stringify","str","replace","generate","rows","find","forEach","row","i","tr","tds","rowData","hasClass","length","td","text","slice","encodeURIComponent","exportCsv","assign","$parent"],"mappings":"AAAAA,QAAQC,OAAO,oBACdC,QAAQ,mBAAoB,SAASC,GAElCA,EAAiBC,2BAA2B,oCAE/CC,UAAU,aAAc,SAAU,SAAUC,GACzC,OACIC,SAAU,IACVC,OAAO,EACPC,KAAM,SAASD,EAAOE,EAASC,GAC3B,GAAIC,GAAO,GACPC,GACAC,UAAW,SAASC,GAChB,MAAO,IACHA,EAAIC,QAAQ,SAAU,IAAIA,QAAQ,SAAU,IACvCA,QAAQ,KAAK,MAClB,KAERC,SAAU,WACNL,EAAO,EACP,IAAIM,GAAOR,EAAQS,KAAK,KACxBnB,SAAQoB,QAAQF,EAAM,SAASG,EAAKC,GAChC,GAAIC,GAAKvB,QAAQU,QAAQW,GACrBG,EAAMD,EAAGJ,KAAK,MACdM,EAAU,EACVF,GAAGG,SAAS,sBAGE,GAAdF,EAAIG,SACJH,EAAMD,EAAGJ,KAAK,OAET,GAALG,IACAtB,QAAQoB,QAAQI,EAAK,SAASI,GAC1BH,GAAWZ,EAAIC,UAAUd,QAAQU,QAAQkB,GAAIC,QAAU,MAE3DJ,EAAUA,EAAQK,MAAM,EAAGL,EAAQE,OAAS,IAEhDf,GAAQa,EAAU,SAG1BhB,KAAM,WACF,MAAO,+BAAiCsB,mBAAmBnB,IAGnEN,GAAOK,EAAMqB,WAAWC,OAAOzB,EAAM0B,QAASrB"} \ No newline at end of file +{"version":3,"file":"ng-table-export.js","sources":["ng-table-export.src.js"],"names":["angular","module","config","$compileProvider","aHrefSanitizationWhitelist","directive","$parse","restrict","scope","link","element","attrs","_logErrorEncodingNotSupported","console","error","_getRequestedEncoding","enc","encodingOverwrite","exportCsvEncoding","match","_transformUtf8ToLatin1AndGetBlob","utf8Text","data","buffer","ArrayBuffer","length","uint8Array","Uint8Array","i","charCode","charCodeAt","blobObject","Blob","_getUtf8Blob","csv","stringify","str","replace","extractData","rows","find","forEach","row","tr","tds","rowData","hasClass","td","hasAttribute","getAttribute","text","slice","generate","$event","encoding","generateIE","window","navigator","msSaveOrOpenBlob","filename","preventDefault","target","attributes","value","warn","encodeURIComponent","escape","exportCsv","assign","$parent"],"mappings":";;AAAAA,QAAQC,OAAO,oBACdC,QAAQ,mBAAoB,SAASC,GAElCA,EAAiBC,2BAA2B,oCAE/CC,UAAU,aAAc,SAAU,SAAUC,GACzC,OACIC,SAAU,IACVC,OAAO,EACPC,KAAM,SAASD,EAAOE,EAASC,GA8G3B,QAASC,KACLC,QAAQC,MAAM,4FAYlB,QAASC,KACL,GAAIC,GAAMC,EAAoBA,EAAoBN,EAAMO,iBACxD,OAAKF,GAGGA,EAAIG,MAAM,OACH,QAEA,aALJ,QAUf,QAASC,KAKL,IAAK,GAJDC,GAAWC,EACXC,EAAS,GAAIC,aAAYH,EAASI,QAClCC,EAAa,GAAIC,YAAWJ,GAEvBK,EAAI,EAAGA,EAAIF,EAAWD,OAAQG,IAAK,CACxC,GAAIC,GAAWR,EAASO,GAAGE,WAAW,EAClCD,GAAW,MAGXA,EAAW,KAEfH,EAAWE,GAAKC,EAEpB,GAAIE,GAAa,GAAIC,OAAMN,GAC3B,OAAOK,GAGX,QAASE,KACL,MAAO,IAAID,OAAMV,IAnJrB,GAAIY,IACAC,UAAW,SAAUC,GACjB,MAAO,IAAMA,EAAIC,QAAQ,SAAU,IAAIA,QAAQ,SAAU,IACpDA,QAAQ,KAAM,MACf,KAERC,YAAa,WACThB,EAAO,EACP,IAAIiB,GAAO7B,EAAQ8B,KAAK,KACxBxC,SAAQyC,QAAQF,EAAM,SAAUG,EAAKd,GACjC,GAAIe,GAAK3C,QAAQU,QAAQgC,GAAME,EAAMD,EAAGH,KAAK,MAAOK,EAAU,EAC1DF,GAAGG,SAAS,sBAGE,GAAdF,EAAInB,SACJmB,EAAMD,EAAGH,KAAK,OAET,GAALZ,IACA5B,QAAQyC,QAAQG,EAAK,SAAUG,EAAInB,GAE3BiB,GADAE,EAAGC,aAAa,iBACLd,EAAIC,UAAUY,EAAGE,aAAa,kBAAoB,IAElDf,EAAIC,UAAUnC,QAAQU,QAAQqC,GAAIG,QAAU,MAG/DL,EAAUA,EAAQM,MAAM,EAAGN,EAAQpB,OAAS,IAEhDH,GAAQuB,EAAU,SAU1BO,SAAU,SAAUC,EAAQC,GACxBrC,EAAoBqC,EAGpBpB,EAAII,cAKJJ,EAAIqB,WAAWF,IAEnBE,WAAY,SAAUF,GAClB,GAAIG,OAAOC,UAAUC,iBAAkB,CACnC,GACI3B,GACA4B,EAFAL,EAAWvC,GAOE,WAAbuC,EACAvB,EAAaE,IAEO,eAAbqB,EACPvB,EAAaX,IAGbR,IAIAyC,GAAUA,EAAOO,gBAEjBD,EAAWN,EAAOQ,OAAOC,WAAqB,SAAEC,MAChDP,OAAOC,UAAUC,iBAAiB3B,EAAY4B,GAC9CN,EAAOO,kBAGP/C,QAAQmD,KAAK,iLAKzBvD,KAAM,WACF,GAAI6C,GAAWvC,GACf,OAAiB,UAAbuC,EACO,+BAAiCW,mBAAmB3C,GAEvC,eAAbgC,EACA,oCAAsCY,OAAO5C,OAGpDV,MAIZN,GAAOK,EAAMwD,WAAWC,OAAO5D,EAAM6D,QAASnC,EAM9C,IAAIZ,GAAO,GACPL,EAAoB"} \ No newline at end of file diff --git a/ng-table-export.src.js b/ng-table-export.src.js index 4649845..cf0a64e 100644 --- a/ng-table-export.src.js +++ b/ng-table-export.src.js @@ -1,28 +1,33 @@ angular.module('ngTableExport', []) + .config(['$compileProvider', function($compileProvider) { // allow data links $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|data):/); }]) + .directive('exportCsv', ['$parse', function ($parse) { return { restrict: 'A', scope: false, link: function(scope, element, attrs) { - var data = ''; + + /******************************************************************************************** + * Public stuff: + * + * csv will be exposed within the PARENT scope. + * This will make it available to siblings of the tag. + */ var csv = { - stringify: function(str) { - return '"' + - str.replace(/^\s\s*/, '').replace(/\s*\s$/, '') // trim spaces - .replace(/"/g,'""') + // replace quotes with double quotes + stringify: function (str) { + return '"' + str.replace(/^\s\s*/, '').replace(/\s*\s$/, '') // trim spaces + .replace(/"/g, '""') + // replace quotes with double quotes '"'; }, - generate: function() { + extractData: function () { data = ''; var rows = element.find('tr'); - angular.forEach(rows, function(row, i) { - var tr = angular.element(row), - tds = tr.find('th'), - rowData = ''; + angular.forEach(rows, function (row, i) { + var tr = angular.element(row), tds = tr.find('th'), rowData = ''; if (tr.hasClass('ng-table-filters')) { return; } @@ -30,19 +35,137 @@ angular.module('ngTableExport', []) tds = tr.find('td'); } if (i != 1) { - angular.forEach(tds, function(td, i) { - rowData += csv.stringify(angular.element(td).text()) + ';'; + angular.forEach(tds, function (td, i) { + if (td.hasAttribute('data-fulltext')) { + rowData += csv.stringify(td.getAttribute('data-fulltext')) + ';'; + } else { + rowData += csv.stringify(angular.element(td).text()) + ';'; + } }); rowData = rowData.slice(0, rowData.length - 1); //remove last semicolon } data += rowData + "\n"; }); }, - link: function() { - return 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); + /** + * @param $event Handing over the $event is necessary to properly cancel event propagation in IE + * @param encoding If you supply this parameter, it will override the csv-export-encoding attribute + * you could alternatively have supplied together with csv-export on the table element. + * This alternative helps you in reducing code-changes in case you have the download-button + * put into a template which you re-use everywhere where you're using ng-table + */ + generate: function ($event, encoding) { + encodingOverwrite = encoding; + + /** all browsers need to have the data prepared ...*/ + csv.extractData(); + + /* ... but only IE will start the download here, that's why we pass $event so the normal behaviour + of following the href can be preventDefaulted. All other browsers will have the download triggered + through this normal behaviour of visiting the href ... that's where the link() function takes over */ + csv.generateIE($event); + }, + generateIE: function ($event) { + if (window.navigator.msSaveOrOpenBlob) { + var encoding = _getRequestedEncoding(), + blobObject, + filename; + + /** + * The following logic depends on the "data" string to be UTF-8. This should usually be the case. + */ + if (encoding === 'UTF-8') { + blobObject = _getUtf8Blob(); + + } else if (encoding === 'ISO-8859-1') { + blobObject = _transformUtf8ToLatin1AndGetBlob(); + + } else { + _logErrorEncodingNotSupported(); + } + + /** we need $event also to collect the filename from the "download" attribute */ + if ($event && $event.preventDefault) { + + filename = $event.target.attributes['download'].value; + window.navigator.msSaveOrOpenBlob(blobObject, filename); + $event.preventDefault(); // href must not be followed + + } else { + console.warn('ng table export: you should pass over the $event in your expression for proper IE support, ' + + 'e.g. via ng-click="csv.generate($event)" --- otherwise the href will be followed'); + } + } + }, + link: function () { + var encoding = _getRequestedEncoding(); + if (encoding === 'UTF-8') { + return 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); + + } else if (encoding === 'ISO-8859-1') { + return 'data:text/csv;charset=ISO-8859-1,' + escape(data); + + } else { + _logErrorEncodingNotSupported(); + } } }; $parse(attrs.exportCsv).assign(scope.$parent, csv); + + + /******************************************************************************************** + * Private stuff here + */ + var data = '', + encodingOverwrite = ''; + + function _logErrorEncodingNotSupported() { + console.error("ng table export: invalid encoding requested. only 'UTF-8' and 'ISO-8859-1' are supported"); + } + + /** + * Returns the requested encoding. + * + * To change the encoding from the default value (UTF-8) to latin1, + * do it like so:
+ * + * Actually at the moment, it does not matter what you insert into export-csv-encoding. It will always be + * interpreted as latin1 (i.e. ISO-8859-1) as long as the value does not match to "utf". + */ + function _getRequestedEncoding() { + var enc = encodingOverwrite ? encodingOverwrite : attrs.exportCsvEncoding; + if (!enc) { + return 'UTF-8'; + } else { + if (enc.match(/utf/)) { + return 'UTF-8'; + } else { + return 'ISO-8859-1'; + } + } + } + + function _transformUtf8ToLatin1AndGetBlob() { + var utf8Text = data, + buffer = new ArrayBuffer(utf8Text.length), + uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < uint8Array.length; i++) { + var charCode = utf8Text[i].charCodeAt(0); + if (charCode > 256) { // latin1 goes only from charCode 1 to 256 + // that's why we set it to a predefined latin1 char which is easily recognizable as "this char has no representation in latin1" + // we "use" the paragraph sign at position %B6 (or 182 in decimal notation): ¶ + charCode = 182; // ¶ + } + uint8Array[i] = charCode; // will be transformed to correct latin1 binary representation + } + var blobObject = new Blob([uint8Array]); + return blobObject; + } + + function _getUtf8Blob() { + return new Blob([data]); + } } }; }]); \ No newline at end of file diff --git a/package.json b/package.json index 26804bf..2f28c02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ng-table-export", - "version": "0.1.0", + "version": "0.2.0", "author": "Vitalii Savchuk ", "license": "BSD", "repository": { diff --git a/src/scripts/00-directive.js b/src/scripts/00-directive.js index 4649845..6f865af 100644 --- a/src/scripts/00-directive.js +++ b/src/scripts/00-directive.js @@ -8,21 +8,24 @@ angular.module('ngTableExport', []) restrict: 'A', scope: false, link: function(scope, element, attrs) { - var data = ''; + + /******************************************************************************************** + * Public stuff: + * + * csv will be exposed within the PARENT scope. + * This will make it available to siblings of the
tag. + */ var csv = { - stringify: function(str) { - return '"' + - str.replace(/^\s\s*/, '').replace(/\s*\s$/, '') // trim spaces - .replace(/"/g,'""') + // replace quotes with double quotes + stringify: function (str) { + return '"' + str.replace(/^\s\s*/, '').replace(/\s*\s$/, '') // trim spaces + .replace(/"/g, '""') + // replace quotes with double quotes '"'; }, - generate: function() { + extractData: function () { data = ''; var rows = element.find('tr'); - angular.forEach(rows, function(row, i) { - var tr = angular.element(row), - tds = tr.find('th'), - rowData = ''; + angular.forEach(rows, function (row, i) { + var tr = angular.element(row), tds = tr.find('th'), rowData = ''; if (tr.hasClass('ng-table-filters')) { return; } @@ -30,19 +33,137 @@ angular.module('ngTableExport', []) tds = tr.find('td'); } if (i != 1) { - angular.forEach(tds, function(td, i) { - rowData += csv.stringify(angular.element(td).text()) + ';'; + angular.forEach(tds, function (td, i) { + if (td.hasAttribute('data-fulltext')) { + rowData += csv.stringify(td.getAttribute('data-fulltext')) + ';'; + } else { + rowData += csv.stringify(angular.element(td).text()) + ';'; + } }); rowData = rowData.slice(0, rowData.length - 1); //remove last semicolon } data += rowData + "\n"; }); }, - link: function() { - return 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); + /** + * @param $event Handing over the $event is necessary to properly cancel event propagation in IE + * @param encoding If you supply this parameter, it will override the csv-export-encoding attribute + * you could alternatively have supplied together with csv-export on the table element. + * This alternative helps you in reducing code-changes in case you have the download-button + * put into a template which you re-use everywhere where you're using ng-table + */ + generate: function ($event, encoding) { + encodingOverwrite = encoding; + + /** all browsers need to have the data prepared ...*/ + csv.extractData(); + + /* ... but only IE will start the download here, that's why we pass $event so the normal behaviour + of following the href can be preventDefaulted. All other browsers will have the download triggered + through this normal behaviour of visiting the href ... that's where the link() function takes over */ + csv.generateIE($event); + }, + generateIE: function ($event) { + if (window.navigator.msSaveOrOpenBlob) { + var encoding = _getRequestedEncoding(), + blobObject, + filename; + + /** + * The following logic depends on the "data" string to be UTF-8. This should usually be the case. + */ + if (encoding === 'UTF-8') { + blobObject = _getUtf8Blob(); + + } else if (encoding === 'ISO-8859-1') { + blobObject = _transformUtf8ToLatin1AndGetBlob(); + + } else { + _logErrorEncodingNotSupported(); + } + + /** we need $event also to collect the filename from the "download" attribute */ + if ($event && $event.preventDefault) { + + filename = $event.target.attributes['download'].value; + window.navigator.msSaveOrOpenBlob(blobObject, filename); + $event.preventDefault(); // href must not be followed + + } else { + console.warn('ng table export: you should pass over the $event in your expression for proper IE support, ' + + 'e.g. via ng-click="csv.generate($event)" --- otherwise the href will be followed'); + } + } + }, + link: function () { + var encoding = _getRequestedEncoding(); + if (encoding === 'UTF-8') { + return 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); + + } else if (encoding === 'ISO-8859-1') { + return 'data:text/csv;charset=ISO-8859-1,' + escape(data); + + } else { + _logErrorEncodingNotSupported(); + } } }; $parse(attrs.exportCsv).assign(scope.$parent, csv); + + + /******************************************************************************************** + * Private stuff here + */ + var data = '', + encodingOverwrite = ''; + + function _logErrorEncodingNotSupported() { + console.error("ng table export: invalid encoding requested. only 'UTF-8' and 'ISO-8859-1' are supported"); + } + + /** + * Returns the requested encoding. + * + * To change the encoding from the default value (UTF-8) to latin1, + * do it like so:
+ * + * Actually at the moment, it does not matter what you insert into export-csv-encoding. It will always be + * interpreted as latin1 (i.e. ISO-8859-1) as long as the value does not match to "utf". + */ + function _getRequestedEncoding() { + var enc = encodingOverwrite ? encodingOverwrite : attrs.exportCsvEncoding; + if (!enc) { + return 'UTF-8'; + } else { + if (enc.match(/utf/)) { + return 'UTF-8'; + } else { + return 'ISO-8859-1'; + } + } + } + + function _transformUtf8ToLatin1AndGetBlob() { + var utf8Text = data, + buffer = new ArrayBuffer(utf8Text.length), + uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < uint8Array.length; i++) { + var charCode = utf8Text[i].charCodeAt(0); + if (charCode > 256) { // latin1 goes only from charCode 1 to 256 + // that's why we set it to a predefined latin1 char which is easily recognizable as "this char has no representation in latin1" + // we "use" the paragraph sign at position %B6 (or 182 in decimal notation): ¶ + charCode = 182; // ¶ + } + uint8Array[i] = charCode; // will be transformed to correct latin1 binary representation + } + var blobObject = new Blob([uint8Array]); + return blobObject; + } + + function _getUtf8Blob() { + return new Blob([data]); + } } }; }]); \ No newline at end of file