Why?
When we design Shiny apps, we should start by a wireframing session with the client, where we sketch the application design and tailor it to the business needs, thereby avoiding long refactoring hours. Yet, it is not always possible to satisfy all the expectations. Some people would like panel A to be on the left, while other would like it on the right side. To overcome this challenge, you may decide to implement more flexible UI, where elements can be rearranged by the user. Tools such as dragula JS, jQueryUI and its R widget shinyjqui, sortable js and its R counterpart sortable can be of great help. There also exist grid managers such as gridstack.js (R package: https://github.com/dreamRs/gridstackr) or masonry.
In our recent project with the pharma company BMS, with the blockr framework, we needed a layout manager so that users can interactively build a dashboard within a Shiny app. None of the above solutions could meet our needs until we discovered dockview …
dockview
dockview is a JavaScript library which provides tools to create docks, that are composed of panels. Each panel can be dragged and dropped as well as grouped with other panels. This results in layouts with almost infinite possibilities due to the ability to nest docks.
dockViewR
{dockViewR} is an HTML widget wrapping the dockview JavaScript library for R. It is available on CRAN as of version 0.1.0.
Demo
In the below demonstration, you can move panels around.
Install
To quickly get started, install it with:
pak::pak("cynkra/dockViewR")
To create a dock you call the dock_view()
function. It expects a list
of panels()
. A panel takes an id
, title
, content
and optional
arguments. id
uniqueness is internally checked so you do not
accidentally create duplicates (id can be character or numeric).
content
expects HTML elements like shiny tags, or even other
htmlwidgets. Among the optional
arguments, you most likely want
to position the panel, which is done with the position
parameter
passing a list like so:
position = list(
referencePanel = "1",
direction = "right"
)
referencePanel
refers to the id of the panel that serves as reference
and direction
indicates where the new panel should be. Valid
directions are 'within' | 'below' | 'above' | 'right' | 'left'
.
You may also want to specify some dimension constraints such as minimum/maximum width or height.
Finally, the icing on the cake is that you can choose among eight different themes with light and dark mode support. Putting this all together yields:
dock_view(
panels = list(
panel(
id = "1",
title = "Panel 1",
content = "Panel 1 content"
),
panel(
id = "2",
title = "Panel 2",
content = "Panel 2 content",
position = list(
referencePanel = "1",
direction = "right"
),
minimumWidth = 500
),
panel(
id = "3",
title = "Panel 3",
content = "Panel 3 content",
position = list(
referencePanel = "2",
direction = "below"
)
)
),
theme = "replit"
)
You may have a look to these examples to get more inspiration.
Save and restore
{dockViewR}
manages the state of a dock so you can save it and
restore any previous intermediate state. We invite the interested
readers to look at this
vignette.
An example of when you may might want to use dock state is to edit dynamically inserted panels.
Other interactions
You can programmatically interact with a dock from the Shiny server function:
- Add panel with
add_panel()
. - Remove panel with
remove_panel()
. - Move panel with
move_panel()
. - Move group of panels with
move_group()
ormove_group2()
. See more on the differences here.
What’s next?
dockview exposes individual components such as a gridview
, a grid
without drag and drop and paneview
that are collapsible panels. While
these features are not yet implemented in {dockViewR}
, we believe they
can be used to build even more interesting layouts, and we plan to add
them in later releases.
Summary
{dockViewR}
is a fresh new approach to build flexible layouts with
Shiny and even interactive R documents like
Quarto. We can’t wait to see what you can build
with {dockViewR}
and are excited to share with you in future blog
posts how we have been using it to improve the blockr framework.