Search code examples
javascriptrrpivottable

Make several configuration saves/loadings of rpivottable on R


The following code I found on an other post (I could link if anybody is interested) adds the saving option on the R rpivottable using flexdashboard. The saving is done through cookies.
I am trying to recreate it but having multiple saving options, basically have SAVE1, SAVE2,.. and being able to load the wanted save. I am unexperienced with JS so it is really hard to do anything.

---
title: "rpivottable_test"
output: html_document
---
knitr::opts_chunk$set(echo = FALSE)
# devtools::install_github("fraupflaume/rpivotTable")
library(rpivotTable)
data(mtcars)
names(mtcars)[10] <- "George.Dontas"

Save Current Configuration Restore Previous Configuration


rpivotTable(mtcars,rows="George.Dontas", cols = c("cyl"), width = "90%", height = "40%",
            rendererOptions = list(
              c3 = list(legend = list(show = FALSE), 
                        data = list(labels = TRUE),
                        options = list(responsive = TRUE,
                                       maintainAspectRatio = FALSE),
                        size = list(width = "600",
                                    height = "500")),
              d3 = list(size = list(width = "500", height = "500")))) 

// save current state of the tables to my browser
setTimeout(function(){       //add the events first
  document.querySelector('a#saveBtn').addEventListener('click', savoring);
  document.querySelector('a#restoBtn').addEventListener('click', giveItBack);
  function savoring() {                             // function to save
    el = document.querySelectorAll('.rpivotTable');
    for(i=0; i < el.length; i++){
      elId = el[i].getAttribute("id");
      stringy = $('#' + elId).data("pivotUIOptions"); // collect rows/columns filters
      delete stringy['aggregators'];                 // remove the arbitrary
      delete stringy['renderers'];
      stringy2 = JSON.stringify(stringy);            // make it one key:value
      window.localStorage.setItem('table' + i, stringy2); // store it!
    }
  };
  function giveItBack() {                           // function to regurgitate
    el = document.querySelectorAll('.rpivotTable');
    console.log("working on the giver");
    ods = [...el[0].ownerDocument.scripts];         // make it an array
    for(j=0; j < el.length; j++){
      elId = el[j].getAttribute("id");
      where = ods.filter(function(ods){             // filter scripts for table data
        return ods.dataset['for'] === elId;
      })[0].innerHTML; 
      where2 = JSON.parse(where).x.data;            // WOOO HOO! I figured it out!!
      where3 = HTMLWidgets.dataframeToD3(where2);   // finally sheesh!!
      gimme = window.localStorage.getItem('table' + j); // get storage
      $('#' + elId).pivotUI(where3, JSON.parse(gimme), true, "en"); // put it back!
    }
  }
},100);


