---
title: "Dynamic App Configuration"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Dynamic App Configuration}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, echo=FALSE, message=FALSE}
library(bslib)
```

## Update app configuration and theme at runtime

Sometimes, you want to update the configuration of your app at runtime. Think about changing the global configuration for one or multiple components or updating the theme of your app.

<br>

In `{shinyMobile}`, there are two functions that can update (parts of) your app:

- `updateF7App()`: injecting any configuration inside the current running instance, including `theme`, `dark`, and `color.`
- `updateF7Entity()`: update a subset of Framework7 instances from the server.

### Updating theme, dark mode and color

In essence, `updateF7App()` is a pretty simple function: it takes a list of options and passes it to the running app instance.

```r
updateF7App(
  options = list(
    theme = "md",
    dark = TRUE,
    color = "#007aff"
    # any other options
    # ...
  )
)
```

The following example shows how to update the theme, dark mode, and color theme of the app:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateApp" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateApp">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

colors <- c(
  lightblue = "#5ac8fa",
  pink = "#ff2d55",
  teal = "#009688",
  yellow = "#ffcc00"
)

shinyApp(
  ui = f7Page(
    title = "Update App",
    options = (
      list(
        color = "#5ac8fa"
      )
    ),
    f7SingleLayout(
      navbar = f7Navbar(title = "Update App"),
      f7BlockTitle("Update theme"),
      f7Segment(
        f7Button(
          inputId = "theme_ios",
          "iOS theme"
        ),
        f7Button(
          inputId = "theme_md",
          "MD theme"
        )
      ),
      f7BlockTitle("Set dark mode"),
      f7Segment(
        f7Button(
          inputId = "enable_darkmode",
          "Enable darkmode"
        ),
        f7Button(
          inputId = "disable_darkmode",
          "Disable darkmode"
        )
      ),
      f7BlockTitle("Change color theme"),
      f7Segment(
        tagList(
          lapply(names(colors),
                 function(color) {
                   f7Button(
                     inputId = paste0("color_", color),
                     label = color,
                     color = color,
                   )
                 }
          )
        )
      )
    )
  ),
  server = function(input, output, session) {

    observeEvent(input$theme_ios, {
      updateF7App(
        options = list(
          theme = "ios"
        )
      )
    })

    observeEvent(input$theme_md, {
      updateF7App(
        options = list(
          theme = "md"
        )
      )
    })

    observeEvent(input$enable_darkmode, {
      updateF7App(
        options = list(
          dark = TRUE
        )
      )
    })

    observeEvent(input$disable_darkmode, {
      updateF7App(
        options = list(
          dark = FALSE
        )
      )
    })

    lapply(names(colors), function(color) {
      observeEvent(input[[paste0("color_", color)]], {
        updateF7App(
          options = list(
            color = colors[color]
          )
        )
      })
    })

  }
)
```
</div>

Note that the color can both be a color of `getF7Colors()` (like "primary", "pink" or  "teal") or a hexadecimal color representing those colors. You can call `updateF7App` multiple times, and the app will update the given configuration settings accordingly.

### Updating component configuration

Besides updating the theme, dark mode, and color, you can also update the configuration of a specific type of component. For example, you can update the configuration of all the `f7Dialog()` components in an app:

```r
updateF7App(
  options = list(
    dialog = list(
      buttonOk = "Yeaaaah!",
      buttonCancel = "Ouuups!"
    )
  )
)
```

