Classically, websites are built using HTML, CSS, and JavaScript to
construct the “client side” of the site, which runs in a user’s browser
on their local computer, and by using SQL plus a general programming
language like Python to construct the “server side” of the website,
which runs remotely on a server that communicates back and forth with
the user’s browser to ensure the site is constructed and displayed to
the user as intended.
R Shiny allows you to build a website using just R + R Shiny code,
such that deep understanding of other web development languages like PHP
and JavaScript is not required.
However, some familiarity with HTML and CSS is practically essential
to build a great app and to really understand what you are doing when
writing “R Shiny” code.
That’s ok—HTML and CSS are very approachable languages compared to
general programming languages like R.
HTML tells a browser what to put where when building a website.
Websites are just “HTML boxes holding stuff and/or other boxes.” Some
boxes force new lines after them; others don’t. How boxes will “flow” on
wide versus narrow screens is an important design consideration.
CSS tells a browser how to display each element on a website. It
consists of rules targeting specific HTML boxes that
specify new values for those boxes’ aesthetic
properties.
Website design has matured such that most websites look and feel
broadly similar and share many elements, such as buttons, links,
widgets, articles, media, and so on. R Shiny lets us
add these same elements to our apps.
Use the three-file system to organize your app’s code to benefit
from several R Shiny features within RStudio.
A global.R file is handy for storing all the code
needed to fuel your app’s start-up and any other code your app needs
that only needs to run once.
A CSS stylesheet holds CSS code for dictating your app’s aesthetics.
One can be linked to an app inside of ui.R using
tags$head().
UI elements get nested inside one another and must all be placed
inside our UI object’s outermost container (here, a
fluidPage()).
Most UI elements can be given id and class
attributes use in CSS selectors. UI elements must be separated from one
another in the UI with commas.
fluidRow() and column() can be used to
create a “grid,” within which elements may arrange next to each other on
wide screens but vertically on narrow screens, creating a responsive,
mobile-first design with little fuss.
CSS styling requires using the right selector to target the right
element(s). If you aren’t sure of the right selector to use, you can
retrieve it using your browser’s developer tools.
Complex UI elements, like tables, first need to be
rendered server-side using a render*({})
function and then placed within our UI using an *Output()
function.
Rendered entities are passed from the server to the UI via the
output object using the outputIds we provided
them when we rendered them.
Input widgets are UI elements that allow users to
interact with our app in pre-defined and familiar ways. The current
values of these widgets are passed from the UI to the server via the
input object using the inputIds we provided
them when we created them.
R knows to watch reactive objects (like those
attached to input) for changes. Any such changes are
events. Event handling is coding how
the app should respond to an event.
The primary way Shiny handles events is by re-running any
reactive contexts containing the reactive object(s)
that just changed (unless those objects have been
isolate()d, in which case they can’t trigger events but
they can be used in operations).
Server-side, reactive contexts are triggered to run
directively (when a precipitating event occurs), not
imperatively (when a coder hits “run”). They might run
in any order, many times, or never—it all depends on what the user
does.
However, once a reactive context begins executing, its
contents are executed imperatively, like “normal,” until they complete
(even if that takes a long time!), during which time the app will be
unresponsive.
Observers (like observeEvent({},{}))
allow for more precise event-handling; observeEvent({},{})s
only rerun their second expression and only in response to events that
occur in their first expression, so they enable precise “If X, then Y”
event handling.
tabPanel() and tabsetPanel() create a
“tabular layout,” dividing one UI space into several, only one of which
is visible at a time.
The DT, leaflet, and plotly
packages can be used to produce web-enabled, interactive tables, maps,
and graphs for incorporation into Shiny apps.
Each of the graphics these packages create come with tons of
interactive features. Because some of this functionality might be
confusing or unnecessary for some users or contexts, it’s valuable to
know how to adjust or disable them. It’s better to have only as many
features as are needed and that your users can handle.
The aesthetics of these graphics can be adjusted or customized, but
the specifics of how to do this (and how easy or intuitive it will be)
varies widely.
We should strive to update graphics, changing only the aspects that
need changing, rather than remaking graphics from scratch. This
generally means handling events using observers and proxies rather than
using render*({}) functions.
Like with other widgets, user interactions with these graphics can
be tracked by the UI and passed to the server for handling. This passing
happens with the input object for DT and
leaflet, but an equivalent system must be used for
plotly.