2

Background

I want to change some elements of a shinydashboard::box. Say, I want to change the icon used for collapsing a box(collapsible = TRUE). Looking at the output, all I need to do is to change the <i> tag accordingly:

(b <- box(collapsible = T))
# <div class="col-sm-6">
#   <div class="box">
#    <div class="box-header">
#       <div class="box-tools pull-right">
#         <button class="btn btn-box-tool" data-widget="collapse">
#           <i class="fa fa-minus"></i> ## change to <i class="fa fa-times">
#         </button>
#       </div>
#    </div>
#    <div class="box-body"></div>
#   </div>
# </div>

Challenge

While I could do some recursive looping through b$children to find the right children element like in

b$children[[1]]$children[[1]]$children[[2]]$children[[1]]$children[[1]]$attribs$class <- "fa fa-times"

I was wondering, whether there is not an easier way? Ideally something resembling jQuery syntax?

Another option would be to write my own box function, but I want to avoid that code duplication.

thothal
  • 16,690
  • 3
  • 36
  • 71

2 Answers2

1

You could write a helper function to edit the generated HTML. We could used the functions from xml2 to parse and edit the html. For example

swap_node <- function(x, xpath, newval) {
  parsed <- xml2::read_html(as.character(x))  
  oldnode <- xml2::xml_find_all(parsed, xpath)
  newnode <- xml2::read_html(as.character(newval))
  xml2::xml_replace(oldnode, newnode)
  shiny::HTML(as.character(xml2::xml_find_first(parsed, "//body/*")))
}

Then you can use it like

b <- shinydashboard::box(collapsible = T)
swap_node(b, "//i", shiny::tags$i(class="fa fa-times"))

But this operates in the world of strings rather than objects.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Ok, very useful indeed, thanks for sharing. I guess I will resort to `jQuery` in this case then. Unless somebody comes up with a solution working on the object rather than the string, I will happily accept this as an answer. – thothal Jul 31 '18 at 13:43
1

Here is a function that navigates the shiny object in the search for a tag of a given type having given attributes. If it is found, it is replaced by a new tag with new attributes:

searchreplaceit <- function(branch, whattag, whatattribs, totag, toattribs) {
    if ("name" %in% names(branch)) {
        if ((branch$name == whattag)&&(identical( branch$attribs[names(whatattribs)], whatattribs))) {
            branch$name    <- totag
            branch$attribs <- modifyList(branch$attribs, toattribs)
        }
    }
    if (class(branch)=="shiny.tag") {
        if (length(branch$children)>0) for (i in 1: length(branch$children)) {
            if (!(is.null(branch$children[[i]]))) {
                branch$children[[i]] = searchreplaceit(branch$children[[i]], whattag, whatattribs, totag, toattribs)
        } }
    } else if (class(branch)=="list") {
        if (length(branch)>0) for (i in 1:length(branch) ) {
            if (!(is.null(branch[[i]]))) {
                branch[[i]] <- searchreplaceit(branch[[i]], whattag, whatattribs, totag, toattribs)
        } }
    } 
    return(branch)
}

For example, the following

b <- shinydashboard::box(collapsible = T)
searchreplaceit(b, "i", list(class="fa fa-minus"), "i", list(class="XXX"))

will search within b a tag "i" having (at least) attributes class="fa fa-minus". When found, it is replaced with a tag "i" and attributes class="XXX":

# <div class="col-sm-6">
#   <div class="box">
#     <div class="box-header">
#       <div class="box-tools pull-right">
#         <button class="btn btn-box-tool" data-widget="collapse">
#           <i class="XXX" role="presentation" aria-label="minus icon"></i>
#         </button>
#       </div>
#     </div>
#     <div class="box-body"></div>
#   </div>
# </div>

The search goes until the whole object has been scanned.

The only difficulties are (1) that objects produced by shinyObjects interleaves both shiny.tag and list sub-objects; (2) some of the elements are sometimes NULL.

The function is a recursive function; for any branch (object of class shiny.tag) or sublist visited, it returns the modified element.

Denis Cousineau
  • 311
  • 4
  • 16