Above code will update the text shown in the dialog buttons. In the below app, there are three `f7Dialog()`s that will be updated when the "Update config" button is clicked:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateDialog" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateDialog">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "Update App",
    f7SingleLayout(
      navbar = f7Navbar(title = "Update App"),
      f7BlockTitle("Update f7Dialog configuration"),
      f7Segment(
        f7Button(
          inputId = "goButton",
          "Show f7Dialog 1"
        ),
        f7Button(
          inputId = "goButton2",
          "Show f7Dialog 2"
        ),
        f7Button(
          inputId = "update",
          "Update config"
        )
      ),
    )
  ),
  server = function(input, output, session) {
    observeEvent(input$goButton, {
      f7Dialog(
        id = "dialog1",
        title = "Dialog title",
        text = "This is a confirm dialog",
        type = "confirm"
      )
    })
    
    observeEvent(input$goButton2, {
      f7Dialog(
        title = "Another dialog title",
        text = "This is an alert"
      )
    })

    observeEvent(input$update, {
      updateF7App(
        options = list(
          dialog = list(
            buttonOk = "Yeaaaah!",
            buttonCancel = "Ouuups!"
          )
        )
      )
      
      f7Dialog(
        id = "dialog2",
        title = "Warning",
        type = "confirm",
        text = "Look at me, I have a new buttons!"
      )
            
    })
    
  }
)
```
</div>

There are many more components that can be updated in this way. For example:

- `f7Card()`: update the [card configuration](https://framework7.io/docs/cards#card-app-parameters)
- `f7Dialog()`: update the [dialog configuration](https://framework7.io/docs/dialog#dialog-shortcuts), like in the examples above
- `f7Panel()`: update (part of) the [panel configuration](https://framework7.io/docs/panel#panel-parameters)
- `f7PhotoBrowser()`: update (part of) the [photo browser configuration](https://framework7.io/docs/photo-browser#photo-browser-parameters)
- `f7Swipeout()`: update the [swipeout configuration](https://framework7.io/docs/swipeout#swipeout-app-parameters)
- `f7Toolbar()`: update the [toolbar configuration](https://framework7.io/docs/toolbar-tabbar#toolbar-app-parameters)

<br>

Basically, whenever a component in the [Framework7 documentation](https://framework7.io/docs/) contains a section on "global app parameters", you can update these parameters using `updateF7App`.

## Update individual components at runtime

If you're not looking to update the entire app or a particular group of components, but rather a specific component, you can use `updateF7Entity`. This function allows you to update a subset of Framework7 instances from the server:

```r
updateF7Entity(
  id = "idtoupdate",
    options = list(
      # your configuration here
    )
)
```

All components that can be updated in this way are:

- `f7ActionSheet()`
- `f7Gauge()`
- `f7ListIndex()`
- `f7PhotoBrowser`()
- `f7Popup()`
- `f7Searchbar()`
- `f7Swiper()`

<br>

There are still specific update functions for some of the above components (like `updateF7ActionSheet()` and `updateF7Gauge()`), but the aim of `updateF7Entity()` is to provide a more general way to update them without calling their specific update function.

<br>

Below are some examples of how to use `updateF7Entity()`.

### Action Sheet

You could update (or reset) the content of an `f7ActionSheet()` like so:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateActionSheet" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateActionSheet">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "Update Entity",
    f7SingleLayout(
      navbar = f7Navbar(title = "Update action sheet instance"),
      f7Segment(
        f7Button(
          inputId = "goButton",
          label = "Go!"
        ),
        f7Button(
          inputId = "update",
          label = "Update config"
        ),
        f7Button(
          inputId = "reset",
          label = "Reset"
        )
      )
    )
  ),
  server = function(input, output, session) {
    observeEvent(input$goButton, {
      f7ActionSheet(
        grid = TRUE,
        id = "action1",
        buttons = list(
          list(
            text = "Notification",
            icon = f7Icon("info"),
            color = NULL
          ),
          list(
            text = "Dialog",
            icon = f7Icon("lightbulb_fill"),
            color = NULL
          )
        )
      )
    })

    observeEvent(input$update, {
      updateF7Entity(
        id = "action1",
        options = list(
          buttons = list(
            list(
              text = "Notification",
              icon = f7Icon("info"),
              color = NULL
            )
          )
        )
      )
    })

    observeEvent(input$reset, {
      updateF7Entity(
        id = "action1",
        options = list(
          buttons = list(
            list(
              text = "Notification",
              icon = f7Icon("info"),
              color = NULL
            ),
            list(
              text = "Dialog",
              icon = f7Icon("lightbulb_fill"),
              color = NULL
            )
          )
        )
      )
    })
  }
)
```
</div>

In the above example, we update the content of `buttons` in the `f7ActionSheet()` with the id `action1`. This way, you can update the component at runtime.

<br>

A similar result can be achieved with `updateF7ActionSheet()`.

### Gauge

