Блестящие / rhandsontable / обновление ячеек

В блестящем у меня есть две таблицы, отображаемые с помощью rhandsontable, я уже реализовал, что всякий раз, когда значение ячейки изменяется, ячейка окрашивается, однако при переключении между таблицами все изменения исчезают, поскольку, естественно, таблица перезагружается каждый раз с самого начала, возможно ли это чтобы как-то сохранить измененную ячейку, чтобы пока вы не вышли из сеанса или не нажали "сброс" все изменения сохранялись даже при переключении между таблицами?

(код основан на решении следующего треда: в блестящем: rhandsontable / afterChange, изменить сразу несколько фонов ячеек)

library(shiny)
library(rhandsontable)
library(tidyverse)

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 */
}"


ui <- div(actionButton(inputId = "reset_button",label = "Reset"), selectInput("data", "Choose data",choices=c("mtcars"=1, "iris"=2), selected=1)
          ,rHandsontableOutput(outputId = "mtcars"))


server <- function(input, output, session) {
  
  
  reset <- reactiveVal(0)
  output$mtcars <- renderRHandsontable({
    r = reset()
    myvec <- c("mtcars", "iris")
    mydata <- eval(parse(text=myvec[as.numeric(input$data)]))
    rht = rhandsontable(mydata,reset=r,stretchH = "all",height=300)
    reset(0)
    htmlwidgets::onRender(rht,change_hook)
  })
  
  observeEvent(input$reset_button,
               {
                 reset(1)
               })
}

shinyApp(ui, server)

какова роль reset?

Stéphane Laurent 05.06.2023 14:34

@StéphaneLaurent: кнопка сброса полностью сбросит все изменения в таблицах.

galaxy-- 05.06.2023 17:32

Я думаю, что это ошибка. Два экземпляра таблицы обмениваются данными.

Stéphane Laurent 05.06.2023 21:30

@StéphaneLaurent: большое спасибо за изучение этой проблемы, так что вы не думаете, что тогда это решаемо?

galaxy-- 05.06.2023 21:45

Можно использовать два renderHansontableOutput.

Stéphane Laurent 05.06.2023 23:36

Если у вас отображаются обе таблицы одновременно, вы можете сделать это с помощью нескольких изменений: 1) Вместо hot = this.hot; используйте var hot = el.htmlwidget_data_init_result.hot;. Префикс var к первому вхождению cellchngs, change, cellchg и td. (Например, вместо cellchngs = []; используйте var cellchngs = [];. Если это не имеет смысла или требует большей ясности, я могу добавить много деталей в качестве ответа. Однако это не отвечает на ваш фактический вопрос. Другой вариант — иметь обе таблицы «показаны» одновременно, но расположены друг над другом (поэтому вы видите только одну).

Kat 07.06.2023 22:16

@Kat: спасибо, я тоже попробую этот подход

galaxy-- 11.06.2023 15:22
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
7
100
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это немного некрасиво (с точки зрения избыточности кода)... но я устал, и это работает. В конце моего ответа я снова предоставил весь код, все вместе, для упрощения копирования и вставки.

Это извлекает выгоду из хранилища сеансов вашего браузера. Это означает, что пока это одно непрерывное событие, данные будут сохраняться. Если вы хотите иметь возможность закрыть браузер или перезапустить приложение и сохранить сохраненные изменения, вы можете вместо этого использовать локальное хранилище (sessionStorage вместо localStorage).

Это будет работать только в том случае, если у вас есть настроенные графики прямо сейчас. Если вы измените свой ui на две отдельные таблицы, это не сработает. (Я могу помочь, если вы настроите его по-другому, просто дайте мне знать.)

Изменения в change_hook:

wh захватит либо mtcars, либо iris из раскрывающегося меню selectInput.

cellchngs будет фиксировать изменения, как и раньше, но теперь у вас будет по одному для каждой таблицы. Есть три массива, потому что это был самый простой способ выровнять индексацию 1... в R и индексацию 0, 1.... в JS. Другими словами, этот первый массив в cellchngs останется пустым, следующий массив (индекс 1 в JS) будет фиксировать все изменения в selectInputvalue = 1 — это ваша mtcars таблица (как вы ее назначили).

afterChange не сильно изменился по сравнению с вашим первоначальным вопросом.

  • добавлен idx для захвата индекса из вашего selectInput
  • изменено cellchngs = на cellchngs[idx] =
  • добавлено sCookie(): функция, которая сохраняет данные в хранилище сессий вашего браузера (см. изображение после этого списка изменений)
  • добавлена ​​глобальная коллекция переменных с помощью chgs (это объявлено в вашем ui; подробнее об этом, когда я расскажу об изменениях в ui)
  • наконец, if (sessionStorage... смотрит, есть ли данные, сохраненные в хранилище сеанса, если они есть, это обновит таблицу, чтобы отразить изменения, внесенные с момента запуска сеанса браузера (независимо от того, сколько раз вы переключаетесь между таблицами с помощью раскрывающегося списка)

Глядя на хранилище сеансов: вы можете увидеть это в инструментах разработчика вашего браузера; вкладка: Приложение, левое меню: Хранилище сессий -> ваш IP (см. изображение ниже)

change_hook <- "function(el, x) {
  var hot = el.htmlwidget_data_init_result.hot;
  var wh = document.querySelector('#data option').innerHTML;   /* DD table select */
  var cellchngs = [[], [], []];             /* 3 arrays: 0 start index vs 1 start */
  
  afterChange = function(changes, source) {
    var idx = document.querySelector('#data option').value;    /* DD table select */
    $.each(changes, function (index, elem) {
      var change = elem;                       /* gather the row, col, old, new values */
      if (change[2] !== change[3]) {            /* if old isn't the same as new */
        var cellchg = ({rowind: change[0], colind: change[2]});
        cellchngs[idx].push(cellchg);          /* add row and column indicies to array */
        sCookie();                             /* save the updated data to cookie */
      }
    });
    $.each(cellchngs[idx], function(ind, ele) {
      var td = hot.getCell(ele['rowind'], ele['colind']);    /* get the html element */
      td.style.background = 'yellow';                        /* set background color */
    });

    chgs[idx] = chgs[idx].concat(cellchngs[idx]);            /* save list of changes to global var*/
    chgs[idx].filter((v,i,a)=>a.findIndex(v2=>['rowind','colind'].every(k=>v2[k] ===v[k]))===i);  /* remove duplicates */
  }
  hot.addHook('afterChange', afterChange);  /* add event to table */

  if (sessionStorage[wh]) {                  /* if data already stored for current table, retrieve it*/
    hot.loadData(JSON.parse(sessionStorage[wh])); 
    hot.render();
    colr(chgs, hot);                        /* re-highlight changes */
  }
}"

Изменения в ui

Я добавил элемент tags$script в ваш ui, код, который изначально был в вашем ui, остался прежним.

В этом сценарии вы найдете объявление глобальной переменной chgs, функцию colr: изменение цвета ячеек вне события изменения и sCookie: сохранение данных в хранилище сеанса.

ui <- div(
  tags$script(HTML(
  'setTimeout(function() {  /* ensure table loads before looking for tbl elem */
    chgs = [[], [], []];    /* global variable */
    colr = function(chgs, hot) { /* for outside of change events (update data from stg */
      var idx = document.querySelector("#data option").value;/* DD table select */
      $.each(chgs[idx], function(ind, ele) {
        var td = hot.getCell(ele["rowind"], ele["colind"]);  /* get the html element */
        td.style.background = "yellow";                      /* set background color */
      });
    }
    sCookie = function() {  /* whenever data changes are made, save to local*/
      var el = document.querySelector(".rhandsontable.html-widget"); /* capture table el */
      var hot = el.htmlwidget_data_init_result.hot;                  /* capture instance */
      var wh = document.querySelector("#data option").innerHTML;     /* DD table select */
      sessionStorage[wh] = JSON.stringify(hot.getData());            /* DD table select */
      return
    }
  }, 200)')),
          actionButton(inputId = "reset_button", label = "Reset"), 
          selectInput("data", "Choose data",
                      choices = c("mtcars" = 1, "iris" = 2), selected = 1),
          rHandsontableOutput(outputId = "mtcars"))

Весь код целиком

Вот опять все, но все сразу. Если у вас есть какие-либо вопросы, дайте мне знать.

Обратите внимание, что я ничего не изменил в вашем server (хотя он может выглядеть иначе из-за пробелов).

library(shiny)
library(rhandsontable)

change_hook <- "function(el, x) {
  var hot = el.htmlwidget_data_init_result.hot;
  var wh = document.querySelector('#data option').innerHTML;   /* DD table select */
  var cellchngs = [[], [], []];             /* 3 arrays: 0 start index vs 1 start */
  
  afterChange = function(changes, source) {
    var idx = document.querySelector('#data option').value;    /* DD table select */
    $.each(changes, function (index, elem) {
      var change = elem;                       /* gather the row, col, old, new values */
      if (change[2] !== change[3]) {            /* if old isn't the same as new */
        var cellchg = ({rowind: change[0], colind: change[2]});
        cellchngs[idx].push(cellchg);          /* add row and column indicies to array */
        sCookie();                             /* save the updated data to cookie */
      }
    });
    $.each(cellchngs[idx], function(ind, ele) {
      var td = hot.getCell(ele['rowind'], ele['colind']);    /* get the html element */
      td.style.background = 'yellow';                        /* set background color */
    });

    chgs[idx] = chgs[idx].concat(cellchngs[idx]);            /* save list of changes to global var*/
    chgs[idx].filter((v,i,a)=>a.findIndex(v2=>['rowind','colind'].every(k=>v2[k] ===v[k]))===i);  /* remove duplicates */
  }
  hot.addHook('afterChange', afterChange);  /* add event to table */

  if (sessionStorage[wh]) {                  /* if data already stored for current table, retrieve it*/
    hot.loadData(JSON.parse(sessionStorage[wh])); 
    hot.render();
    colr(chgs, hot);                        /* re-highlight changes */
  }
}"