Solution

  • This uses the default system alert and prompt boxes. These vary in appearance a bit from one OS to another.

    I've included CSS for the buttons and RMD. I used html_document. If you use flex_dashboard you won't need the CSS for the class main-container. The CSS for body may or may not be useful (depending on what else you have going on).

    If you're using the Cran package, the rendererOptions won't work as-is. You have to remove a list level after you make the object. The D3 rendererOptions won't work unless you change the Javascript file, d3_renderers.js, in the rpivotTable package. (That's what I did with my fork.) If you use my fork, it will work as-is.

    I've added handlers for

    • naming violations (whitespace, special characters, that sort of thing)
    • no matching name when restoring the configuration
      • returns a list of all names in storage
      • then prompts the user for another configuration name
    • if cancel is pressed from prompt, it cancels the save or restore process

    You may notice quite a few changes in the restoration function. That's because the original code would lost subtotals, sorters, and the renderer tsv.

    I tried to think of anything that could go wrong and this is where I'm at. If you run into any issues, let me know. If you have questions, ask.

    The YAML, options, and CSS:

    ---
    title: "button and input tester!"
    author: "me"
    date: '2022-05-12'
    output: html_document
    ---
    
    ```{r setup, include=FALSE}
    knitr::opts_chunk$set(echo = FALSE)
    ```
    
    <style>
    body {    /*push content away from far right and left edges*/
      margin-right: 2%;
      margin-left: 2%;
    }
    .main-container {
      max-width: unset;
    }
    .btn { 
      vertical-align: middle;
      -moz-box-shadow: 0px 10px 14px -7px #000000;
      -webkit-box-shadow: 0px 10px 14px -7px #000000;
      box-shadow: 0px 10px 14px -7px #000000;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      border-radius: 4px;
      border: .5px solid black;
      display: inline-block;
      font-size: 1.2em;           
      padding: .3em 0px;
      width: 20em;                 
      text-decoration: none; /*no underline!!*/
      cursor: pointer;
    }
    .btn:active {      /*simulate movement*/
      position: relative;
      top: 1px;
    }
    </style>
    

    The table.

    ```{r data,include=F}
    # devtools::install_github("fraupflaume/rpivotTable")
    library(rpivotTable)
    data(mtcars)
    
    ```
    
    ## Make it Interesting...or not
    
    Do you want to save or restore the previously saved pivot tables' configuration?
    
    <a id='saveNamed' class='btn' style="background-color:#003b70;color:white;">Save Configuration by Name</a>
    <a id='restoNamed' class='btn' style="background-color:#b21e29;color:white;">Restore Configuration with Custom Name</a>
    
    
    ```{r showMe, echo=FALSE, fig.show="hold"}
    rpivotTable(mtcars, rows="am", cols = c("cyl"), width = "90%", 
                height = "40%", subtotals = TRUE,
                rendererOptions = list(
                  c3 = list(legend = list(show = FALSE), 
                            data = list(labels = TRUE),
                            options = list(responsive = TRUE,
                                           maintainAspectRatio = FALSE),
                            size = list(width = "600",
                                        height = "500")),
                  d3 = list(size = list(width = "500", height = "500")) 
                ))
    ```
    
    
    That's all, folks.
    

    Lastly, the JS.

    ```{r listenOrElse,results="asis",engine="js"}
    
    // for ONE TABLE
    setTimeout(function(){  // add to buttons
      document.querySelector('a#saveNamed').addEventListener('click', savoring); 
      document.querySelector('a#restoNamed').addEventListener('click', giveItBack);
      function savoring() {                              // function to save
        el = document.querySelector('.rpivotTable');
        msg = "Choose a name for the configuration that you are saving.";
        inName = prompt(msg, ['Enter a name with no spaces or special characters'])
        if(inName === null) {return;};                   // they changed their mind; nothing saved
        inName = inName.replace(/[^a-z0-9.]/gi, '');     // validate string
        path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
        elId = el.getAttribute("id");
        stringy = $('#' + elId).data("pivotUIOptions");  // collect rows/col filters
        delete stringy['aggregators'];                   // remove not-parse-friendly keys
        delete stringy['renderers'];
        stringy2 = JSON.stringify(stringy);              // one key:value pair for storage
        window.localStorage.setItem(path + '_' + inName, stringy2);  // STORE it!
      };
      function giveItBack() {                           // function to regurgitate
        el = document.querySelector('.rpivotTable');
        msg = "Enter the name of the configuration you would like to retrieve.";
        confName = prompt(msg, ["Enter a name with no spaces or special characters"]);
        if(confName === null) {return;}; 
        confName = confName.replace(/[^a-z0-9.]/gi, '');    // validate string
        ods = [...el.ownerDocument.scripts];             // make it an array
        path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
        elId = el.getAttribute("id");
        where = ods.filter(function(ods){             // filter scripts for data
          return ods.dataset['for'] === elId;
        })[0].innerHTML; 
        where2 = JSON.parse(where).x.data;            // format data for pivotUI()
        where3 = HTMLWidgets.dataframeToD3(where2);   // ...still formatting
        if(window.localStorage.getItem(path + '_' + confName) === null) { // alert
          len = window.localStorage.length
          var str;
          for(i = 0; i < len; i++) {
            w = window.localStorage.key(i);
            ind = w.lastIndexOf('_');
            w2 = w.substr(ind + 1);   // remove file/page name and table number
            str = str + w2 + '\n';    // make one long string of names
          }
          str2 = "WARNING: There is no saved pivot table configuration with the name " + confName + '.';
          str2 += " Here is a list of the configuration names that are currently stored for this page:\n";
          str2 += str;
          alert(str2);
          confName = prompt(msg, ["Enter a name with no spaces or special characters"]);
          if(confName === null) {
            return
          }; 
          confName = confName.replace(/[^a-z0-9.]/gi, '');    // validate string
        }
        gimme = window.localStorage.getItem(path + '_' + confName); // get storage
        gimmeMore = JSON.parse(gimme);                            // prepare for recall
        if(where.includes('"subtotals":true')){       // is the option 'subtotal' used?
          gimmeMore.renderers = $.pivotUtilities.subtotal_renderers;
          gimmeMore.dataClass = $.pivotUtilities.SubtotalPivotData;
        }; 
        if(where.includes('"tsv":true')){             // is the option 'tsv' used?
          gimmeMore.renderers = $.extend(gimmeMore.renderers, $.pivotUtilities.export_renderers);
        };
        if(where.includes('sortAs')){
          // passed as a function, they will get lost in save & retrieve
          stringy = $('#' + elId).data("pivotUIOptions").sorters;
          gimmeMore.sorters = stringy;
        }
        $('#' + elId).pivotUI(where3, gimmeMore, true, "en"); // put it back!
      };
    }, 500);
    
    ```
    

    In this image, you can see that 'bar chart' was entered (name violation).

    enter image description here

    During restoration, I searched for 'bar' (which didn't exist).

    enter image description here

    enter image description here

    enter image description here

    Now when I'm prompted again, I'm going to type bar chart (with the space) even though you can see that 'barchart' exists, not bar chart.

    enter image description here

    You'll still get the chart. The handler will remove white space and special characters here, as well.

    enter image description here