.generalOrganizePanels <- "iSEE_INTERNAL_organize_panels"
#' Panel organization observers for \code{\link{iSEE}}
#'
#' A function to set up observers for the panel organization observers used in the app.
#' These handle the creation, destruction, re-ordering and resizing of all panels.
#'
#' @param session The Shiny session object from the server function.
#' @param input The Shiny input object from the server function.
#' @param output The Shiny output object from the server function.
#' @param se The \linkS4class{SummarizedExperiment} object.
#' @param pObjects An environment containing global parameters generated in the \code{\link{iSEE}} app.
#' @param rObjects A reactive list of values generated in the \code{\link{iSEE}} app.
#'
#' @details
#' The application does not immediately respond to user changes in the reorganization modal.
#' Rather, the user must press the \dQuote{Apply settings} for the application to fully re-render.
#' This avoids wasting time in repeated re-renderings before the reorganization is fully complete.
#'
#' The exception to the above rule lies in the interface elements for changing panel sizes.
#' These are immediately re-rendered upon changes in panel organization while preserving any user-set values of the height/width.
#' Achieving this requires the use of a \dQuote{working memory}.
#'
#' @return Observers are created in the server function in which this is called.
#' A \code{NULL} value is invisibly returned.
#'
#' @author Aaron Lun
#' @rdname INTERNAL_organization_observers
#' @importFrom shiny renderUI reactiveValues observeEvent
#' showModal modalDialog isolate removeModal
.create_organization_observers <- function(se, input, output, session, pObjects, rObjects) {
# nocov start
output$allPanels <- renderUI({
force(rObjects$rerender)
rObjects$rerendered <- .increment_counter(isolate(rObjects$rerendered))
.panel_generation(se, pObjects$memory)
})
# nocov end
# Persistent objects to give the modal a 'working memory'.
# These get captured in the current environment to persist
# when observeEvent's expression actually gets executed.
org_pObjects <- new.env()
org_pObjects$initialized <- FALSE
org_pObjects$ui_updating <- FALSE
org_rObjects <- reactiveValues(rerender=0)
available_enc <- vapply(pObjects$reservoir, .encodedName, "")
names(available_enc) <- vapply(pObjects$reservoir, .fullName, "")
# nocov start
observeEvent(input[[.generalOrganizePanels]], {
enc_names <- .define_memory_panel_choices(pObjects$memory)
org_pObjects$memory <- pObjects$memory
org_pObjects$counter <- pObjects$counter
if (!org_pObjects$initialized) {
for (x in org_pObjects$memory) {
.create_width_height_observers(x, input, org_pObjects)
}
org_pObjects$initialized <- TRUE
}
# Here we disable the observers until the UI has been rerendered with
# the latest memory. Otherwise, we'll be stuck in a tricky situation
# where the modal is launched before 'panelParams' has a chance to
# rerender, causing the old UI elements to trigger the width/height
# observers to set the old defaults.
org_pObjects$ui_updating <- TRUE
org_rObjects$rerender <- .increment_counter(org_rObjects$rerender)
showModal(modalDialog(
title="Panel organization", size="m", fade=TRUE,
footer=NULL, easyClose=TRUE,
actionButton("update_ui", "Apply settings", icon=icon("object-ungroup"), width='100%'),
hr(),
selectizeInput("panel_order", label=NULL,
selected=enc_names,
choices=c(enc_names, available_enc), # Choosing the unnumbered panel choice will add a new panel.
multiple=TRUE,
options=list(plugins=list('remove_button', 'drag_drop')), width="500px"),
uiOutput("panelParams")
))
}, ignoreInit=TRUE)
# nocov end
# nocov start
output$panelParams <- renderUI({
force(org_rObjects$rerender)
org_pObjects$ui_updating <- FALSE
.panel_organization(org_pObjects$memory)
})
# nocov end
# nocov start
observeEvent(input$panel_order, {
ipo <- unname(input$panel_order)
enc_names <- .define_memory_panel_choices(org_pObjects$memory, named=FALSE)
if (identical(ipo, unname(enc_names))) {
return(NULL)
}
adjusted <- org_pObjects$memory[ipo]
# Adding some newly created panels. This assumes that
# the reservoir types have already been cached and refined.
if (length(added <- which(ipo %in% available_enc))) {
for (a in added) {
mode <- ipo[a]
latest <- pObjects$reservoir[[mode]]
idx <- org_pObjects$counter[[mode]] + 1L
latest[[.organizationId]] <- idx
adjusted[[a]] <- latest
names(adjusted)[a] <- paste0(mode, idx)
org_pObjects$counter[[mode]] <- idx
.create_width_height_observers(latest, input, org_pObjects)
}
}
updated_names <- .define_memory_panel_choices(adjusted)
updateSelectizeInput(session, 'panel_order',
choices=c(updated_names, available_enc), selected=updated_names)
org_pObjects$memory <- adjusted
org_rObjects$rerender <- .increment_counter(org_rObjects$rerender)
}, ignoreInit=TRUE,
ignoreNULL=FALSE) # necessary when users remove the last panel from the UI
# nocov end
# nocov start
observeEvent(input$update_ui, {
left <- names(org_pObjects$memory)
right <- names(pObjects$memory)
pObjects$memory <- org_pObjects$memory
pObjects$counter <- org_pObjects$counter
added <- setdiff(left, right)
for (a in added) {
instance <- pObjects$memory[[a]]
.createObservers(instance, se, input=input, session=session, pObjects=pObjects, rObjects=rObjects)
.renderOutput(instance, se, output=output, pObjects=pObjects, rObjects=rObjects)
}
pObjects$selection_links <- .spawn_multi_selection_graph(pObjects$memory)
pObjects$aesthetics_links <- .spawn_single_selection_graph(pObjects$memory)
pObjects$dynamic_multi_selections <- .spawn_dynamic_multi_selection_list(pObjects$memory)
pObjects$dynamic_single_selections <- .spawn_dynamic_single_selection_list(pObjects$memory)
# NOTE: there should be no need to updateSelectize on the choice of
# linkable panels; this should be handled by the rerendering of the UI.
# This should also trigger the corresponding observers to update the
# memory and propagate the required downstream changes, which avoids us
# having to recapitulate all of that in this observer. As a consequence,
# though, some downstream observers need to be robustified to references
# to panels that no longer exist in the brief time after panel deletion
# but before the memory is resync'd. See .add_interpanel_link() and
# .create_dimname_observers() for some affected functions.
rObjects$rerender <- .increment_counter(rObjects$rerender)
removeModal(session)
}, ignoreInit=TRUE)
# nocov end
invisible(NULL)
}
#' List active panels in memory
#'
#' @param memory A list of \linkS4class{Panel} instances representing the app state.
#' @param named A logical scalar indicating whether to name the return value with panel full names.
#'
#' @return A character name of encoded panel identifiers, optionally named with their full name.
#'
#' @rdname INTERNAL_define_memory_panel_choices
.define_memory_panel_choices <- function(memory, named=TRUE) {
enc_names <- vapply(memory, .getEncodedName, "")
if (named) {
names(enc_names) <- vapply(memory, .getFullName, "")
}
enc_names
}
#' @importFrom shiny observeEvent
.create_width_height_observers <- function(x, input, org_pObjects) {
panel_name <- .getEncodedName(x)
width_name <- paste0(panel_name, "_", .organizationWidth)
# nocov start
observeEvent(input[[width_name]], {
if (org_pObjects$ui_updating) {
return(NULL)
}
cur.width <- slot(org_pObjects$memory[[panel_name]], .organizationWidth)
new.width <- as.integer(input[[width_name]])
if (!isTRUE(all.equal(new.width, cur.width))) {
slot(org_pObjects$memory[[panel_name]], .organizationWidth) <- new.width
}
}, ignoreInit=TRUE)
# nocov end
height_name <- paste0(panel_name, "_", .organizationHeight)
# nocov start
observeEvent(input[[height_name]], {
if (org_pObjects$ui_updating) {
return(NULL)
}
cur.height <- slot(org_pObjects$memory[[panel_name]], .organizationHeight)
new.height <- as.integer(input[[height_name]])
if (!isTRUE(all.equal(new.height, cur.height))) {
slot(org_pObjects$memory[[panel_name]], .organizationHeight) <- new.height
}
}, ignoreInit=TRUE)
# nocov end
invisible(NULL)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.