ui <- div(
  tags$script(HTML(
    'setTimeout(function() {  /* ensure table loads before looking for tbl elem */
    chgs = [[], [], []];    /* global variable */
    colr = function(chgs, hot) { /* for outside of change events (update data from stg */
      var idx = document.querySelector("#data option").value;/* DD table select */
      $.each(chgs[idx], function(ind, ele) {
        var td = hot.getCell(ele["rowind"], ele["colind"]);  /* get the html element */
        td.style.background = "yellow";                      /* set background color */
      });
    }
    sCookie = function() {  /* whenever data changes are made, save to local*/
      var el = document.querySelector(".rhandsontable.html-widget"); /* capture table el */
      var hot = el.htmlwidget_data_init_result.hot;                  /* capture instance */
      var wh = document.querySelector("#data option").innerHTML;     /* DD table select */
      sessionStorage[wh] = JSON.stringify(hot.getData());            /* DD table select */
      return
    }
  }, 200)')),
  # tags$style(HTML(".colorMe {background: yellow !important;}")),
  actionButton(inputId = "reset_button", label = "Reset"), 
  selectInput("data", "Choose data",
              choices = c("mtcars" = 1, "iris" = 2), selected = 1),
  rHandsontableOutput(outputId = "mtcars"))

server <- function(input, output, session) { # unchanged from your question
  reset <- reactiveVal(0)
  output$mtcars <- renderRHandsontable({
    r = reset()
    myvec <- c("mtcars", "iris")
    mydata <- eval(parse(text = myvec[as.numeric(input$data)]))
    rht = rhandsontable(mydata, reset = r, stretchH = "all", height = 300)
    reset(0)
    htmlwidgets::onRender(rht, change_hook)
  })
  observeEvent(input$reset_button, {reset(1)})
}

shinyApp(ui, server)

Большое спасибо, что нашли время и написали такой подробный ответ, однако, к сожалению, для меня это не работает ни в Rstudio, ни в браузере, когда я меняю значение, вокруг ячейки появляется только синий внешний прямоугольник, и все зависает . пробовал на файрфоксе и эдже

galaxy-- 11.06.2023 15:21
Ответ принят как подходящий

Для этого вы можете использовать скрытые tabsetPanels.

Обратите внимание, что я не изменил блок change_hook.

library(shiny)
library(rhandsontable)

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 */
}"


ui <- fluidPage(
  actionButton(inputId = "reset_button",label = "Reset"),
  selectInput(
    inputId = "data",
    label = "Choose data",
    choices = c("mtcars", "iris")
  ),
  tabsetPanel(
    id = "tabs",
    type = "hidden",
    tabPanelBody(
      value = "mtcars",
      rHandsontableOutput(outputId = "mtcars")
    ),
    tabPanelBody(
      value = "iris",
      rHandsontableOutput(outputId = "iris")
    )
  )
)


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)
  })
  
  output$iris <- renderRHandsontable({
    r = reset()
    rht = rhandsontable(
      iris,
      reset = r,
      stretchH = "all",
      height = 300
    )
    reset(0)
    htmlwidgets::onRender(rht, change_hook)
  })
  
  observeEvent(input$reset_button, {
    reset(1)
  })
  
  # if the selectInput value changes, switch tabs:
  observeEvent(input$data, {
    updateTabsetPanel(
      session = session,
      inputId = "tabs",
      selected = input$data
    )
  })
}

shinyApp(ui, server)

Другие вопросы по теме