Export a Mongo Query to CSV (client-side)

Hello,

I have multiple mongo.db queries, one example being:

return Trips.find({ $or: [ { "a": { $eq: companySelected } },  { "b": { $eq: companySelected } }], "year": {$eq: yearNum}, "car": {$eq: carSelected}, "month": { $eq: monthNumber},  "userId" : {$eq: Meteor.userId()}}, {sort:{createdAt: -1}});

Everything is cool and all, and it’s great, however, I have a Export to CSV button available for users too, I want them to be able to export this query to CSV. How do I go about that?

My html

 {{#if showHistory}}
 <h2> History </h2>

  <form class="history">

<table>

<tr>
<td>
 <h3>Month</h3>
  <select class="form-control month-filter">
    <option value="0">Any</option>
    <option value="1">January</option>
    <option value="2">Februray</option>
    <option value="3">March</option>
    <option value="4">April</option>
    <option value="5">May</option>F
    <option value="6">June</option>
    <option value="7">July</option>
    <option value="8">August</option>
    <option value="9">September</option>
    <option value="10">October</option>
    <option value="11">November</option>
    <option value="12">December</option>
 </select>
</td>

<td>
 <h3>Car</h3>
  <select class="form-control carfilter" name="carfilter">
      <option value="anycar">Any</option>
     {{#each cars_db}}
    <option value={{carNum}}>{{carNum}}</option>
    {{/each}}
 </select>
</td>

<td>
 <h3>Client</h3>
  <select class="form-control corpfilter" name="corpfilter">
  <option class="anycorp" value="anycorp">Any</option>
    {{#each corps_db}}
    <option value={{name}}>{{name}}</option>
    {{/each}}
 </select>



</td>
</tr>



<tr>
<td>
 <h3>Filter by Year</h3>
 <input class="form-control input-lg year-filter" name="yearfilter" type="text" value={{filter_year}} placeholder="Year"/> 
</td>


</tr>


</table>



 

<button type="submit" class="btn btn-success btn-block btn-search" name="btn-search">Search</button>
<button type="button" class="btn btn-info btn-block" name="btn-export">Export to CSV</button>

You need a big papa to help you out :wink:
Atmosphere package – https://atmospherejs.com/harrison/papa-parse
Documentation – http://papaparse.com/

Here my goto method of doing this sort of thing

const csvString = Papa.unparse(myQueryObject); // unparse generates CSV from your Object
const a = document.createElement('a');  // create a simple link to a resource where your payload is your encoded CSV
a.href = `data:text/csv;charset=utf-8,${encodeURIComponent(csvString)}`;
a.download = 'query.csv'; // or whatever you wanna call it
a.click();

Fair warning, last I checked, this doesn’t work for very LARGE files on Google Chrome for some reason…but you shouldn’t have a problem for your use case.

1 Like

I understand that I need the myQueryObject, so I tried this

  	const csvString = Papa.unparse(Trips.find({"year": {$eq: yearNum}, "userId" : {$eq: Meteor.userId()} } , {sort:{createdAt: -1}})); // unparse generates CSV from your Object
const a = document.createElement('a');  // create a simple link to a resource where your payload is your encoded CSV
a.href = `data:text/csv;charset=utf-8,${encodeURIComponent(csvString)}`;
a.download = 'query.csv'; // or whatever you wanna call it
a.click();

For some reason the CSV is empty, also how do I store that query output as an object, so I can use it later on?

You said you wanted to store the query object not the results. Also, make sure to read the docs carefully, because Collection.find() returns a Mongo cursor that points to your data, not your actual results.

To get what you actually want, you need to do Trips.find(yourQuery).fetch(), which fetches the results of the query from the returned cursor.

1 Like

How do I store “yourQuery” if my query for example is this:

 return Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "car": {$eq: carSelected}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});

to i store it as a string variable? as

var yourQuery = "{"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "car": {$eq: carSelected}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}}";

 return Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "car": {$eq: carSelected}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});

Here’s how I did it in my application.

package used:

