Export-import widget

Hello there,

We want to create a custom widget that helps export specific data from a table and the corresponding data from a card. For example, if we want to download a specific data row and its card fields as a CSV or xlsx file, we can select them and download them. Also, similar data can be imported from an external file. It’s pretty much an export/import widget.

Is it doable? Any suggestions would be much appreciated!

Best regards!

I did it to export a contact in vcard format. It was a button which wrote a text file, I think it would work for your csv export. If you are interested I can post the code (ugly but works).

1 Like

Please share. If it works, the look doesn’t matter.! I hope it will work for us as well. Thank you for your reply and help!

script.js:

  var globalData="";
  var bcolor="";
  var bcolorh="";

$(document).ready(function() {
var pif=$(window.parent.document).find('iframe').filter(function(){
      return this.contentWindow === window
    }).parent();
var obj=pif.closest('.view_data_pane_container');
pif.closest('.view_data_pane_container').css('height','50px');

});

function GoogleSearch(text) {
var search=getStringInBetween(text,"\nN:",";\nFN:").replaceAll(";"," ");
search=search.replaceAll(" ","+");
link="https://www.google.com/search?q="+search;
window.open(link,"_blank");
}

function getStringInBetween(str, start, end) {
const res = str.match(new RegExp(start + "(.*)" + end));
return res[1];
}

  function textToClipboard (text) {
     var dummy = document.createElement("textarea");
     document.body.appendChild(dummy);
     dummy.value = text;
     dummy.select();
     document.execCommand("copy");
     document.body.removeChild(dummy);

     // Create element with <a> tag
     const link = document.createElement("a");
     // Create a blob object with the file content which you want to add to the file
     const file = new Blob([text], { type: 'text/plain' });
     // Add file content in the object URL
     link.href = URL.createObjectURL(file);
     // Add file name
     var fnam= getStringInBetween(text,"\nN:",";\nFN:").replaceAll(";","_").concat(".vcf");
     link.download = fnam;
     // Add click event to <a> tag to save file.
     link.click();
     URL.revokeObjectURL(link.href);
  }

  function search(){
    GoogleSearch(globalData);
  }

  function copyText() {
    textToClipboard(globalData);
    document.getElementById('copy').innerHTML = "vCard salvata";
  }
  function showError(msg) {
    var el = document.getElementById('error')
    if (!msg) {
      el.style.display = 'none';
    } else {
      el.innerHTML = msg;
      el.style.display = 'block';
    }
  }
  // Returns first column from a record or undefined.
  function firstColumn(record) {
    return record ? record[Object.keys(record).filter(k => k !== 'id')[0]] : undefined;
  }
  grist.ready({
    columns: ["Column"]
  });
  grist.on('message', (e) => {
    if (e.tableId) { tableId = e.tableId; }
  });
  grist.onRecord(function(record) {
    const mapped = grist.mapColumnNames(record);
    // We will fallback to reading a value from a first column to
    // support old way of mapping (exposing only a single column).
    // New widgets should only check if mapped object is truthy.
    const valu`Preformatted text`e = mapped?.Column ?? firstColumn(record);
    if (value === undefined) {
      showError("Select a column to copy using the Creator Panel.");
    } else {
      globalData=value;
      document.getElementById('copy').innerHTML = "Salva vCard";
    }
  });
1 Like

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Save vCard to file</title>
    <script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="script.js"></script>

<!--
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.mi>
-->
    <link rel="stylesheet" type="text/css" href="inline1.css">
    <link rel="stylesheet" type="text/css" href="inline2.css">
    <link rel="stylesheet" type="text/css" href="styles.css">
  </head>
  <body>
    <div class="content buttonbar">
    <div class="_grain675_-top-space _grain675_ flexvbox">
    <div class="layout_box layout_hbox">
    <button class="_grain12_ _grain12_-primary"  id="copy" onclick="copyText()">Salva vCard</button>
    <button class="_grain12_ _grain12_-primary"  id="search" onclick="search()">Cerca</button>
    </div>
    </div>
    <div id="error"></div>
    </div>
  </body>
</html>


1 Like

I redefine some css so that the buttonbar can be shrunk nicely, but if you do not care about formatting you don’t need them. Some comments are in italian, if unclear I can translate them.
If you also want the css, just let me know.
It is just a quick and dirty solution I am sure you will do much better.

1 Like

Thank you very much for sharing the code. I will try to implement it and will let you know. You don’t need to translate and the CSS is not needed. I really appreciate your time and help! :pray: