1

The following working example is derived from my earlier question Prevent an event happened in Shiny if no UI inputs are available. Now I want to format the numericInput field with commas to assist the users to read large numbers. I followed the example of Option 2 from this post "https://beta.rstudioconnect.com/barbara/format-numbers/" to change the Total numericInput with this style. The key is to create a .js file that formats the numbers and stores it in a www directory under the same folder as the shiny script.

This works nicely with the Total numericInput. However, how can I use the same format for those updated, later added numericINputs? The challenge is I am not able to know how many numericInput would be added later, so it is difficult to modify the format_numbers.js file if I don't know the inpur ID to add to that file.

The format_numbers.js is as follows.

$(document).ready(function() {
  // Helper function to guarantee cross-browser compatibility
  // adapted from: http://stackoverflow.com/a/16157942
  function localeString(x, sep, grp) {
    var sx = (''+x).split('.'), s = '', i, j;
    sep || (sep = ',');            // default separator
    grp || grp === 0 || (grp = 3); // default grouping
    i = sx[0].length;
    while (i > grp) {
      j = i - grp;
      s = sep + sx[0].slice(j, i) + s;
      i = j;
    }
    s = sx[0].slice(0, i) + s;
    sx[0] = s;
    return sx.join('.');
  }

  // To change Total's input field (lose arrows and other functionality)
  $('#Total')[0].type = 'text';

  // To format the number when the app starts up
  $('#Total').val(localeString($('#Total').val()));

  // To format the number whenever the input changes
  $('#Total').keyup(function(event) {
    $(this).val(localeString($(this).val().replace(/,/g, '')));
  });
});

The shiny script is as follows.

library(shiny)

# Define UI
ui <- fluidPage(
  # Modify tags
  tags$head(tags$script(src = "format_numbers.js")),

  # Action button to add numeric input
  actionButton("add", "Add UI"),
  actionButton("sum", "Sum"),

  # Numeric Input
  numericInput(inputId = "Total", label = "Total", value = 0),

  # Text output
  "The number is ", 
  textOutput(outputId = "out_num", inline = TRUE)

)

# Server logic
server <- function(input, output, session){

  # Add numeric input
  observeEvent(input$add, {
    insertUI(
      selector = "#add",
      where = "afterEnd",
      ui = numericInput(paste0("txt", input$add), label = "Number", value = 0)
    )
  })

  # Reactive values for Total
  Num_In <- reactiveValues(
    Total_In = 0
  )

  # Convert number to character
  # This is to fill in the Total numeric input formatting with comma
  total_num_as_char <- reactive({format(Num_In$Total_In, big.mark = ",", trim = TRUE)})

  total_input <- reactive({Num_In$Total_In})

  observeEvent(input$sum, {
    num_names <- names(input)[grepl("^txt", names(input))]

    if (length(num_names) == 0) {
      foo <- 0
    } else {
      foo <- sum(sapply(num_names, function(x) input[[x]]), na.rm = TRUE)
    } 
    Num_In$Total_In <- foo

    updateNumericInput(session = session,
                       inputId = "Total", 
                       value = total_num_as_char())
  })

  # Convert input to numeric
  total_num <- reactive({as.numeric(gsub(",", "", input$Total))})

  # Create text output
  output$out_num <- renderText({total_num()})
}

# Complete app with UI and server components
shinyApp(ui, server)

1 Answer 1

1

For me, the following works.

When a UI component is added with insertUI, the JS event shiny:bound is triggered. Then we can take advantage of that:

  // Helper function to guarantee cross-browser compatibility
  // adapted from: http://stackoverflow.com/a/16157942
  function localeString(x, sep, grp) {
    var sx = (''+x).split('.'), s = '', i, j;
    sep || (sep = ',');            // default separator
    grp || grp === 0 || (grp = 3); // default grouping
    i = sx[0].length;
    while (i > grp) {
      j = i - grp;
      s = sep + sx[0].slice(j, i) + s;
      i = j;
    }
    s = sx[0].slice(0, i) + s;
    sx[0] = s;
    return sx.join('.');
  }

$(document).ready(function() {
  // To change Total's input field (lose arrows and other functionality)
  $('#Total')[0].type = 'text';

  // To format the number when the app starts up
  $('#Total').val(localeString($('#Total').val()));

  // To format the number whenever the input changes
  $('#Total').keyup(function(event) {
    $(this).val(localeString($(this).val().replace(/,/g, '')));
  });
});

$(document).on('shiny:bound', function(evt){
  var id = evt.target.getAttribute('id');
  if((/^(txt)/).test(id)){
    var selector = '#' + id; 
    $(selector)[0].type = 'text';
    $(selector).val(localeString($(selector).val()));
    $(selector).keyup(function(event) {
      $(this).val(localeString($(this).val().replace(/,/g, '')));
    });
  }
});

Now, in R:

unformat <- function(x) as.numeric(gsub(",", "", x))

and

  observeEvent(input$sum, {
    num_names <- names(input)[grepl("^txt", names(input))]

    if (length(num_names) == 0) {
      foo <- 0
    } else {
      foo <- sum(sapply(num_names, 
                        function(x) unformat(input[[x]])), na.rm = TRUE)
    } 
    Num_In$Total_In <- foo

    updateNumericInput(session = session,
                       inputId = "Total", 
                       value = total_num_as_char())
  })
Sign up to request clarification or add additional context in comments.

5 Comments

This for your help. This solution looks promising. I have upvoted your answer. After changing the js and the R script with your updates, the added numeric input fields still display numbers without commas. Could you check if this solution works on your machine again?
@www Weird. I have checked again and yes, this works. E.g. 1111 is automatically changed to 1,111.
I agree it should work. I feel like I have the same code as your solution. The only difference could be the location of the unformat function, but it should not matter. I edited my post and put the js and shiny code. If you don't mind, could you check if the codes work on your machine? Thank you so much.
@www Have you tried to put the function localeString outside the $(document).ready(function() {......}) ? Otherwise I'm not sure it is defined in the $(document).on('shiny:bound', .......
It works. As you said, my js script was not correct. Thank you very much for looking into this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.