harrison:papa-parse
pfafman:filesaver

function

_onClick() { 
    const arrayParts = this.props.parts;   //Sku.find().fetch()
    const csv = Papa.unparse({
      fields: [
        "partNumber",
        "description",
        "manufacturer",
        "unit",
        "stockLocation",
        "reOrderQty",
        "maxStockQty",
        "createdBy",
        "createdAt"
      ],
      data: arrayParts
    });

      const blob = new Blob([csv], { type: "text/plain;charset=utf-8;" });
      saveAs(blob, "Sku-Report.csv");
    
  }
4 Likes

What exactly is props.parts? Is that a variable? How did you declare it?

You can convert Objects to Strings using JSON.stringify(anObject).

Yeah +1 it’s a better idea to use a library like pfafman:filesaver or https://github.com/eligrey/FileSaver.js/ because just hacking together an <a> tag with a big payload has inconsistent results across different browsers.

1 Like

its the data I received from my data container… You see I used React + createContainer…
In your case replace it with you own collection like this

Trips.find().fetch()

1 Like

clinical:csv has an isomorphic API for importing/exporting CSV files, based on evaisse:csv and PapaParse.

4 Likes

Any idea why if I use Trips.find().fetch(), I get everything while using

default: //month selected
	    if(companySelected == "anycorp"){
	    	if(carSelected == "anycar") {
	    		arrayParts = Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});
    return Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});
} else {
	arrayParts = Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "car": {$eq: carSelected}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});
	    return Trips.find({"month": { $eq: monthNumber}, "userId" : {$eq: Meteor.userId()}, "car": {$eq: carSelected}, "year": {$eq: yearNum}} , {sort:{createdAt: -1}});
      } } else {
       	if(carSelected == "anycar") {
       		arrayParts = Trips.find({ $or: [ { "a": { $eq: companySelected } },  { "b": { $eq: companySelected } }], "year": {$eq: yearNum}, "month": { $eq: monthNumber},  "userId" : {$eq: Meteor.userId()}}, {sort:{createdAt: -1}});
   	return Trips.find({ $or: [ { "a": { $eq: companySelected } },  { "b": { $eq: companySelected } }], "year": {$eq: yearNum}, "month": { $eq: monthNumber},  "userId" : {$eq: Meteor.userId()}}, {sort:{createdAt: -1}});
  } else {
  	      arrayParts = Trips.find({ $or: [ { "a": { $eq: companySelected } },  { "b": { $eq: companySelected } }], "year": {$eq: yearNum}, "car": {$eq: carSelected}, "month": { $eq: monthNumber},  "userId" : {$eq: Meteor.userId()}}, {sort:{createdAt: -1}});
   	  	return Trips.find({ $or: [ { "a": { $eq: companySelected } },  { "b": { $eq: companySelected } }], "year": {$eq: yearNum}, "car": {$eq: carSelected}, "month": { $eq: monthNumber},  "userId" : {$eq: Meteor.userId()}}, {sort:{createdAt: -1}});
      } }
	break;

gives me no results.

Trips.find() returns a cursor - that’s like a description of how to get the results without actually getting them. Trips.find().fetch() actually gets the results from the cursor. So your arrayParts is a cursor and arrayParts.fetch() will get the results using the cursor.

find returns a cursor. It does not immediately access the database or return documents. Cursors provide fetch to return all matching documents, map and forEach to iterate over all matching documents, and observe and observeChanges to register callbacks when the set of matching documents changes.

2 Likes

Wow, you are absolutely right my friend!
Thank you very much, now I got it working.

Final code if anybody needs it

 "click .btn-export": function(event, template){
  event.preventDefault();
    const csv = Papa.unparse({
      fields: [
        "createdAt",
        "day",
        "month",
        "year",
        "a",
        "b",
        "car",
        "dist",
        "cost",
        "comment"
      ],
      data: arrayParts.fetch()
    });

      const blob = new Blob([csv], { type: "text/plain;charset=utf-8;" });
      saveAs(blob, "Trip-Report.csv");



  },
3 Likes