1

I've been required to make a script for a google analytics thing. The script works completely fine but I'm running into issues with transferring it to an excel document with different worksheets.

I came across this workaround to effectively add in the tables (I am outputting a series of tables, and each table is meant to be a separate spreadsheet).

var tablesToExcel = (function() {
  var uri = 'data:application/vnd.ms-excel;base64,'
  , tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
    + '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Author>Tony Lopez</Author><Created>{created}</Created></DocumentProperties>'
    + '<Styles>'
    + '<Style ss:ID="Currency"><NumberFormat ss:Format="Currency"></NumberFormat></Style>'
    + '<Style ss:ID="Date"><NumberFormat ss:Format="Medium Date"></NumberFormat></Style>'
    + '</Styles>'
    + '{worksheets}</Workbook>'
  , tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{rows}</Table></Worksheet>'
  , tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>'
  , base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))) }
  , format = function(s, c) { return s.replace(/{(\w+)}/g, function(m, p) { return c[p]; }) }
  return function(tables, wsnames, wbname, appname) {
    var ctx = "";
    var workbookXML = "";
    var worksheetsXML = "";
    var rowsXML = "";

    for (var i = 0; i < tables.length; i++) {
      if (!tables[i].nodeType) tables[i] = document.getElementById(tables[i]);
      for (var j = 0; j < tables[i].rows.length; j++) {
        rowsXML += '<Row>'
        for (var k = 0; k < tables[i].rows[j].cells.length; k++) {
          var dataType = tables[i].rows[j].cells[k].getAttribute("data-type");
          var dataStyle = tables[i].rows[j].cells[k].getAttribute("data-style");
          var dataValue = tables[i].rows[j].cells[k].getAttribute("data-value");
          dataValue = (dataValue)?dataValue:tables[i].rows[j].cells[k].innerHTML;
          var dataFormula = tables[i].rows[j].cells[k].getAttribute("data-formula");
          dataFormula = (dataFormula)?dataFormula:(appname=='Calc' && dataType=='DateTime')?dataValue:null;
          ctx = {  attributeStyleID: (dataStyle=='Currency' || dataStyle=='Date')?' ss:StyleID="'+dataStyle+'"':''
                 , nameType: (dataType=='Number' || dataType=='DateTime' || dataType=='Boolean' || dataType=='Error')?dataType:'String'
                 , data: (dataFormula)?'':dataValue
                 , attributeFormula: (dataFormula)?' ss:Formula="'+dataFormula+'"':''
                };
          rowsXML += format(tmplCellXML, ctx);
        }
        rowsXML += '</Row>'
      }
      ctx = {rows: rowsXML, nameWS: wsnames[i] || 'Sheet' + i};
      worksheetsXML += format(tmplWorksheetXML, ctx);
      rowsXML = "";
    }

    ctx = {created: (new Date()).getTime(), worksheets: worksheetsXML};
    workbookXML = format(tmplWorkbookXML, ctx);

    var link = document.createElement("A");
    link.href = uri + base64(workbookXML);
    link.download = wbname || 'Workbook.xls';
    link.target = '_blank';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
})();

And my export as follows (will be changing the function slightly, but only needs to work on chrome)

  $('.export').on('click',function(){
    $('table').each(function(){
      var table = $(this);
      tables.push(table[0]);
      labels.push(table.find('thead th').text());
    });
    tablesToExcel(tables,labels,'TheDanApp.xls','Excel');
  });

Now onto the annoying bit, this works perfectly as desired for the data I have, except on queries that generate large amounts of data (up to 10k rows per spreadsheet), which it then crashes and shows up the aw snap page of chrome.

I have been looking into PHPExcel for a while now as well, but I can't get my head round how to make the excel document appear solely when the export button is clicked on (I want it to only do the query on click).

Here is the php I have so far

$objPHPExcel = new PHPExcel();
$cacheMethod = PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
PHPExcel_Settings::setCacheStorageMethod($cacheMethod);

$date = date('M Y',strtotime(startmonths($month,$numyears - ceil($i/12))));
$myWorkSheet = new PHPExcel_Worksheet($objPHPExcel, strval($date));
$objPHPExcel->addSheet($myWorkSheet);
$arrayData = array($rowlabels,$renamed);
$objPHPExcel->getActiveSheet()->fromArray($arrayData,NULL,'A1');

header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="danapp.xls"');
header('Cache-Control: max-age=0');

$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
$objWriter->save('php://output');

Where $renamed is the array of data I'm passing in (probably could rename it but just want this functional). And the section with the sheets is within a large for loop for the queries.

The problem with the initial method, is that this is effectively a workaround for the export function within the actual google analytics page, which doesn't have the capability to go to up to 10k links per month.

This is the final part to getting this to work, and I have until the end of tomorrow to get it working (it's my final day working here tomorrow).

The question that I am asking in conclusion, is if there is a way to convert that javascript function that I have into a php function, since it just writes into a string (albeit a strictly formatted one), so I can output that string in the link (or maybe that's the problem since the link is so long? Perhaps a better way would be to open a new window for just that document, I'm not sure, and I don't have a lot of time to muddle on it since I'm finishing off some other work at the same time).

Just had a printout of the url for one of the results that broke... I think the URL is slightly long for this method (13.5million characters.. all of them needed)

pnuts
  • 58,317
  • 11
  • 87
  • 139
Zephyr
  • 167
  • 1
  • 8
  • The short answer is Yes you can make that JavaScript into a PHP function. The JS is just formatting all the content into the page. It crashes the browser, I suspect due to memory issues. – Twisty Jul 30 '15 at 16:10
  • This should help you some: http://stackoverflow.com/questions/8449116/html-table-to-php-array – Twisty Jul 30 '15 at 17:23
  • Sorry, this doesn't help me out. And I'm pretty sure it's because the url that is generated for the data uri is way too long, which is why I want to generate the file via php temporarily, and force the user to download it then remove the file automatically (basically simulating the behaviour). The initial table is generated via a php array that is populated by google analytics api – Zephyr Jul 31 '15 at 09:27
  • How is the data added to the table? Or is this table static? Meaning, could I load that HTML in PHP, meaning I could then use DOM on it. – Twisty Jul 31 '15 at 15:09

0 Answers0