Get xlsx file from backend - Adapter config

I’m able to get an xlsx file from my rails backend with a GET-Request to “/companies/export_xslx”, now I’m facing the problem of getting the file passed the JSON parser. For every request the console shows “JSON.parse: unexpected character at line 1 column 1 of the JSON data”.

This is my setup:

//company model ...
exportXlsx: function() {
  const adapter = this.store.adapterFor('company');
  return adapter.exportXlsx();
}

//adapters/company.js
import DS from 'ember-data';
import TokenAuthorizerMixin from 'ember-simple-auth-token/mixins/token-authorizer';

export default DS.JSONAPIAdapter.extend(TokenAuthorizerMixin, {

  exportXlsx() {
    const url = 'companies/export_xlsx';
    return this.ajax(url, 'GET',
      { dataType: 'text',
         accepts: { xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
       } });
  }
});

I’ll try to alter the default accept header but the requests gets sent with “Accept: application/vnd.api+json”.

I already tried different approaches with “ember-custon-actions” or “ember-cli-file-saver”, they all failed with the JSON.parse… response.

I’ve found a solution. I tackle the problem in the component with a download service:

// components/companies-download.js
import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({

  download: service(),

  actions: {

    downloadXlsx() {
      let url = `/companies/export_xlsx`;
      this.get('download').file(url);
    }

  }
});



// services/download.js
import Service from "@ember/service";
import { inject as service } from '@ember/service';

export default Service.extend({

  session: service(),

  file(url) {
    let xhr = new XMLHttpRequest();
    xhr.responseType = 'blob';
    xhr.onload = () => {
      let [, fileName] = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(
        xhr.getResponseHeader("Content-Disposition")
      );
      let file = new File([xhr.response], decodeURIComponent(fileName));
      let link = document.createElement('a');
      link.style.display = 'none';
      link.href = URL.createObjectURL(file);
      link.download = file.name;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    };
    xhr.open('GET', url);
    xhr.setRequestHeader(
      'Authorization',
      'Bearer ' + this.get('session.data.authenticated.token')
    );
    xhr.send();
  }
});

Yes, fetching the file directly without using ember-data is the right idea.

I would suggest not manipulating the DOM from the service. Instead, expose the URL from the service and render the <a> in the more usual way from inside a template.

Also, this example code might not tell the whole story, but I’m not sure I see a reason to fetch the file as a blob only to give people a link to it. Wouldn’t it work the same if you just give them a link to the original URL?

If you’re trying to get the file to download instead of navigating to it, the nicest solution is the download attribute, which works for all our supported browsers except IE11.