Posts Tagged R

Dropdown menu in R Shiny

Shiny framework use bootstrap 3 that support drop down menus, all you have to do, to have drop down menu in Shiny, is to prepare html and the bootstrap will handle style and logic for opening and closing the menu. Here is the handy function that will create drop down menu for you:

dropdownMenu <- function(label=NULL, icon=NULL, menu=NULL) {
  ul <- lapply(names(menu), function(id) {
    if (is.character(menu[[id]])) {
      tags$li(actionLink(id, menu[[id]]))
    } else {
      args <- menu[[id]]
      args$inputId <- id
      tags$li(do.call(actionLink, args))
    }
  })
  ul$class <- "dropdown-menu"
  tags$div(
    class = "dropdown",
    tags$button(
      class = "btn btn-default dropdown-toggle",
      type = "button",
      `data-toggle` = "dropdown",
      label,
      `if`(!is.null(icon), icon, tags$span(class="caret"))
    ),
    do.call(tags$ul, ul)
  )
}

The function will create drop down menu with caret like on the demo on getbootstrap.com or if you provide icon you will get only that icon the label is optional so you can have dropdown menu with just an icon. It use actionLink to create links so you don't need to to create custom input widget

You can use this function like this, to create hamburger menu:

dropdownMenu(
  icon = icon("bars"),
  menu = list(edit = "edit item", rename = list(label = "address", icon = icon("id-card"))
)

and it will create two inputs input$edit and input$rename so you can add observeEvent to listen to on click.

Advertisements

, ,

Leave a comment

Execute javascript code when R shiny output have been rendered

If you have reactive observer like this:

output$resultsTable <- renderTable({
   tableData
});

and you want to modify the table from javascript you can’t put shinyjs::runjs after table is rendered because the table data need to be the last expression of the reactive renderTable observer. The solution is to use new feature of JavaScript, implemented in browsers, which is mutation observer. The browser support is very good, you can check it on caniuse. The solution that will work, look like this (using jQuery plugin):

$.fn.onRender = function(callback, options) {
  if (arguments[0] == 'cancel') {
    return this.each(function() {
      var self = $(this);
      var render = self.data('render');
      if (render) {
        render.observer.disconnect();
        self.removeData('render');
      }
    });
  } else {
    var settings = $.extend({
      // if set to true will remove mutation observer after first mutation
      oneTime: true,
      // if set to true will always execute and clear mutation observer on first mutation
      // default will not fire callback when mution is recalcuation of any element
      strict: false,
      observer: {childList: true, subtree: true, attributes: true},
      check: function(node) { return node.closest('body').length; }
    }, options || {});
    return this.each(function() {
      var node = $(this);
      var render = node.data('render');
      if (!render) {
        render = {
          callbacks: $.Callbacks(),
          observer: new MutationObserver(function(mutations) {
            // if recalculating wait for next mutation
            if (!(node.hasClass('recalculating') || node.find('.recalculating').length) ||
                settings.strict) {
              if (settings.check(node)) {
                render.callbacks.fireWith(node[0]);
              }
              if (settings.oneTime) {
                node.onRender('cancel');
              }
            }
          })
        };
        render.observer.observe(node[0], settings.observer);
        node.data('render', render);
      }
      render.callbacks.add(callback);
    });
  }
};

You can use it like this:

function changeTable(selector) {
   $(selector).onRender(function() {
      $(this).find('thead th').css('color', 'red');
   });
}
output$resultsTable <- renderTable({
   shinyjs::runjs("changeTable('#resultsTable')")
   tableData
});

if you want to execute code once and use it outside of shiny observer you can use option oneTime: false like this:

function updateTableWhenRender(selector) {
   $(selector).onRender(function() {
      $(this).find('thead th').css('color', 'red');
   }, {oneTime: false});
}

then the function can be put outside of observer like this:

output$resultsTable <- renderTable({
   tableData
});
shinyjs::runjs("updateTableWhenRender('#resultsTable')")

To cancel the mutation observer, that was created with the plugin, you can execute this line of code:

$(selector).onRender('cancel');

If you have multiple renders and have onRender on each render call you should call cancel before you call onRender again.

, ,

Leave a comment

%d bloggers like this: