Search code examples
javascriptrshinyhandsontablerhandsontable

in shiny: rhandsontable / afterChange, change multiple cell backgrounds at once


What I am trying to do: If I paste multiple values at once into a row into the shiny app with an integrated rhandsontable, that all the new values get a color change of the background at the same time.

with the answer from here the background color change works, but only if I paste the new values in the cells one by one and not row-wise: Change cell background of rHandsontable with afterChange event on client side

    library(shiny)
library(rhandsontable)

change_hook <- "function(el,x) {
var hot = this.hot;  
var cellChanges = [];

var changefn = function(changes,source) { 
if (source === 'edit' || source === 'undo' || source === 'autofill' || source === 'paste') {
row = changes[0][0];
col = changes[0][1];
oldval = changes[0][2];
newval = changes[0][3];

if (oldval !== newval) {
  var cell = hot.getCell(row, col);
  cell.style.background = 'cyan';
  cellChanges.push({'rowid':row, 'colid':col});
}
}
}

var renderfn = function(isForced) {

for(i = 0; i < cellChanges.length; i++)
{

var rowIndex = cellChanges[i]['rowid'];
var columnIndex = cellChanges[i]['colid'];

var cell = hot.getCell(rowIndex, columnIndex);
cell.style.background = 'cyan';

}


}

var loadfn = function(initialLoad) {

for(i = 0; i < cellChanges.length; i++)
    {
      var rowIndex = cellChanges[i]['rowid'];
      var columnIndex = cellChanges[i]['colid'];

      var cell = hot.getCell(rowIndex, columnIndex);

      cell.style.background = 'white';

    }
cellChanges = []

}


hot.addHook('afterChange', changefn);
hot.addHook('afterRender', renderfn);
hot.addHook('afterLoadData', loadfn);


}  "


ui <- div(actionButton(inputId = "reset_button",label = "Reset")
          ,rHandsontableOutput(outputId="mtcars"))


server <- function(input, output, session) {
  

  reset <- reactiveVal(0)
  output$mtcars <- renderRHandsontable({
    r = reset()
    rht = rhandsontable(mtcars,reset=r,stretchH="all",height=300)
    reset(0)
    htmlwidgets::onRender(rht,change_hook)
  })
  
  observeEvent(input$reset_button,
               {
                 reset(1)
               })
}

shinyApp(ui, server)

code modified from the answer of here

I tried to implement a for-loop in the changefn function, however unfortunately that did not work


Solution

  • Things change all the time, but this seems like it's overly complex for something that doesn't have to be. You only need a change event if all you want to do is color changed cells.

    You won't need to specify whether it was pasted, filled, etc. If there is a change for any reason, this function looks at it.

    I used a bit of JQuery here instead of for loops. I also didn't make as many variables. For example, change is used in for each changes (change is each index of changes). cellchng is used in for each cellchngs.

    change_hook <- "function(el,x) {
      hot = this.hot;
      cellchngs = [];
      afterChange = function(changes, source) {
        $.each(changes, function (index, elem) {
          change = elem;                  /* gather the row, col, old, new values */
          if(change[2] !== change[3]) {   /* if old isn't the same as new */
            cellchg = ({rowind: change[0], colind: change[1]});
            cellchngs.push(cellchg);      /* add row and column indicies to array */
          }
        });
        $.each(cellchngs, function(ind, elem) { 
          td = hot.getCell(elem['rowind'], elem['colind']); /* get the html element */
          td.style.background = 'cyan';                     /* set background color */
        });
      }
      hot.addHook('afterChange', afterChange);  /* add event to table */
    }"
    

    Nothing in your Shiny code needs to change.

    enter image description here