From 22691310814e29d3aa2dd77739d8ba39f187ef2d Mon Sep 17 00:00:00 2001 From: Jake Tufts <137207796+JT-39@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:53:01 +0100 Subject: [PATCH] Shiny template module (#5) * New helper function to add css to UI * Removed shiny-template cookie logic and replaced with dfeshiny cookie functions. Added cookie page to app. Removed use_shinydashboard. * renv updated --- R/helper_functions.R | 184 +++++++++++++++++++-------------- global.R | 1 + renv.lock | 2 +- server.R | 103 +++---------------- ui.R | 235 +++++++++++++++++++++---------------------- 5 files changed, 238 insertions(+), 287 deletions(-) diff --git a/R/helper_functions.R b/R/helper_functions.R index f9f3391e..ba36aab1 100644 --- a/R/helper_functions.R +++ b/R/helper_functions.R @@ -1,74 +1,110 @@ -# ----------------------------------------------------------------------------- -# This is the helper file, filled with lots of helpful functions! -# -# It is commonly used as an R script to store custom functions used through the -# app to keep the rest of the app code easier to read. -# ----------------------------------------------------------------------------- - -# Expandable function --------------------------------------------------------- -expandable <- function(input_id, label, contents) { - gov_details <- shiny::tags$details( - class = "govuk-details", id = input_id, - shiny::tags$summary( - class = "govuk-details__summary", - shiny::tags$span( - class = "govuk-details__summary-text", - label - ) - ), - shiny::tags$div(contents) - ) -} - -# Value box function ---------------------------------------------------------- -# fontsize: can be small, medium or large -value_box <- function(value, subtitle, icon = NULL, - color = "blue", width = 4, - href = NULL, fontsize = "medium") { - validate_color(color) - if (!is.null(icon)) tagAssert(icon, type = "i") - - box_content <- div( - class = paste0("small-box bg-", color), - div( - class = "inner", - p(value, id = paste0("vboxhead-", fontsize)), - p(subtitle, id = paste0("vboxdetail-", fontsize)) - ), - if (!is.null(icon)) div(class = "icon-large", icon) - ) - - if (!is.null(href)) { - box_content <- a(href = href, box_content) - } - - div( - class = if (!is.null(width)) paste0("col-sm-", width), - box_content - ) -} - -# Valid colours for value box ------------------------------------------------- -valid_colors <- c("blue", "dark-blue", "green", "orange", "purple", "white") - -# Validate that only valid colours are used ----------------------------------- -validate_color <- function(color) { - if (color %in% valid_colors) { - return(TRUE) - } - - stop( - "Invalid color: ", color, ". Valid colors are: ", - paste(valid_colors, collapse = ", "), "." - ) -} - -# GSS colours ----------------------------------------------------------------- -# Current GSS colours for use in charts. These are taken from the current -# guidance here: -# https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/ -# Note the advice on trying to keep to a maximum of 4 series in a single plot -# AF colours package guidance here: https://best-practice-and-impact.github.io/afcolours/ -suppressMessages( - gss_colour_pallette <- afcolours::af_colours("categorical", colour_format = "hex", n = 4) -) +# ----------------------------------------------------------------------------- +# This is the helper file, filled with lots of helpful functions! +# +# It is commonly used as an R script to store custom functions used through the +# app to keep the rest of the app code easier to read. +# ----------------------------------------------------------------------------- + +# Expandable function --------------------------------------------------------- +expandable <- function(input_id, label, contents) { + gov_details <- shiny::tags$details( + class = "govuk-details", id = input_id, + shiny::tags$summary( + class = "govuk-details__summary", + shiny::tags$span( + class = "govuk-details__summary-text", + label + ) + ), + shiny::tags$div(contents) + ) +} + +# Value box function ---------------------------------------------------------- +# fontsize: can be small, medium or large +value_box <- function(value, subtitle, icon = NULL, + color = "blue", width = 4, + href = NULL, fontsize = "medium") { + validate_color(color) + if (!is.null(icon)) tagAssert(icon, type = "i") + + box_content <- div( + class = paste0("small-box bg-", color), + div( + class = "inner", + p(value, id = paste0("vboxhead-", fontsize)), + p(subtitle, id = paste0("vboxdetail-", fontsize)) + ), + if (!is.null(icon)) div(class = "icon-large", icon) + ) + + if (!is.null(href)) { + box_content <- a(href = href, box_content) + } + + div( + class = if (!is.null(width)) paste0("col-sm-", width), + box_content + ) +} + +# Valid colours for value box ------------------------------------------------- +valid_colors <- c("blue", "dark-blue", "green", "orange", "purple", "white") + +# Validate that only valid colours are used ----------------------------------- +validate_color <- function(color) { + if (color %in% valid_colors) { + return(TRUE) + } + + stop( + "Invalid color: ", color, ". Valid colors are: ", + paste(valid_colors, collapse = ", "), "." + ) +} + +# GSS colours ----------------------------------------------------------------- +# Current GSS colours for use in charts. These are taken from the current +# guidance here: +# https://analysisfunction.civilservice.gov.uk/policy-store/data-visualisation-colours-in-charts/ +# Note the advice on trying to keep to a maximum of 4 series in a single plot +# AF colours package guidance here: https://best-practice-and-impact.github.io/afcolours/ +suppressMessages( + gss_colour_pallette <- afcolours::af_colours("categorical", colour_format = "hex", n = 4) +) + + +#' Set CSS Style Sheet -------------------------------------------------------- +#' +#' This function generates an HTML `head` tag that includes a +#' link to a specified CSS stylesheet. +#' It can be used to dynamically set or include a CSS file in a Shiny +#' application or any other HTML-based interface that utilizes R. +#' +#' @param css_filename A character string specifying the path or +#' URL to the CSS file. +#' This should be a relative or absolute path to a `.css` file or a URL +#' pointing to an external stylesheet. +#' +#' @return A `tags$head` object containing a `link` tag that references +#' the specified CSS file. +#' This object can be directly included in the UI definition of a +#' Shiny application. +#' +#' @details +#' When included in the UI of a Shiny app, it instructs the web browser +#' to load and apply the specified CSS stylesheet. +#' +#' This function is useful when you need to modularise the +#' inclusion of stylesheets, especially in applications where the CSS file +#' might change dynamically or needs to be set programmatically. +#' +set_css_style_sheet <- function(css_filename) { + tags$head( + tags$link( + rel = "stylesheet", + type = "text/css", + href = css_filename + ) + ) +} diff --git a/global.R b/global.R index b19e08cd..0ecf48a9 100644 --- a/global.R +++ b/global.R @@ -46,6 +46,7 @@ source(here::here("R/helper_functions.R")) # Source all files in the ui_panels folder lapply(list.files(here::here("R/ui_panels/"), full.names = TRUE), source) +lapply(list.files(here::here("R/general_modules/"), full.names = TRUE), source) # Set global variables -------------------------------------------------------- diff --git a/renv.lock b/renv.lock index e19b20d8..8fc7422b 100644 --- a/renv.lock +++ b/renv.lock @@ -1306,7 +1306,7 @@ "Package": "shinyWidgets", "Version": "0.8.6", "Source": "Repository", - "Repository": "RSPM", + "Repository": "CRAN", "Requirements": [ "R", "anytime", diff --git a/server.R b/server.R index 178a2575..0ff939ff 100644 --- a/server.R +++ b/server.R @@ -68,99 +68,20 @@ server <- function(input, output, session) { # }) # Cookies logic ------------------------------------------------------------- - shiny::observeEvent(input$cookies, { - if (!is.null(input$cookies)) { - if (!("dfe_analytics" %in% names(input$cookies))) { - shinyjs::show(id = "cookieMain") - } else { - shinyjs::hide(id = "cookieMain") - msg <- list( - name = "dfe_analytics", - value = input$cookies$dfe_analytics - ) - session$sendCustomMessage("analytics-consent", msg) - if ("cookies" %in% names(input)) { - if ("dfe_analytics" %in% names(input$cookies)) { - if (input$cookies$dfe_analytics == "denied") { - ga_msg <- list(name = paste0("_ga_", google_analytics_key)) - session$sendCustomMessage("cookie-remove", ga_msg) - } - } - } - } - } else { - shinyjs::hide(id = "cookieMain") - } - }) - - # Need these set of observeEvent to create a path through the cookie banner - shiny::observeEvent(input$cookieAccept, { - msg <- list( - name = "dfe_analytics", - value = "granted" - ) - session$sendCustomMessage("cookie-set", msg) - session$sendCustomMessage("analytics-consent", msg) - shinyjs::show(id = "cookieAcceptDiv") - shinyjs::hide(id = "cookieMain") - }) - - shiny::observeEvent(input$cookieReject, { - msg <- list( - name = "dfe_analytics", - value = "denied" - ) - session$sendCustomMessage("cookie-set", msg) - session$sendCustomMessage("analytics-consent", msg) - shinyjs::show(id = "cookieRejectDiv") - shinyjs::hide(id = "cookieMain") - }) - - shiny::observeEvent(input$hideAccept, { - shinyjs::toggle(id = "cookieDiv") - }) - - shiny::observeEvent(input$hideReject, { - shinyjs::toggle(id = "cookieDiv") - }) - - shiny::observeEvent(input$remove, { - shinyjs::toggle(id = "cookieMain") - msg <- list(name = "dfe_analytics", value = "denied") - session$sendCustomMessage("cookie-remove", msg) - session$sendCustomMessage("analytics-consent", msg) - print(input$cookies) - }) - - cookies_data <- shiny::reactive({ - input$cookies - }) + output$cookie_status <- dfeshiny::cookie_banner_server( + "cookie-banner", + input_cookies = shiny::reactive(input$cookies), + parent_session = session, + google_analytics_key = google_analytics_key, + cookie_link_panel = "cookies_panel_ui" + ) - output$cookie_status <- shiny::renderText({ - cookie_text_stem <- "To better understand the reach of our dashboard tools, - this site uses cookies to identify numbers of unique users as part of Google - Analytics. You have chosen to" - cookie_text_tail <- "the use of cookies on this website." - if ("cookies" %in% names(input)) { - if ("dfe_analytics" %in% names(input$cookies)) { - if (input$cookies$dfe_analytics == "granted") { - paste(cookie_text_stem, "accept", cookie_text_tail) - } else { - paste(cookie_text_stem, "reject", cookie_text_tail) - } - } - } else { - "Cookies consent has not been confirmed." - } - }) + dfeshiny::cookies_panel_server( + id = "cookie-panel", + input_cookies = shiny::reactive(input$cookies), + google_analytics_key = google_analytics_key + ) - shiny::observeEvent(input$cookieLink, { - # Need to link here to where further info is located. You can - # updateTabsetPanel to have a cookie page for instance - shiny::updateTabsetPanel(session, "navlistPanel", - selected = "Support and feedback" - ) - }) # Dataset with timeseries data ---------------------------------------------- reactive_rev_bal <- shiny::reactive({ diff --git a/ui.R b/ui.R index 4f091ae9..2b0f3929 100644 --- a/ui.R +++ b/ui.R @@ -1,121 +1,114 @@ -# ----------------------------------------------------------------------------- -# This is the ui file. Use it to call elements created in your server file into -# the app, and define where they are placed, and define any user inputs. -# -# Other elements like charts, navigation bars etc. are completely up to you to -# decide what goes in. However, every element should meet accessibility -# requirements and user needs. -# -# This is the user-interface definition of a Shiny web application. You can -# run the application by clicking 'Run App' above. -# -# Find out more about building applications with Shiny here: -# -# http://shiny.rstudio.com/ -# -# The documentation for GOV.UK components can be found at: -# -# https://github.com/moj-analytical-services/shinyGovstyle -# -# ----------------------------------------------------------------------------- -ui <- function(input, output, session) { - shiny::fluidPage( - # Set application metadata ------------------------------------------------ - tags$head(HTML("Local Authority Interactive Tool (LAIT)")), - tags$head(tags$link(rel = "shortcut icon", href = "dfefavicon.png")), - shinytitle::use_shiny_title(), - tags$html(lang = "en"), - # Add meta description for search engines - metathis::meta() |> - metathis::meta_general( - application_name = "Local Authority Interactive Tool (LAIT)", - description = "Local Authority Interactive Tool (LAIT)", - robots = "index,follow", - generator = "R-Shiny", - subject = "data tool", - rating = "General", - referrer = "no-referrer" - ), - - # Custom disconnect function ---------------------------------------------- - # Variables used here are set in the global.R file - dfeshiny::custom_disconnect_message( - links = sites_list, - publication_name = parent_pub_name, - publication_link = parent_publication - ), - - # Load javascript dependencies -------------------------------------------- - shinyWidgets::useShinydashboard(), - shinyjs::useShinyjs(), - - # Cookies ----------------------------------------------------------------- - # Setting up cookie consent based on a cookie recording the consent: - # https://book.javascript-for-r.com/shiny-cookies.html - tags$head( - tags$script( - src = paste0( - "https://cdn.jsdelivr.net/npm/js-cookie@rc/", - "dist/js.cookie.min.js" - ) - ), - tags$script(src = "cookie-consent.js") - ), - shinyGovstyle::cookieBanner("Local Authority Interactive Tool (LAIT)"), - - # Google analytics -------------------------------------------------------- - tags$head(shiny::includeHTML(("google-analytics.html"))), - tags$head( - tags$link( - rel = "stylesheet", - type = "text/css", - href = "dfe_shiny_gov_style.css" - ) - ), - - # Header ------------------------------------------------------------------ - shinyGovstyle::header( - main_text = "", - main_link = "https://www.gov.uk/government/organisations/department-for-education", - secondary_text = "Local Authority Interactive Tool (LAIT)", - logo = "images/DfE_logo_landscape.png", - logo_width = 150, - logo_height = 32 - ), - - # Beta banner ------------------------------------------------------------- - shinyGovstyle::banner( - "beta banner", - "beta", - paste0( - "This Dashboard is in beta phase and we are still reviewing performance - and reliability. ", - "In case of slowdown or connection issues due to high demand, we have - produced two instances of this site which can be accessed at the - following links: ", - "Site 1 and ", - "Site 2." - ) - ), - - # Nav panels -------------------------------------------------------------- - shiny::navlistPanel( - "", - id = "navlistPanel", - widths = c(2, 8), - well = FALSE, - # Content for these panels is defined in the R/ui_panels/ folder - example_tab_1_panel(), - user_guide_panel(), - a11y_panel(), - dfeshiny::support_panel( - team_email = "explore.statistics@education.gov.uk", - repo_name = "https://github.com/dfe-analytical-services/local-authority-interactive-tool", - form_url = "https://forms.office.com" - ) - ), - - # Footer ------------------------------------------------------------------ - shinyGovstyle::footer(full = TRUE) - ) -} +# ----------------------------------------------------------------------------- +# This is the ui file. Use it to call elements created in your server file into +# the app, and define where they are placed, and define any user inputs. +# +# Other elements like charts, navigation bars etc. are completely up to you to +# decide what goes in. However, every element should meet accessibility +# requirements and user needs. +# +# This is the user-interface definition of a Shiny web application. You can +# run the application by clicking 'Run App' above. +# +# Find out more about building applications with Shiny here: +# +# http://shiny.rstudio.com/ +# +# The documentation for GOV.UK components can be found at: +# +# https://github.com/moj-analytical-services/shinyGovstyle +# +# ----------------------------------------------------------------------------- +ui <- function(input, output, session) { + shiny::fluidPage( + # Set application metadata ------------------------------------------------ + tags$head(HTML("Local Authority Interactive Tool (LAIT)")), + tags$head(tags$link(rel = "shortcut icon", href = "dfefavicon.png")), + shinytitle::use_shiny_title(), + tags$html(lang = "en"), + # Add meta description for search engines + metathis::meta() |> + metathis::meta_general( + application_name = "Local Authority Interactive Tool (LAIT)", + description = "Local Authority Interactive Tool (LAIT)", + robots = "index,follow", + generator = "R-Shiny", + subject = "data tool", + rating = "General", + referrer = "no-referrer" + ), + + # Custom disconnect function ---------------------------------------------- + # Variables used here are set in the global.R file + dfeshiny::custom_disconnect_message( + links = sites_list, + publication_name = parent_pub_name, + publication_link = parent_publication + ), + + # Styling with CSS + set_css_style_sheet("dfe_shiny_gov_style.css"), + + # Load javascript dependencies -------------------------------------------- + shinyWidgets::useShinydashboard(), + shinyjs::useShinyjs(), + + # Cookies ----------------------------------------------------------------- + # Setting up cookie consent based on a cookie recording the consent: + # https://book.javascript-for-r.com/shiny-cookies.html + dfeshiny::dfe_cookie_script(), + dfeshiny::cookie_banner_ui( + "cookie-banner", + "Local Authority Interactive Tool (LAIT)" + ), + + # Header ------------------------------------------------------------------ + shinyGovstyle::header( + main_text = "", + main_link = "https://www.gov.uk/government/organisations/department-for-education", + secondary_text = "Local Authority Interactive Tool (LAIT)", + logo = "images/DfE_logo_landscape.png", + logo_width = 150, + logo_height = 32 + ), + + # Beta banner ------------------------------------------------------------- + shinyGovstyle::banner( + "beta banner", + "beta", + paste0( + "This Dashboard is in beta phase and we are still reviewing performance + and reliability. ", + "In case of slowdown or connection issues due to high demand, we have + produced two instances of this site which can be accessed at the + following links: ", + "Site 1 and ", + "Site 2." + ) + ), + + # Nav panels -------------------------------------------------------------- + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + # Content for these panels is defined in the R/ui_panels/ folder + example_tab_1_panel(), + user_guide_panel(), + a11y_panel(), + dfeshiny::support_panel( + team_email = "explore.statistics@education.gov.uk", + repo_name = "https://github.com/dfe-analytical-services/local-authority-interactive-tool", + form_url = "https://forms.office.com" + ), + # Cookies info + dfeshiny::cookies_panel_ui( + id = "cookie-panel", + google_analytics_key = google_analytics_key + ) + ), + + # Footer ------------------------------------------------------------------ + shinyGovstyle::footer(full = TRUE) + ) +}