├── .Rbuildignore
├── .github
├── .gitignore
└── workflows
│ └── R-CMD-check.yaml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── DESCRIPTION
├── LICENSE
├── LICENSE.md
├── NAMESPACE
├── NEWS.md
├── R
├── funs.R
└── logs.R
├── README.Rmd
├── README.md
├── cran-comments.md
├── gargoyle.Rproj
├── inst
└── example
│ └── examples.R
├── man
├── Event.Rd
├── logs.Rd
└── on.Rd
├── tests
├── testthat.R
└── testthat
│ ├── test-funs.R
│ └── test-logs.R
└── vignettes
├── .gitignore
└── gargoyle.Rmd
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^gargoyle\.Rproj$
2 | ^\.Rproj\.user$
3 | ^LICENSE\.md$
4 | ^README\.Rmd$
5 | ^CODE_OF_CONDUCT\.md$
6 | ^\.github$
7 | ^cran-comments\.md$
8 | ^README\.html$
9 |
--------------------------------------------------------------------------------
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: R-CMD-check
10 |
11 | jobs:
12 | R-CMD-check:
13 | runs-on: ${{ matrix.config.os }}
14 |
15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | config:
21 | - {os: macos-latest, r: 'release'}
22 | - {os: windows-latest, r: 'release'}
23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
24 | - {os: ubuntu-latest, r: 'release'}
25 | - {os: ubuntu-latest, r: 'oldrel-1'}
26 |
27 | env:
28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
29 | R_KEEP_PKG_SOURCE: yes
30 |
31 | steps:
32 | - uses: actions/checkout@v3
33 |
34 | - uses: r-lib/actions/setup-pandoc@v2
35 |
36 | - uses: r-lib/actions/setup-r@v2
37 | with:
38 | r-version: ${{ matrix.config.r }}
39 | http-user-agent: ${{ matrix.config.http-user-agent }}
40 | use-public-rspm: true
41 |
42 | - uses: r-lib/actions/setup-r-dependencies@v2
43 | with:
44 | extra-packages: any::rcmdcheck
45 | needs: check
46 |
47 | - uses: r-lib/actions/check-r-package@v2
48 | with:
49 | upload-snapshots: true
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | inst/doc
3 | .Rhistory
4 | .Rdata
5 | .httr-oauth
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect all people who
4 | contribute through reporting issues, posting feature requests, updating documentation,
5 | submitting pull requests or patches, and other activities.
6 |
7 | We are committed to making participation in this project a harassment-free experience for
8 | everyone, regardless of level of experience, gender, gender identity and expression,
9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
10 |
11 | Examples of unacceptable behavior by participants include the use of sexual language or
12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment,
13 | insults, or other unprofessional conduct.
14 |
15 | Project maintainers have the right and responsibility to remove, edit, or reject comments,
16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this
17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed
18 | from the project team.
19 |
20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
21 | opening an issue or contacting one or more of the project maintainers.
22 |
23 | This Code of Conduct is adapted from the Contributor Covenant
24 | (https://www.contributor-covenant.org), version 1.0.0, available at
25 | https://contributor-covenant.org/version/1/0/0/.
26 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: gargoyle
2 | Title: An Event-Based Mechanism for 'Shiny'
3 | Version: 0.0.1
4 | Authors@R: person("Colin", "Fay", email = "contact@colinfay.me", role = c("aut", "cre"))
5 | Description: An event-Based framework for building 'Shiny' apps.
6 | Instead of relying on standard 'Shiny' reactive objects, this
7 | package allow to relying on a lighter set of triggers, so that
8 | reactive contexts can be invalidated with more control.
9 | License: MIT + file LICENSE
10 | URL: https://github.com/ColinFay/gargoyle
11 | BugReports: https://github.com/ColinFay/gargoyle/issues
12 | Encoding: UTF-8
13 | LazyData: true
14 | RoxygenNote: 7.2.3
15 | Imports:
16 | shiny,
17 | attempt
18 | Suggests:
19 | knitr,
20 | rmarkdown,
21 | testthat (>= 3.0.0)
22 | VignetteBuilder: knitr
23 | Config/testthat/edition: 3
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2019
2 | COPYRIGHT HOLDER: Colin Fay
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2019 Colin Fay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(clear_gargoyle_logs)
4 | export(get_gargoyle_logs)
5 | export(init)
6 | export(on)
7 | export(trigger)
8 | export(watch)
9 | importFrom(attempt,stop_if)
10 | importFrom(shiny,getDefaultReactiveDomain)
11 | importFrom(shiny,observeEvent)
12 | importFrom(shiny,reactiveVal)
13 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # gargoyle 0.0.0.9000
2 |
3 | * Added a `NEWS.md` file to track changes to the package.
4 |
--------------------------------------------------------------------------------
/R/funs.R:
--------------------------------------------------------------------------------
1 | #' Initiate, trigger, event
2 | #'
3 | #' @param name,... The name(s) of the events
4 | #' @param session The shiny session object
5 | #'
6 | #' @rdname Event
7 | #'
8 | #' @return The `session` object invisibly.
9 | #' These functions are mainly used for side-effects.
10 | #'
11 | #' @importFrom shiny reactiveVal
12 | #' @export
13 | #' @examples
14 | #' if (interactive()){
15 | #' library(shiny)
16 | #' library(gargoyle)
17 | #' options("gargoyle.talkative" = TRUE)
18 | #' ui <- function(request){
19 | #' tagList(
20 | #' h4('Go'),
21 | #' actionButton("y", "y"),
22 | #' h4('Output of z$v'),
23 | #' tableOutput("evt")
24 | #' )
25 | #' }
26 | #'
27 | #' server <- function(input, output, session){
28 | #'
29 | #' # Initiating the flags
30 | #' init("airquality", "iris", "renderiris")
31 | #'
32 | #' # Creating a new env to store values, instead of
33 | #' # a reactive structure
34 | #' z <- new.env()
35 | #'
36 | #' observeEvent( input$y , {
37 | #' z$v <- mtcars
38 | #' # Triggering the flag
39 | #' trigger("airquality")
40 | #' })
41 | #'
42 | #' on("airquality", {
43 | #' # Triggering the flag
44 | #' z$v <- airquality
45 | #' trigger("iris")
46 | #' })
47 | #'
48 | #' on("iris", {
49 | #' # Triggering the flag
50 | #' z$v <- iris
51 | #' trigger("renderiris")
52 | #' })
53 | #'
54 | #' output$evt <- renderTable({
55 | #' # This part will only render when the renderiris
56 | #' # flag is triggered
57 | #' watch("renderiris")
58 | #' head(z$v)
59 | #' })
60 | #'
61 | #' }
62 | #'
63 | #' shinyApp(ui, server)
64 | #'
65 | #' }
66 | init <- function(..., session = getDefaultReactiveDomain()){
67 | lapply(
68 | list(...),
69 | function(x){
70 | session$userData[[x]] <- reactiveVal(0)
71 | }
72 | )
73 |
74 | }
75 |
76 | #' @rdname Event
77 | #' @export
78 | trigger <- function(..., session = getDefaultReactiveDomain()){
79 | .logs$log <- rbind(
80 | .logs$log,
81 | data.frame(
82 | what = c(...),
83 | time = as.character(Sys.time()),
84 | stringsAsFactors = FALSE
85 | )
86 | )
87 | lapply(
88 | list(...),
89 | function(x){
90 | if (getOption("gargoyle.talkative", FALSE)){
91 | cat(
92 | "- [Gargoyle] Triggering",
93 | x,
94 | "\n"
95 | )
96 | }
97 | session$userData[[x]](
98 | session$userData[[x]]() + 1
99 | )
100 | }
101 | )
102 |
103 | }
104 | #' @rdname Event
105 | #' @export
106 | watch <- function(name, session = getDefaultReactiveDomain()){
107 | session$userData[[name]]()
108 | }
109 |
110 |
111 | #' React on an event
112 | #'
113 | #' @param name the name of the event to react to
114 | #' @param expr the expression to run when the event
115 | #' is triggered.
116 | #' @param session The shiny session object
117 | #'
118 | #' @return An observeEvent object. This object will
119 | #' rarely be used, `on` is mainly called for side-effects.
120 | #'
121 | #' @export
122 | #' @importFrom shiny observeEvent getDefaultReactiveDomain
123 | #' @importFrom attempt stop_if
124 | on <- function(
125 | name,
126 | expr,
127 | session = getDefaultReactiveDomain()
128 | ){
129 |
130 | stop_if(
131 | session$userData[[name]],
132 | is.null,
133 | sprintf(
134 | "[Gargoyle] Flag %s hasn't been initiated: can't listen to it.",
135 | name
136 | )
137 | )
138 | observeEvent(
139 | substitute(gargoyle::watch(name, session = session)),
140 | {
141 | substitute(expr)
142 | },
143 | event.quoted = TRUE,
144 | handler.quoted = TRUE,
145 | ignoreInit = TRUE,
146 | event.env = parent.frame(),
147 | handler.env = parent.frame()
148 | )
149 | }
150 |
--------------------------------------------------------------------------------
/R/logs.R:
--------------------------------------------------------------------------------
1 | .logs <- new.env()
2 | .logs$log <- data.frame(
3 | what = character(0),
4 | time = character(0),
5 | stringsAsFactors = FALSE
6 | )
7 |
8 | #' Handle logs
9 | #'
10 | #' Get / Clear the logs of all the time the `trigger()` functions are launched.
11 | #'
12 | #' @return A data.frame of the logs.
13 | #' @export
14 | #' @rdname logs
15 | #' @examples
16 | #' if (interactive()){
17 | #' get_gargoyle_logs()
18 | #' clear_gargoyle_logs()
19 | #' }
20 | get_gargoyle_logs <- function(){
21 | return(.logs$log)
22 | }
23 | #' @export
24 | #' @rdname logs
25 | clear_gargoyle_logs <- function(){
26 | .logs$log <- data.frame(
27 | what = character(0),
28 | time = character(0),
29 | stringsAsFactors = FALSE
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | ```{r, include = FALSE}
8 | knitr::opts_chunk$set(
9 | collapse = TRUE,
10 | comment = "#>",
11 | fig.path = "man/figures/README-",
12 | out.width = "100%"
13 | )
14 | ```
15 | # gargoyle
16 |
17 |
18 | [](https://lifecycle.r-lib.org/articles/stages.html#stable)
19 | [](https://github.com/ColinFay/gargoyle/actions/workflows/R-CMD-check.yaml)
20 |
21 |
22 | The goal of gargoyle is to provide an event-based mechanism for `{shiny}`.
23 |
24 | ## Installation
25 |
26 | You can install the dev version of `{gargoyle}` with:
27 |
28 | ``` r
29 | remotes::install_github("ColinFay/gargoyle")
30 | ```
31 |
32 | ## About
33 |
34 | You're reading the doc about version : `r pkgload::pkg_version()`
35 |
36 | This README has been compiled on the
37 |
38 | ```{r}
39 | Sys.time()
40 | ```
41 |
42 | Here are the test & coverage results :
43 |
44 | ```{r}
45 | devtools::check(quiet = TRUE)
46 | ```
47 |
48 | ```{r echo = FALSE}
49 | unloadNamespace("shinipsum")
50 | ```
51 |
52 | ```{r}
53 | covr::package_coverage()
54 | ```
55 |
56 | ### What the heck?
57 |
58 | `{gargoyle}` is a package that provides wrappers around `{shiny}` to turn your app into and event-based application instead of a full reactive app.
59 | The framework is centered around a `watch` & `trigger` mechanism.
60 |
61 | It works with classical UI, and just needs tweaking the server side of your app.
62 |
63 | `{shiny}`'s default reactive behavior is very helpful when it comes to building small applications.
64 | Because, you know, the good thing about reactivity is that when something moves somewhere, it's updated everywhere.
65 | But the bad thing about reactivity is that when something moves somewhere, it's updated everywhere.
66 | So it does work pretty well on small apps, but can get very complicated on bigger apps, and can quickly get out of hands.
67 |
68 | That's where `{gargoyle}` comes into play: it provides an event based paradigm for building your apps, so that reactivity happens under a controlled flow.
69 |
70 | ### For whom?
71 |
72 | If you're just building small `{shiny}` apps you're probably good with `{shiny}` default reactive behavior.
73 | But if ever you've struggled with reactivity in larger apps you might find `{gargoyle}` useful: only invalidate contexts when you want and make the general flow of your app more predictable!
74 |
75 | ### The trade-off
76 |
77 | `{gargoyle}` will be more verbose and will demand more work upfront to make things happen.
78 | I believe this is for the best if you're working on a big project: from the very beginning
79 | it forces the user to think more carefully about app design and the reactive flow.
80 |
81 | ## Design pattern
82 |
83 | `{gargoyle}` has:
84 |
85 | + `init`, `watch` & `trigger`, which allow to initiate, watch, and trigger an event
86 |
87 | + `on`, that runs the `expr` when the event is triggered
88 |
89 | `gargoyle::trigger()` can print messages to the console using `options("gargoyle.talkative" = TRUE)`.
90 |
91 | ## Example
92 |
93 | ```{r eval = FALSE}
94 | library(shiny)
95 | library(gargoyle)
96 | options("gargoyle.talkative" = TRUE)
97 | ui <- function(request){
98 | tagList(
99 | h4('Go'),
100 | actionButton("y", "y"),
101 | h4('Output of z$v'),
102 | tableOutput("evt")
103 | )
104 | }
105 |
106 | server <- function(input, output, session){
107 |
108 | # Initiating the flags
109 | init("airquality", "iris", "renderiris")
110 |
111 | # Creating a new env to store values, instead of
112 | # a reactive structure
113 | z <- new.env()
114 |
115 | observeEvent(input$y , {
116 | z$v <- mtcars
117 | # Triggering the flag
118 | trigger("airquality")
119 | })
120 |
121 | on("airquality", {
122 | # Triggering the flag
123 | z$v <- airquality
124 | trigger("iris")
125 | })
126 |
127 | on("iris", {
128 | # Triggering the flag
129 | z$v <- iris
130 | trigger("renderiris")
131 | })
132 |
133 | output$evt <- renderTable({
134 | # This part will only render when the renderiris
135 | # flag is triggered
136 | watch("renderiris")
137 | head(z$v)
138 | })
139 |
140 | }
141 |
142 | shinyApp(ui, server)
143 | ```
144 |
145 | You can then get & clear the logs of the times the triggers were called:
146 |
147 | ```{r eval = FALSE}
148 | get_gargoyle_logs()
149 | clear_gargoyle_logs()
150 | ```
151 |
152 |
153 |
154 |
155 | ## Code of Conduct
156 |
157 | Please note that the gargoyle project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). By contributing to this project, you agree to abide by its terms.
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # gargoyle
5 |
6 |
7 |
8 | [](https://lifecycle.r-lib.org/articles/stages.html#stable)
10 | [](https://github.com/ColinFay/gargoyle/actions/workflows/R-CMD-check.yaml)
11 |
12 |
13 | The goal of gargoyle is to provide an event-based mechanism for
14 | `{shiny}`.
15 |
16 | ## Installation
17 |
18 | You can install the dev version of `{gargoyle}` with:
19 |
20 | ``` r
21 | remotes::install_github("ColinFay/gargoyle")
22 | ```
23 |
24 | ## About
25 |
26 | You’re reading the doc about version : 0.0.1
27 |
28 | This README has been compiled on the
29 |
30 | ``` r
31 | Sys.time()
32 | #> [1] "2023-03-27 16:53:20 CEST"
33 | ```
34 |
35 | Here are the test & coverage results :
36 |
37 | ``` r
38 | devtools::check(quiet = TRUE)
39 | #> ℹ Loading gargoyle
40 | #> ── R CMD check results ───────────────────────────────────── gargoyle 0.0.1 ────
41 | #> Duration: 10.3s
42 | #>
43 | #> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔
44 | ```
45 |
46 | ``` r
47 | covr::package_coverage()
48 | #> gargoyle Coverage: 56.36%
49 | #> R/funs.R: 51.02%
50 | #> R/logs.R: 100.00%
51 | ```
52 |
53 | ### What the heck?
54 |
55 | `{gargoyle}` is a package that provides wrappers around `{shiny}` to
56 | turn your app into and event-based application instead of a full
57 | reactive app. The framework is centered around a `listen` & `trigger`
58 | mechanism.
59 |
60 | It works with classical UI, and just needs tweaking the server side of
61 | your app.
62 |
63 | `{shiny}`’s default reactive behavior is very helpful when it comes to
64 | building small applications. Because, you know, the good thing about
65 | reactivity is that when something moves somewhere, it’s updated
66 | everywhere. But the bad thing about reactivity is that when something
67 | moves somewhere, it’s updated everywhere. So it does work pretty well on
68 | small apps, but can get very complicated on bigger apps, and can quickly
69 | get out of hands.
70 |
71 | That’s where `{gargoyle}` comes into play: it provides an event based
72 | paradigm for building your apps, so that things happen under a control
73 | flow.
74 |
75 | ### For whom?
76 |
77 | If you’re just building small `{shiny}` apps, you’re probably good with
78 | `{shiny}` default reactive behavior. But if ever you’ve struggled with
79 | reactivity on more bigger apps, you might find `{gargoyle}` useful.
80 |
81 | ### The trade-off
82 |
83 | `{gargoyle}` will be more verbose and will demand more work upfront to
84 | make things happen. I believe this is for the best if you’re working on
85 | a big project.
86 |
87 | ## Design pattern
88 |
89 | `{gargoyle}` has:
90 |
91 | - `init`, `listen` & `trigger`, which allow to initiate, listen on, and
92 | trigger an event
93 |
94 | - `on`, that runs the `expr` when the event in triggered
95 |
96 | `gargoyle::trigger()` can print messages to the console using
97 | `options("gargoyle.talkative" = TRUE)`.
98 |
99 | ## Example
100 |
101 | ``` r
102 | library(shiny)
103 | library(gargoyle)
104 | options("gargoyle.talkative" = TRUE)
105 | ui <- function(request){
106 | tagList(
107 | h4('Go'),
108 | actionButton("y", "y"),
109 | h4('Output of z$v'),
110 | tableOutput("evt")
111 | )
112 | }
113 |
114 | server <- function(input, output, session){
115 |
116 | # Initiating the flags
117 | init("airquality", "iris", "renderiris")
118 |
119 | # Creating a new env to store values, instead of
120 | # a reactive structure
121 | z <- new.env()
122 |
123 | observeEvent( input$y , {
124 | z$v <- mtcars
125 | # Triggering the flag
126 | trigger("airquality")
127 | })
128 |
129 | on("airquality", {
130 | # Triggering the flag
131 | z$v <- airquality
132 | trigger("iris")
133 | })
134 |
135 | on("iris", {
136 | # Triggering the flag
137 | z$v <- iris
138 | trigger("renderiris")
139 | })
140 |
141 | output$evt <- renderTable({
142 | # This part will only render when the renderiris
143 | # flag is triggered
144 | watch("renderiris")
145 | head(z$v)
146 | })
147 |
148 | }
149 |
150 | shinyApp(ui, server)
151 | ```
152 |
153 | You can then get & clear the logs of the times the triggers were called:
154 |
155 | ``` r
156 | get_gargoyle_logs()
157 | clear_gargoyle_logs()
158 | ```
159 |
160 |
161 |
162 | ## Code of Conduct
163 |
164 | Please note that the gargoyle project is released with a [Contributor
165 | Code of
166 | Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/).
167 | By contributing to this project, you agree to abide by its terms.
168 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## Test environments
2 | * local R installation, R 3.6.1
3 | * ubuntu 16.04 (on travis-ci), R 3.6.1
4 | * win-builder (devel)
5 |
6 | ## R CMD check results
7 |
8 | 0 errors | 0 warnings | 1 note
9 |
10 | This is a resubmission after CRAN feedback
11 |
12 | > Please add \value to .Rd files regarding exported methods and explain
13 | > the functions results in the documentation. Please write about the
14 | > structure of the output (class) and also what the output means. (If a
15 | > function does not return a value, please document that too, e.g.
16 | > \value{No return value, called for side effects} or similar)
17 | > Missing Rd-tags:
18 | > Event.Rd: \value
19 | > on.Rd: \value
20 |
21 |
22 | => Added `\value`
23 |
24 | => Checked with `urlchecker::url_check()`
25 |
--------------------------------------------------------------------------------
/gargoyle.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: No
4 | SaveWorkspace: No
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source
21 | PackageRoxygenize: rd,collate,namespace
22 |
--------------------------------------------------------------------------------
/inst/example/examples.R:
--------------------------------------------------------------------------------
1 | ui <- function(request){
2 | tagList(
3 | h4('Go'),
4 | actionButton("y", "y"),
5 | h4('Output of z$v'),
6 | tableOutput("evt")
7 | )
8 | }
9 |
10 | server <- function(input, output, session){
11 |
12 | # Initiating the flags
13 | init("airquality", "iris", "renderiris")
14 |
15 | # Creating a new env to store values, instead of
16 | # a reactive structure
17 | z <- new.env()
18 |
19 | observeEvent( input$y , {
20 | z$v <- mtcars
21 | # Triggering the flag
22 | trigger("airquality")
23 | })
24 |
25 | on("airquality", {
26 | # Triggering the flag
27 | z$v <- airquality
28 | trigger("iris")
29 | })
30 |
31 | on("iris", {
32 | # Triggering the flag
33 | z$v <- iris
34 | trigger("renderiris")
35 | })
36 |
37 | output$evt <- renderTable({
38 | # This part will only render when the renderiris
39 | # flag is triggered
40 | watch("renderiris")
41 | head(z$v)
42 | })
43 |
44 | }
45 |
46 | shinyApp(ui, server)
47 |
--------------------------------------------------------------------------------
/man/Event.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/funs.R
3 | \name{init}
4 | \alias{init}
5 | \alias{trigger}
6 | \alias{watch}
7 | \title{Initiate, trigger, event}
8 | \usage{
9 | init(..., session = getDefaultReactiveDomain())
10 |
11 | trigger(..., session = getDefaultReactiveDomain())
12 |
13 | watch(name, session = getDefaultReactiveDomain())
14 | }
15 | \arguments{
16 | \item{session}{The shiny session object}
17 |
18 | \item{name, ...}{The name(s) of the events}
19 | }
20 | \value{
21 | The `session` object invisibly.
22 | These functions are mainly used for side-effects.
23 | }
24 | \description{
25 | Initiate, trigger, event
26 | }
27 | \examples{
28 | if (interactive()){
29 | library(shiny)
30 | library(gargoyle)
31 | options("gargoyle.talkative" = TRUE)
32 | ui <- function(request){
33 | tagList(
34 | h4('Go'),
35 | actionButton("y", "y"),
36 | h4('Output of z$v'),
37 | tableOutput("evt")
38 | )
39 | }
40 |
41 | server <- function(input, output, session){
42 |
43 | # Initiating the flags
44 | init("airquality", "iris", "renderiris")
45 |
46 | # Creating a new env to store values, instead of
47 | # a reactive structure
48 | z <- new.env()
49 |
50 | observeEvent( input$y , {
51 | z$v <- mtcars
52 | # Triggering the flag
53 | trigger("airquality")
54 | })
55 |
56 | on("airquality", {
57 | # Triggering the flag
58 | z$v <- airquality
59 | trigger("iris")
60 | })
61 |
62 | on("iris", {
63 | # Triggering the flag
64 | z$v <- iris
65 | trigger("renderiris")
66 | })
67 |
68 | output$evt <- renderTable({
69 | # This part will only render when the renderiris
70 | # flag is triggered
71 | watch("renderiris")
72 | head(z$v)
73 | })
74 |
75 | }
76 |
77 | shinyApp(ui, server)
78 |
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/man/logs.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/logs.R
3 | \name{get_gargoyle_logs}
4 | \alias{get_gargoyle_logs}
5 | \alias{clear_gargoyle_logs}
6 | \title{Handle logs}
7 | \usage{
8 | get_gargoyle_logs()
9 |
10 | clear_gargoyle_logs()
11 | }
12 | \value{
13 | A data.frame of the logs.
14 | }
15 | \description{
16 | Get / Clear the logs of all the time the `trigger()` functions are launched.
17 | }
18 | \examples{
19 | if (interactive()){
20 | get_gargoyle_logs()
21 | clear_gargoyle_logs()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/man/on.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/funs.R
3 | \name{on}
4 | \alias{on}
5 | \title{React on an event}
6 | \usage{
7 | on(name, expr, session = getDefaultReactiveDomain())
8 | }
9 | \arguments{
10 | \item{name}{the name of the event to react to}
11 |
12 | \item{expr}{the expression to run when the event
13 | is triggered.}
14 |
15 | \item{session}{The shiny session object}
16 | }
17 | \value{
18 | An observeEvent object. This object will
19 | rarely be used, `on` is mainly called for side-effects.
20 | }
21 | \description{
22 | React on an event
23 | }
24 |
--------------------------------------------------------------------------------
/tests/testthat.R:
--------------------------------------------------------------------------------
1 | library(testthat)
2 | library(gargoyle)
3 |
4 | test_check("gargoyle")
5 |
--------------------------------------------------------------------------------
/tests/testthat/test-funs.R:
--------------------------------------------------------------------------------
1 | test_that("gargoyle init(), trigger() and watch() work", {
2 | shiny::reactiveConsole(TRUE)
3 |
4 | s <- shiny::MockShinySession$new()
5 |
6 | init("pif", session = s)
7 |
8 | expect_true(
9 | s$userData$pif() == 0
10 | )
11 |
12 | # I. Run test for the talkative feature OFF; default option is FALSE
13 | trigger("pif", session = s)
14 | expect_true(
15 | s$userData$pif() == 1
16 | )
17 | expect_equal(
18 | watch(
19 | "pif",
20 | s
21 | ),
22 | 1
23 | )
24 |
25 | # II. Run test for the talkative feature ON; set option to TRUE
26 | options("gargoyle.talkative" = TRUE)
27 | expect_output(
28 | trigger("pif", session = s),
29 | regexp = "\\[Gargoyle\\] Triggering pif "
30 | )
31 | expect_true(
32 | s$userData$pif() == 2
33 | )
34 | expect_equal(
35 | watch(
36 | "pif",
37 | s
38 | ),
39 | 2
40 | )
41 | options("gargoyle.talkative" = FALSE)
42 | shiny::reactiveConsole(FALSE)
43 | })
44 | test_that("gargoyle on() works", {
45 | shiny::reactiveConsole(TRUE)
46 |
47 | s <- shiny::MockShinySession$new()
48 |
49 | init("pif", session = s)
50 |
51 | # I. Run tests for on() without error
52 | expect_identical(
53 | class(
54 | on(
55 | "pif",
56 | {
57 | cat(1 + 1)
58 | },
59 | session = s
60 | )
61 | ),
62 | c("Observer.event", "Observer", "R6")
63 | )
64 | # II. Run tests for on() with errors - uninitialized events
65 | # II.A Provoke error due to uninitialized event - missmatch from init
66 | expect_error(
67 | on(
68 | "pif2",
69 | {
70 | cat(1 + 1)
71 | },
72 | session = s
73 | )
74 | )
75 | # II.B Check error message of uninitialized event - missmatch from init
76 | out_error <- try(on(
77 | "pif2",
78 | {
79 | cat(1 + 1)
80 | },
81 | session = s
82 | ))
83 | expect_equal(
84 | out_error[[1]],
85 | "Error : [Gargoyle] Flag pif2 hasn't been initiated: can't listen to it.\n"
86 | )
87 |
88 | # II.C Provoke error due to uninitialized event - missmatch from session arg
89 | expect_error(
90 | on(
91 | "pif",
92 | {
93 | cat(1 + 1)
94 | },
95 | session = shiny::getDefaultReactiveDomain()
96 | )
97 | )
98 | # II.D Check error message of uninitialized event - missmatch session arg
99 | out_error <- try(on(
100 | "pif",
101 | {
102 | cat(1 + 1)
103 | },
104 | session = shiny::getDefaultReactiveDomain()
105 | ))
106 | expect_equal(
107 | out_error[[1]],
108 | "Error : [Gargoyle] Flag pif hasn't been initiated: can't listen to it.\n"
109 | )
110 |
111 | shiny::reactiveConsole(FALSE)
112 | })
113 |
--------------------------------------------------------------------------------
/tests/testthat/test-logs.R:
--------------------------------------------------------------------------------
1 | test_that("get_gargoyle_logs() works", {
2 | clear_gargoyle_logs()
3 | expect_equal(
4 | nrow(get_gargoyle_logs()),
5 | 0
6 | )
7 | expect_equal(
8 | names(get_gargoyle_logs()),
9 | c("what", "time")
10 | )
11 | .logs$log <- rbind(
12 | .logs$log,
13 | data.frame(
14 | what = c("this"),
15 | time = as.character(Sys.time()),
16 | stringsAsFactors = FALSE
17 | )
18 | )
19 | expect_equal(
20 | nrow(get_gargoyle_logs()),
21 | 1
22 | )
23 | expect_equal(
24 | names(get_gargoyle_logs()),
25 | c("what", "time")
26 | )
27 | })
28 |
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.R
3 |
--------------------------------------------------------------------------------
/vignettes/gargoyle.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "gargoyle"
3 | output: rmarkdown::html_vignette
4 | vignette: >
5 | %\VignetteIndexEntry{gargoyle}
6 | %\VignetteEngine{knitr::rmarkdown}
7 | %\VignetteEncoding{UTF-8}
8 | ---
9 |
10 | ```{r, include = FALSE}
11 | knitr::opts_chunk$set(
12 | collapse = TRUE,
13 | comment = "#>"
14 | )
15 | ```
16 |
17 | ```{r setup}
18 | library(gargoyle)
19 | ```
20 |
21 | ## About
22 |
23 | `{gargoyle}` is a package that provides wrappers around `{shiny}` to turn your app into and event-based application instead of a full reactive app.
24 | The framework is centered around a `listen` & `trigger` mechanism.
25 |
26 | It works with classical UI, and just needs tweaking the server side of your app.
27 |
28 | ### What the heck?
29 |
30 | `{shiny}`'s default reactive behavior is very helpful when it comes to building small applications.
31 | Because, you know, the good thing about reactivity is that when something moves somewhere, it's updated everywhere.
32 | But the bad thing about reactivity is that when something moves somewhere, it's updated everywhere.
33 | So it does work pretty well on small apps, but can get very complicated on bigger apps, and can quickly get out of hands.
34 |
35 | That's where `{gargoyle}` comes into play: it provides an event based paradigm for building your apps, so that things happen under a control flow.
36 |
37 | ### For whom?
38 |
39 | If you're just building small `{shiny}` apps, you're probably good with `{shiny}` default reactive behavior.
40 | But if ever you've struggled with reactivity on more bigger apps, you might find `{gargoyle}` useful.
41 |
42 | ### The trade-off
43 |
44 | `{gargoyle}` will be more verbose and will demand more work upfront to make things happen.
45 | I believe this is for the best if you're working on a big project.
46 |
47 | ## Design pattern
48 |
49 | `{gargoyle}` has:
50 |
51 | + `init`, `listen` & `trigger`, which allow to initiate, listen on, and trigger an event
52 |
53 | + `on`, that runs the `expr` when the event in triggered
54 |
55 | `gargoyle::trigger()` can print messages to the console using `options("gargoyle.talkative" = TRUE)`.
56 |
57 | ## Example
58 |
59 | ```{r eval = FALSE}
60 | library(shiny)
61 | library(gargoyle)
62 | options("gargoyle.talkative" = TRUE)
63 | ui <- function(request){
64 | tagList(
65 | h4('Go'),
66 | actionButton("y", "y"),
67 | h4('Output of z$v'),
68 | tableOutput("evt")
69 | )
70 | }
71 |
72 | server <- function(input, output, session){
73 |
74 | # Initiating the flags
75 | init("airquality", "iris", "renderiris")
76 |
77 | # Creating a new env to store values, instead of
78 | # a reactive structure
79 | z <- new.env()
80 |
81 | observeEvent( input$y , {
82 | z$v <- mtcars
83 | # Triggering the flag
84 | trigger("airquality")
85 | })
86 |
87 | on("airquality", {
88 | # Triggering the flag
89 | z$v <- airquality
90 | trigger("iris")
91 | })
92 |
93 | on("iris", {
94 | # Triggering the flag
95 | z$v <- iris
96 | trigger("renderiris")
97 | })
98 |
99 | output$evt <- renderTable({
100 | # This part will only render when the renderiris
101 | # flag is triggered
102 | watch("renderiris")
103 | head(z$v)
104 | })
105 |
106 | }
107 |
108 | shinyApp(ui, server)
109 | ```
110 |
111 | You can then get & clear the logs of the times the triggers were called:
112 |
113 | ```{r eval = FALSE}
114 | get_gargoyle_logs()
115 | clear_gargoyle_logs()
116 | ```
117 |
118 |
119 |
120 |
121 | ## Code of Conduct
122 |
123 | Please note that the gargoyle project is released with a [Contributor Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). By contributing to this project, you agree to abide by its terms.
124 |
--------------------------------------------------------------------------------