如何在R Shiny中实现Undo按钮逐次撤销操作,解决一次性恢复全部修改的问题
R Shiny逐次撤销功能实现方案
核心问题原因
原代码仅存储了初始数据集和当前操作后的数据集,没有记录每一步操作的中间状态,因此点击撤销按钮只能直接恢复到文件上传后的初始状态,无法实现逐步回退。
我们可以通过栈结构存储操作历史的方案实现类Ctrl+Z的逐次撤销效果:每次执行修改表格的操作前,先把当前的表格状态存入历史栈,点击撤销按钮时取出栈顶最近一次的状态恢复即可。
具体修改点
- 扩展响应式变量,新增
history列表用于存储每一步的历史状态 - 所有修改表格的操作(拆分列、删除行、填充值、删除列)执行前,先将当前表格状态存入历史栈
- 上传新CSV文件时清空历史栈,避免不同文件的操作历史混淆
- 重写Undo按钮逻辑,每次点击恢复到上一步操作前的状态,历史栈为空时不执行操作避免报错
完整修改后代码
library(shiny) library(reshape2) library(DT) library(tibble) ### 拆分列函数 splitColumn <- function(data, column_name) { newColNames <- c("Unmerged_type1", "Unmerged_type2") newCols <- colsplit(data[[column_name]], " ", newColNames) after_merge <- cbind(data, newCols) after_merge[[column_name]] <- NULL after_merge } ###_______________________________________________ ### 插入新列填充值函数 fillvalues <- function(data, values, columName){ df_fill <- data vec <- strsplit(values, ",")[[1]] df_fill <- tibble::add_column(df_fill, newcolumn = vec, .after = columName) df_fill } ## 删除列函数 removecolumn <- function(df, nameofthecolumn){ df[ , -which(names(df) %in% nameofthecolumn)] } ui <- fluidPage( sidebarLayout( sidebarPanel( fileInput("file1", "选择CSV文件", accept = ".csv"), checkboxInput("header", "包含表头", TRUE), actionButton("Splitcolumn", "拆分列"), uiOutput("selectUI"), actionButton("deleteRows", "删除选中行"), textInput("textbox", label="输入要填充的值,多值用逗号分隔:"), actionButton("replacevalues", label = '插入列填充值'), actionButton("removecolumn", "删除选中列"), actionButton("Undo", '撤销') ), mainPanel( DTOutput("table1") ) ) ) server <- function(session, input, output) { # 新增history存储操作历史 rv <- reactiveValues(data = NULL, orig=NULL, history = list()) observeEvent(input$file1, { file <- input$file1 ext <- tools::file_ext(file$datapath) req(file) validate(need(ext == "csv", "请上传CSV格式的文件")) rv$orig <- read.csv(file$datapath, header = input$header, sep = "") rv$data <- rv$orig # 上传新文件清空历史栈 rv$history = list() }) output$selectUI<-renderUI({ req(rv$data) selectInput(inputId='selectcolumn', label='选择要操作的列', choices = names(rv$data)) }) observeEvent(input$Splitcolumn, { # 操作前先存当前状态到历史栈 rv$history <- c(rv$history, list(rv$data)) rv$data <- splitColumn(rv$data, input$selectcolumn) }) observeEvent(input$deleteRows,{ if (!is.null(input$table1_rows_selected)) { # 操作前先存当前状态到历史栈 rv$history <- c(rv$history, list(rv$data)) rv$data <- rv$data[-as.numeric(input$table1_rows_selected),] } }) output$table1 <- renderDT({ rv$data }) observeEvent(input$replacevalues, { # 操作前先存当前状态到历史栈 rv$history <- c(rv$history, list(rv$data)) rv$data <- fillvalues(rv$data, input$textbox, input$selectcolumn) }) observeEvent(input$removecolumn, { # 操作前先存当前状态到历史栈 rv$history <- c(rv$history, list(rv$data)) rv$data <- removecolumn(rv$data,input$selectcolumn) }) observeEvent(input$Undo, { # 历史栈不为空才能撤销 if(length(rv$history) > 0){ # 取出最近一次的历史状态恢复 rv$data <- rv$history[[length(rv$history)]] # 弹出已恢复的栈顶元素 rv$history <- rv$history[-length(rv$history)] } }) } shinyApp(ui, server)
内容的提问来源于stack exchange,提问作者Kevin Tracey
相关产品推荐
相关产品推荐

