Reputation: 1638
Is it possible to use the jQuery validation plugin to validate a form in an R/Shiny application?
I would like to take advantage of this great package in my applications
I wasn't able to get a minimal example, below, to work and I am having trouble identifying the issue.
library(shiny)
library(shinydashboard)
ui <-
dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
fluidPage(
includeScript('jquery.validate.min.js'),
tags$form(id='myform',
textInput('field1','label'),
textInput('field2','label2'),
submitButton('submit')
),
tags$script('
$(document).ready(function () {
$(\'#myform\').validate({ // initialize the plugin
rules: {
field1: {
required: true,
email: true
},
field2: {
required: true,
minlength: 5
}
}
});
});
')
)
)
)
server <- function(input, output) {
# Once form is validated, do something
}
# Run the application
shinyApp(ui = ui, server = server)
Upvotes: 1
Views: 820
Reputation: 4124
It's possible, but a huge pain to do in Shiny. According to the jQuery Validation plugin docs:
Mandated: A 'name' attribute is required for all input elements needing validation, and the plugin will not work without this. A 'name' attribute must also be unique to the form, as this is how the plugin keeps track of all input elements.
https://jqueryvalidation.org/reference/
Shiny textInput
s don't come with a 'name' attribute, nor do they provide an easy way to add one. You kind of have to hack around it - either construct your own textInput, or modify the existing one.
Second pain: Shiny catches and specially handles form submissions to make submitButton
work. That means we can't rely on jQuery.Validate's built-in behavior of preventing invalid forms from being submitted. Instead, we have to work around it, using something like the showErrors
callback to simply raise a flag if the form is invalid. It'll be tough to prevent an invalid form from being sent to the server, but at least you can check whether the inputs are valid before doing anything with them.
This example shows two ways of adding rules: pure markup, and a custom rules object like in your example. I recommend markup only for ease of use. See https://jqueryvalidation.org/documentation/ for examples.
library(shiny)
library(shinydashboard)
# Use a CDN for the example, but can also be sourced locally
jQueryValidateLib <- htmltools::htmlDependency(
"jquery.validate", "1.17.0",
src = c(href = "https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0"),
script = "jquery.validate.min.js"
)
# Form validity status is accessible at input$formId_valid
validatedForm <- function(formId, ..., rules = NULL, options = list()) {
options$rules <- rules
tagList(
jQueryValidateLib,
tags$form(id = formId, ...),
tags$script(HTML(sprintf("
$(function () {
var formId = '%s';
var options = %s;
options.showErrors = function(e) {
var isValid = this.numberOfInvalids() === 0;
Shiny.onInputChange(formId + '_valid', isValid);
this.defaultShowErrors();
};
$('#' + formId).validate(options);
});
", formId, toJSON(options))
))
)
}
# Like textInput but allows adding attributes to the input element through ...
validatedTextInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL, ...) {
inputContainer <- textInput(inputId, label, value = value,
width = width, placeholder = placeholder)
inputElement <- inputContainer$children[[2]]
attribs <- c(inputElement$attribs, list(name = inputId, ...))
attribs[duplicated(names(attribs), fromLast = TRUE)] <- NULL
inputElement$attribs <- attribs
inputContainer$children[[2]] <- inputElement
inputContainer
}
toJSON <- function(x, auto_unbox = TRUE, ...) {
jsonlite::toJSON(x, auto_unbox = auto_unbox)
}
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
validatedForm(
"myform",
validatedTextInput("field1", "field1", required = TRUE, type = "email"),
validatedTextInput("field2", "field2"),
submitButton("Submit"),
rules = list(field2 = list(required = TRUE, minlength = 5))
),
verbatimTextOutput("fields")
)
)
server <- function(input, output) {
output$fields <- renderPrint({
cat(
paste("field1:", input$field1),
paste("field2:", input$field2),
paste("myform_valid:", input$myform_valid),
sep = "\n"
)
})
}
shinyApp(ui = ui, server = server)
Here's what the generated HTML looks like:
<form id="myform">
<div class="form-group shiny-input-container">
<label for="field1">field1</label>
<input id="field1" class="form-control" value="" name="field1" required="TRUE" type="email"/>
</div>
<div class="form-group shiny-input-container">
<label for="field2">field2</label>
<input id="field2" type="text" class="form-control" value="" name="field2"/>
</div>
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
<script>
$(function () {
var formId = 'myform';
var options = {"rules":{"field2":{"required":true,"minlength":5}}};
options.showErrors = function(e) {
var isValid = this.numberOfInvalids() === 0;
Shiny.onInputChange(formId + '_valid', isValid);
this.defaultShowErrors();
};
$('#' + formId).validate(options);
});
</script>
Upvotes: 3
Reputation: 33
I'm not positive if it will work, but you may have better luck with using the shinyJS package from Dean Attali, specifically with extendshinyjs().
See https://deanattali.com/shinyjs/ and https://deanattali.com/shinyjs/extend
Look at this template from Dean's website and see the obvious swaps you can make with your $(document).ready()
validation part:
jscode <- "
shinyjs.init = function() {
$(document).keypress(function(e) { alert('Key pressed: ' + e.which); });
}"
shinyApp(
ui = fluidPage(
useShinyjs(),
extendShinyjs(text = jscode),
"Press any key"
),
server = function(input, output) {}
)
Please post back here if it works.
Upvotes: 0