Changing the value of a gauge is a common task and perfectly possible with `updateF7Entity()`:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateGauge" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateGauge">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "Gauges",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7Gauge"),
      f7Segment(
        f7Button(
          inputId = "update",
          label = "Update Gauge"
          ),
        f7Button(
          inputId = "reset",
          label = "Reset"
          )
      ),
      f7Block(
        f7Gauge(
          id = "mygauge",
          type = "semicircle",
          value = 50,
          borderColor = "#2196f3",
          borderWidth = 10,
          valueFontSize = 41,
          valueTextColor = "#2196f3",
          labelText = "amount of something"
        )
      )
    )
  ),
  server = function(input, output, session) {
    observeEvent(input$update, {
      new_val <- 75
      updateF7Entity(
        id = "mygauge",
        options = list(
          # Must be between 0 and 1
          value = new_val / 100, 
          valueText = paste0(new_val, "%"),
          labelText = "New label!"
        )
      )
    })
    
    observeEvent(input$reset, {
      updateF7Entity(
        id = "mygauge",
        options = list(
          value = 50 / 100,
          valueText = "50%",
          labelText = "amount of something"
        )
      )
    })
  }
)
```
</div>

A similar result can be achieved with the `updateF7Gauge()`.

### List Index

The `f7ListIndex()` component has limited [configuration options](https://framework7.io/docs/list-index#list-index-parameters). Most of the default options make the most sense for this particular component. But, you could update the label (bubble with selected index when you swipe over list index) of a `f7ListIndex()` component like so:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateListIndex" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateListIndex">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "My app",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7List"),
      f7Segment(
        f7Button(
          inputId = "update",
          label = "Update"
        ),
        f7Button(
          inputId = "reset",
          label = "Reset"
        )
      ),
      f7List(
            id = "mycontacts",
            mode = "contacts",
            lapply(1:3, function(i) {
              f7ListGroup(
                title = LETTERS[i],
                lapply(1:10, function(j) f7ListItem(letters[j]))
              )
            })
          )
    )
  ),
  server = function(input, output, session) {
    f7ListIndex(id = "contacts", 
                target = "#mycontacts", 
                label = TRUE)
    
    observeEvent(input$update, {
      updateF7Entity(
        "contacts",
        options = list(
          label = FALSE
        )
      )
    })
    
    observeEvent(input$reset, {
      updateF7Entity(
        "contacts",
        options = list(
          label = TRUE
        )
      )
    })
    
  }
)
```
</div>

### Photo Browser

Using `updateF7Entity()`, you can update the photos that are displayed in the photo browser and any other configuration:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updatePhotoBrowser" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updatePhotoBrowser">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "f7PhotoBrowser",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7PhotoBrowser"),
      f7Segment(
        f7Button(
          inputId = "togglePhoto",
          label = "Open photo"
        ),
        f7Button(
          inputId = "update",
          label = "Update"
        )
      )
    )
  ),
  server = function(input, output, session) {
    observeEvent(input$togglePhoto, {
      f7PhotoBrowser(
        id = "photobrowser1",
        theme = "dark",
        type = "page",
        photos = list(
          list(url = "https://cdn.framework7.io/placeholder/sports-1024x1024-1.jpg"),
          list(url = "https://cdn.framework7.io/placeholder/sports-1024x1024-2.jpg"),
          list(url = "https://cdn.framework7.io/placeholder/sports-1024x1024-3.jpg",
               caption = "Me cycling")
        ),
        thumbs = c(
          "https://cdn.framework7.io/placeholder/sports-1024x1024-1.jpg",
          "https://cdn.framework7.io/placeholder/sports-1024x1024-2.jpg",
          "https://cdn.framework7.io/placeholder/sports-1024x1024-3.jpg"
        )
      )
    })

    observeEvent(input$update, {
      updateF7Entity(
        "photobrowser1",
        options = list(
          type = "popup",
          popupPush = TRUE,
          toolbar = FALSE,
          photos = list(
          list(url = "https://cdn.framework7.io/placeholder/sports-1024x1024-1.jpg")
          ),
          thumbs = list("https://cdn.framework7.io/placeholder/sports-1024x1024-1.jpg")
        )
      )
    })
  }
)
```
</div>

### Popup

For the `f7Popup()` component, you will need a bit more code to make updates. Within `updateF7Entity()`, you need to provide the necessary HTML of the popup content:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updatePopup" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updatePopup">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "Popup",
    f7SingleLayout(
      navbar = f7Navbar(
        title = "f7Popup"
      ),
      f7Segment(
        f7Button(
          inputId = "toggle", 
          label = "Toggle Popup"
        ),
        f7Button(
          inputId = "update", 
          label = "Update Popup")
      )
    )
  ),
  server = function(input, output, session) {
    output$res <- renderPrint(input$text)
    
    observeEvent(input$toggle, {
      f7Popup(
        id = "popup",
        title = "My first popup",
        f7Text(
          "text", "Popup content",
          "This is my first popup ever, I swear!"
        ),
        verbatimTextOutput("res")
      )
    })
    
    observeEvent(input$update, {
      updateF7Entity(
        id = "popup",
        options = list(
          swipeToClose = TRUE,
          animate = FALSE,
          closeOnEscape = TRUE,
          # Content must contain the popup
          # layout!!!
          content = '<div class="popup">
            <div class="view">
              <div class="page">
                <div class="navbar">
                  <div class="navbar-bg"></div>
                  <div class="navbar-inner">
                    <div class="title">Popup</div>
                    <div class="right">
                      <!-- Link to close popup -->
                      <a class="link popup-close">Close</a>
                    </div>
                  </div>
                </div>
                <div class="page-content">
                  <div class="block">New content ...</div>
                </div>
              </div>
            </div>
          </div>'
        )
      )
    })
  }
)
```
</div>

