Tag Archives: Javascript

JavaScript: In-browser export to CSV

This function will turn an array of data into a CSV file and get the browser to “download” it. It should also be wrapping and escaping values properly and, because of the BOM, be read correctly when opened in e.g. Excel.

It’s written in TypeScript ("lib": ["ES2017", "DOM"]), but should be easy to “downgrade” to regular JS if needed…

type GetValue <T> = <I extends T>(item: I) => any;
type FieldName <T> = keyof T;

export interface Columns<T> {
  [s: string]: FieldName<T> | GetValue<T>;
}

const COLUMN_SEPARATOR = ';';
const ROW_SEPARATOR = '\r\n';
const UNICODE_BOM = '\uFEFF';

const wrapValue = (value: string) => `"${value}"`;
const escapeValue = (value: string) => (value || '').replace(/"/, '""');

const toHeaderRow = <T> (columns: Columns<T>) => Object
  .keys(columns)
  .map(escapeValue)
  .map(wrapValue)
  .join(COLUMN_SEPARATOR);

const toRow = <T> (columns: Columns<T>, item: T) => Object
  .values(columns)
  .map(field => typeof field === 'function' ? field(item) : item[field])
  .map(String)
  .map(escapeValue)
  .map(wrapValue)
  .join(COLUMN_SEPARATOR);

export const exportToCsv = function <T> (data: T[], columns: Columns<T>, filename: string): void {
  const rows = [];

  rows.push(toHeaderRow(columns));

  for (const item of data) {
    rows.push(toRow(columns, item));
  }

  const csv = UNICODE_BOM + rows.join(ROW_SEPARATOR);
  const uri = `data:text/csv;charset=utf-8;header=present,${encodeURIComponent(csv)}`;

  const link = document.createElement('a');
  link.setAttribute('href', uri);
  link.setAttribute('download', filename);
  link.addEventListener('click', () => link.parentNode.removeChild(link));
  document.body.appendChild(link);

  link.click();
};

Usage

const users: User[] = [{id: 1, name: 'Alice', isCool: true}, ...];
const columns: Columns<User> = {
  'Id': 'id',
  'Name': 'name',
  'Is cool': user => user.isCool ? 'Yes' : 'No',
};
exportToCsv(users, columns, 'users.csv');

jQuery: How to extract a tag from an HTML response

Making a website, and using ajax for some things. Sometimes things fail and return custom error pages. I made them to be helpful, but since you can only see them in the browser developer console, they were a bit of a hassle to look at.

To see what the error was much easier, I figured I could just parse the returned HTML, extract the message I knew was there, and insert it into the page that way.

And you’d think the following would work fine:

$(document).ajaxError(function(event, jqxhr, settings, error)
{
    // Find the message in the response HTML
    var m = $(jqxhr.responseText)
        .find('#message');

    // Except .find() doesn't find anything

    // And we replace our DOM with nothing
    $('#content')
        .replaceWith(m);
});

But… No… Apparently, since the response was a complete HTML page, i.e. including html, head and body tags, jQuery was getting a bit tricked up when trying to parse it. Actually not sure if it’s jQuery or native browser parsing behind that’s causing it, but where there’s a will, there’s a way:

$(document).ajaxError(function(event, jqxhr, settings, error)
{
    // Find the inner HTML of the body tag
    var body = /<body.*>([\s\S]+)<\/body>/
        .exec(jqxhr.responseText);

    // Parse the HTML
    body = $.parseHTML(body[1])

    // Append the HTML to a non-special root tag
    body = $('<output>').append(body);

    // And *now* we can finally find our message
    var message = body.find('#message');

    // And add it to our DOM
    $('#content')
        .replaceWith(m);
});

¯\_(ツ)_/¯

PHP: Easy Javascript compression with closure compiler

Here’s how to fairly easily compress/minify Javascript by using the Closure Compiler Service API.

More or less verbatim how I’m currently doing it over at bibelstudiet.no in my script controller class, and so far it’s been working great. Just remember to cache the result on your server so that you’re not calling the API more than necessary. In my case it’s handled further up the chain by the parent class 🙂

<?php
ob_start('ob_gzhandler');
header('Content-Type: text/javascript; charset=utf-8');

$files = ['foo.js', 'bar.js'];

$js = array_map('file_get_contents', $files);
$js = implode(PHP_EOL.PHP_EOL, $script);

$c = curl_init();
curl_setopt_array($ch, array
(
    CURLOPT_URL => 'https://closure-compiler.appspot.com/compile',
    CURLOPT_POST => TRUE,
    CURLOPT_POSTFIELDS => http_build_query([
        'language' => 'ECMASCRIPT5',
        'output_info' => 'compiled_code',
        'output_format' => 'text',
        'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
        'js_code' => $js,
    ]),
));

if(curl_exec($ch) === FALSE)
    http_response_code(500) and exit("// ERROR: ".curl_error($c));

if(curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD ) <= 1)
    echo $js;

curl_close($ch);

Note that if the compilation fails for any reason, the response will be empty. In that case I’ve chosen to output the original Javascript instead so the site doesn’t suddenly break. To check what went wrong, you need to repeat the request, but output_info set to errors instead of inline=”true”>compiled_code.

See their API documentation for more information about the parameters.