Shiny slickR优化:未填写输入时阻止幻灯片切换
解决方案
要实现点击幻灯片切换箭头时检查当前输入是否为空并阻止切换,核心是利用slick的beforeChange事件拦截切换动作,同时结合输入状态验证。以下是修改后的完整代码及关键说明:
修改后的完整代码
library(shiny) library(shinyalert) library(slickR) library(tidyverse) library(shinyvalidate) library(shinyjs) js <- " $(document).ready(function(){ var ss = document.getElementById('slickr'); // 拦截幻灯片切换前的事件 $(ss).on('beforeChange', function(event, slick, currentSlide, nextSlide) { // 当前幻灯片索引(从0开始),对应输入项的序号是currentSlide+1 var currentInputSeq = currentSlide + 1; var inputId; // 匹配当前幻灯片对应的输入框ID if(currentInputSeq === 1){ inputId = 'Name'; } else if(currentInputSeq >=2 && currentInputSeq <=7){ inputId = 'Photo' + (currentInputSeq -1); } else { // 最后一页是提交按钮,允许切换 return true; } // 获取输入框的值并检查是否为空 var inputValue = $('#' + inputId).val(); if(!inputValue || inputValue.trim() === ''){ // 阻止幻灯片切换 event.preventDefault(); event.stopPropagation(); // 弹出提示 alert('请先填写当前项的内容!'); return false; } }); // 原有的MutationObserver,同步幻灯片与输入项 var observer = new MutationObserver(function(mutations) { var index = $(ss).find('.slick-current').data('slick-index'); Shiny.setInputValue('imageIndex', parseInt(index)+1); }); var config = {subtree: true, attributes: true}; observer.observe(ss, config); var deleteThis = function(elem){ elem.style.display = 'none'; }; }) " cP1=htmlwidgets::JS("function(slick,index) {return '<a>'+(index+1)+'</a>';}") # 读取图片文件(确保samplepictures文件夹下有对应数量的图片) imgs <- list.files("samplepictures/", pattern=".jpg|.jpeg|.png", full.names = TRUE) imgs <- imgs[!grepl("photo_ages",imgs)] # 构建输入项列表 photoages <- lapply(1:6,function(x){ numericInput(paste0("Photo",x), paste0("Photo ",x," Age Guess:"), min = 1, max = 100, value = NULL) }) exfn <- function(x= "Guesser"){ textInput("Name",paste(x)) } actionfn <- function(){ actionButton("go", "Submit All Guesses") } photoages <- as.vector(photoages) photoages <- c(list(exfn()), # 修正原代码拼写错误:texfn改为exfn photoages, list(actionfn())) ui <- fluidPage( useShinyjs(), useShinyalert(), # 可选,用于替换原生alert titlePanel("Photo Guesses"), mainPanel( tags$head( tags$script(HTML(js)), tags$style(HTML(" .arrows { height: 30px; } .slick-prev { left: 10px; } .slick-next { left: 30px; } ")) ), fluidRow( column(4,align = "left", div(id = "mydiv", uiOutput("photoinput"))) , column(4, align = "left", div(id = "npht", h4("Advance:")), tags$br(), div(id = "arr", class="arrows")) ), tags$hr(), slickROutput("slickr") , tags$br() ) ) server <- function(input, output,session) { # 配置输入验证规则,强制必填 iv <- InputValidator$new() iv$add_rule("Name", sv_required()) iv$add_rule("Photo1", sv_between(1,100, allow_na = FALSE)) iv$add_rule("Photo2", sv_between(1,100, allow_na = FALSE)) iv$add_rule("Photo3", sv_between(1,100, allow_na = FALSE)) iv$add_rule("Photo4", sv_between(1,100, allow_na = FALSE)) iv$add_rule("Photo5", sv_between(1,100, allow_na = FALSE)) iv$add_rule("Photo6", sv_between(1,100, allow_na = FALSE)) iv$enable() observeEvent(input[["imageIndex"]],{ if(input[["imageIndex"]] >7){ removeUI("#npht") removeUI("#arr") } }) output$slickr <- renderSlickR({ imgs <- list.files("samplepictures/", pattern=".jpg|.jpeg|.png", full.names = TRUE) imgs <- imgs[!grepl("photo_ages",imgs)] slickR(imgs) + slickR::settings(dots = TRUE, appendArrows = '.arrows', prevArrow = '<button type="button" class="slick-prev">Previous</button>', nextArrow = '<button type="button" class="slick-next">Next</button>' ) }) output[["photoinput"]] <- renderUI({ req(input[["imageIndex"]]) photoages[input[["imageIndex"]]] }) } shinyApp(ui = ui, server = server)
关键修改说明
拦截幻灯片切换事件:
- 通过
slick的beforeChange事件在切换前触发检查逻辑,利用event.preventDefault()阻止无效切换。 - 根据当前幻灯片索引匹配对应的输入框ID,检查输入值是否为空,为空则弹出提示并终止切换。
- 通过
强化输入验证:
- 将
sv_between中的allow_na = T改为FALSE,确保数字输入框必须填写有效值。
- 将
修复代码错误:
- 修正原代码中
texfn的拼写错误,改为正确的exfn。
- 修正原代码中
优化箭头按钮定义:
- 明确指定
prevArrow和nextArrow的HTML结构,确保事件能正确绑定到按钮上。
- 明确指定
如果偏好使用shinyalert替代原生弹窗,可将JS中的alert替换为:
Shiny.setInputValue('showAlert', true, {priority: 'event'});
并在server中添加:
observeEvent(input$showAlert, { shinyalert("提示", "请先填写当前项的内容!", type = "warning") })
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

