Suggestive JavaScript save file name using Blob

If you’ve ever wondered how to suggest a file name using JavaScript, I found a method that’s proven to work. Using a Blob, some AJAX and copying an example provided by Google I came up with the following code. I used this successfully within a GreaseMonkey script to be able to automate saving files off, but it should work universally. the only hurdle would be saving files that cross the domain boundary (where GM_xmlhttpRequest can bypass this boundary natively, where as a traditional AJAX request cannot). With that said, here is the code:

  // Here we setup the path to the content (to be retrieved using AJAX)
  var contentUrl  = "/path/to/file.jpg";

  // Helper function that's going to take the data and bundle it in
  // to a blob using whatever Blob method available for the current
  // client's browser
  function dataToBlob(data,mimeString){
    // convert data to ArrayBuffer
    var buffer = new Int8Array(new ArrayBuffer(data.length));
    for (var i = 0; i < data.length; i++){
      buffer[i] = data.charCodeAt(i) & 0xff;
    }

    // http://stackoverflow.com/a/15302872/298053
    try {
      return new Blob([buffer],{type:mimeString});
    } catch (e1) {
      try {
        var BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
        if (e.name == 'TypeError' && window.BlobBuilder){
          bb = new BlobBuilder();
          bb.append([buffer.buffer]);
          return bb.getBlob(mimeString);
        } else if (e.name == 'InvalidStateError'){
          return new Blob([buffer.buffer],{type:mimeString});
        }
      } catch (e2) {
      }
    }
    return null;
  }

  // Perform the actual download (remember that GreaseMonkey can cross domain
  // boundaries. if you find this doesn't work when you port it to an $.ajax
  // call, make sure you're on the same domain (or using JSONP)!
  GM_xmlhttpRequest({
    'method': 'GET',
    'url': contentUrl, // we assign this above
    'overrideMimeType': 'text/plain; charset=x-user-defined',
    'onload': function(response){
      // try to get as much of the metadata from the response as possible. this way
      // all we really need is the contentURL and the rest will come.
      var fileName = response.finalUrl.replace(/^.*[\\\/]/, ''),
          mimeString = response.responseHeaders.match(/^Content-Type: (\w+\/\w+)$/m)[1];

      var blob = dataToBlob(response.responseText, mimeString);
      if (blob){
        // if we were able to process it to a blob, let's create an anchor we can
        // assign the blob to. You can either create a new anchor, or assign it to
        // an existing one. if you do use an existing one, just make sure you set
        // the download, href & dataset.downloadurl properties!
        var a = document.createElement('a');
        a.download = fileName; // or whatever you want to name it
        a.href = (window.webkitURL || window.URL).createObjectURL(blob);
        a.innerHTML = 'Click to save ' + fileName;
        a.dataset.downloadurl = [mimeString, a.download, a.href].join(':');
        a.draggable = true;
        a.style.color = 'white';
        document.body.apendChild(a);
      } else {
        // warn user it's unavailable or do nothing--it's up to you
      }
    }
  });

All of this could just as easily be assigned to a click function (instead of at page load). It’s up to you how you want to implement it.

Flattr this!