### Searchbar

In a Searchbar, you can have a disable button: a "Cancel" (or "Back") button that will deactivate the Searchbar, reset search results and clear search field. In below example this button is excluded with `options(list(disableButton = FALSE))` when update is being clicked:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateSearchbar" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateSearchbar">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

cities <- names(precip)

shinyApp(
  ui = f7Page(
    title = "Expandable searchbar",
    f7SingleLayout(
      navbar = f7Navbar(
        title = "f7Searchbar with trigger",
        subNavbar = f7SubNavbar(
          f7Searchbar(
            id = "search1", 
            expandable = TRUE
          )
        )
      ),
      f7Segment(
        f7Button(
          inputId = "update",
          label = "Update"
        ),
        f7Button(
          inputId = "reset",
          label = "Reset"
        )
      ),
      f7Block(
        f7SearchbarTrigger(targetId = "search1")
      ) %>% f7HideOnSearch(),
      f7List(
        lapply(seq_along(cities), function(i) {
          f7ListItem(cities[i])
        })
      ) %>% f7Found(),
      f7Block(
        p("Nothing found")
      ) %>% f7NotFound()
    )
  ),
  server = function(input, output) {
    observeEvent(input$update, {
      updateF7Entity(
        id = "search1",
        options = list(
          disableButton = FALSE
        )
      )
    })
    
    observeEvent(input$reset, {
      updateF7Entity(
        id = "search1",
        options = list(
          disableButton = TRUE
        )
      )
    })
  }
)
```
</div>

### Swiper

There are many options to customize the `f7Swiper()` component. Note that on desktop you need to click and hold to swipe:

```{r, eval=TRUE, echo=FALSE}
card(
  shinyMobile:::create_app_link(
    " ", # nolint
    "app",
    header = FALSE
  ),
  full_screen = TRUE,
  style = "width: 393px; margin: 0 auto; float: none;"
)
```
<div class="text-center mt-4">
<a class="btn btn-primary" data-bs-toggle="collapse" href="#updateSwiper" role="button" aria-expanded="false" aria-controls="singleLayout">
Code
</a>
</div>

<div class="collapse" id="updateSwiper">
```{r, eval=FALSE}
library(shiny)
library(shinyMobile)

shinyApp(
  ui = f7Page(
    title = "Swiper",
    f7SingleLayout(
      navbar = f7Navbar(title = "f7Swiper"),
      f7Segment(
        f7Button(
          inputId = "update",
          label = "Update"
        ),
        f7Button(
          inputId = "reset",
          label = "Reset"
        )
      ),
      f7Swiper(
        id = "swiper",
        options = list(
          navigation = list(
            enabled = FALSE
          ),
          pagination = list(
            enabled = TRUE,
            el = ".swiper-pagination", 
            clickable = TRUE
          )
        ),
        lapply(1:20, \(c) {
          f7Slide(
            f7Card(
              title = sprintf("Slide %s", c)
            )
          )
        })
      )
    )
  ),
  server = function(input, output) {
    observeEvent(input$update, {
      updateF7Entity(
        "swiper",
        options = list(
          speed = 100,
          slidesPerView = 2,
          spaceBetween = 10,
          autoplay = TRUE,
          scrollbar = list(
            enabled = FALSE
          ),
          pagination = list(
            type = "progressbar",
            enabled = TRUE
          ),
          grid = list(
            fill = "columns",
            rows = 4
          )
        )
      )
    })
    
    observeEvent(input$reset, {
      updateF7Entity(
        "swiper",
        options = list(
          speed = 400,
          slidesPerView = "auto",
          spaceBetween = 50,
          autoplay = FALSE,
          scrollbar = list(
            enabled = TRUE
          ),
          pagination = list(
            type = "bullets",
            enabled = TRUE
          ),
          grid = list(
            fill = "column",
            rows = 1
          )
        )
      )
    })
  }
)
```
</div>
