├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── pkgdown.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── ModStore.R ├── Store.R ├── TidyModule.R ├── UtilityModule.R ├── add_module.R ├── examples.R ├── pipes.R ├── snippets.R ├── tidymodules.R ├── utility.R └── verbs.R ├── README.Rmd ├── README.md ├── inst ├── .DS_Store ├── rstudio │ └── r.snippets └── shiny │ └── examples │ ├── 1_simple_addition │ ├── Addition.R │ ├── AdditionSM.R │ ├── DESCRIPTION │ └── app.R │ ├── 2_linked_scatter │ ├── DESCRIPTION │ ├── app.R │ └── linked_scatter.R │ ├── 3_nested_modules │ ├── ColorPicker.R │ ├── DESCRIPTION │ ├── Kmeans.R │ └── app.R │ ├── 4_communication │ ├── DESCRIPTION │ ├── app.R │ ├── mod_BasePlot.R │ ├── mod_BoxPlot.R │ ├── mod_ClosablePanel.R │ ├── mod_ColSelector.R │ ├── mod_DataFilter.R │ ├── mod_DatasetSelector.R │ ├── mod_LinePlot.R │ ├── mod_Panel.R │ ├── mod_PlotGenerator.R │ ├── mod_Scatter3DPlot.R │ ├── mod_ScatterPlot.R │ ├── model │ │ ├── ERD.graphml │ │ ├── ERD.svg │ │ ├── ports.graphml │ │ └── ports.svg │ ├── module │ │ ├── BasePlot.R │ │ ├── BoxPlot.R │ │ ├── ClosablePanel.R │ │ ├── ColSelector.R │ │ ├── DataFilter.R │ │ ├── DatasetSelector.R │ │ ├── LinePlot.R │ │ ├── Panel.R │ │ ├── PlotGenerator.R │ │ ├── Scatter3DPlot.R │ │ └── ScatterPlot.R │ └── www │ │ ├── ERD.svg │ │ └── ports.svg │ └── 5_counter │ ├── Counter.R │ ├── DESCRIPTION │ ├── app.R │ └── counterSM.R ├── man ├── ModStore.Rd ├── Store.Rd ├── TidyModule.Rd ├── UtilityModule.Rd ├── add_module.Rd ├── add_tm_snippets.Rd ├── callModules.Rd ├── check_and_load.Rd ├── combine_ports.Rd ├── defineEdges.Rd ├── figures │ ├── QR_tidymodules.svg │ └── logo.svg ├── getCacheOption.Rd ├── getMod.Rd ├── getSessionId.Rd ├── get_R6CG_list.Rd ├── global_options.Rd ├── grapes-colon-c-greater-than-colon-grapes.Rd ├── grapes-colon-greater-than-colon-grapes.Rd ├── grapes-colon-greater-than-greater-than-colon-grapes.Rd ├── grapes-colon-i-colon-grapes.Rd ├── grapes-colon-pi-colon-grapes.Rd ├── grapes-greater-than-grapes.Rd ├── grapes-greater-than-greater-than-grapes.Rd ├── grapes-greater-than-greater-than-y-grapes.Rd ├── grapes-greater-than-y-grapes.Rd ├── grapes-x-greater-than-greater-than-y-grapes.Rd ├── grapes-x-greater-than-y-grapes.Rd ├── grapes-x-less-than-less-than-y-grapes.Rd ├── grapes-x-less-than-y-grapes.Rd ├── iport.Rd ├── listModules.Rd ├── make_double_pipe.Rd ├── make_single_pipe.Rd ├── map_ports.Rd ├── mod.Rd ├── multi_port_map.Rd ├── oport.Rd ├── port.Rd ├── port_map.Rd ├── race_ports.Rd ├── session_type.Rd ├── showExamples.Rd └── tidymodules-package.Rd ├── pkgdown ├── _pkgdown.yml └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── testthat.R └── testthat │ └── test_dummy.R ├── tidymodules.Rproj └── vignettes ├── .gitignore ├── communication.Rmd ├── figures ├── ERD.png ├── mod_network.png ├── ports.svg └── ports_intro.png ├── inheritance.Rmd ├── intro.Rmd ├── namespace.Rmd ├── session.Rmd └── tidymodules.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CODE_OF_CONDUCT\.md$ 2 | ^README\.Rmd$ 3 | ^LICENSE\.md$ 4 | ^.*\.Rproj$ 5 | ^\.Rproj\.user$ 6 | ^data-raw$ 7 | dev_history.R 8 | ^dev$ 9 | $run_dev.* 10 | .travis.yml 11 | ^_pkgdown\.yml$ 12 | ^pkgdown$ 13 | ^docs$ 14 | ^docs$ 15 | ^\.github$ 16 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.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 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.4.1 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | .Rproj.user 3 | .Rhistory 4 | .RData 5 | .Ruserdata 6 | .Rprofile 7 | renv 8 | -------------------------------------------------------------------------------- /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 | (http://contributor-covenant.org), version 1.0.0, available at 25 | http://contributor-covenant.org/version/1/0/0/ 26 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: tidymodules 2 | Title: A robust framework for developing shiny modules 3 | Version: 0.1.6 4 | Authors@R: c( 5 | person('Mustapha', 'Larbaoui', email = 'mustapha.larbaoui@novartis.com', role = c('cre', 'aut')), 6 | person('Douglas', 'Robinson', email = 'douglas.robinson@novartis.com', role = c('ctb')), 7 | person('Xiao', 'Ni', email = 'xiao.ni@novartis.com', role = c('ctb')), 8 | person('David', 'Granjon', email = 'david.granjon@novartis.com', role = c('ctb')), 9 | person('Stephen', 'Eng', email = 'stefaneng13@gmail.com', role = c('ctb')), 10 | person('Marzieh', 'Eslami Rasekh', email = 'marzie.rasekh@gmail.com', role = c('ctb')), 11 | person('Renan', 'Sauteraud', email = 'rxs575@psu.edu', role = c('ctb'))) 12 | Description: tidymodules offers a robust framework for developing shiny modules based on R6 classes which should facilitates inter-modules communication. 13 | License: Apache License (>= 2.0) 14 | Encoding: UTF-8 15 | LazyData: true 16 | Imports: 17 | miniUI, 18 | stringi, 19 | digest, 20 | R6, 21 | methods, 22 | snippr, 23 | cli, 24 | dplyr, 25 | fs, 26 | purrr, 27 | visNetwork 28 | Depends: 29 | shiny 30 | RoxygenNote: 7.2.1 31 | Roxygen: list(markdown = TRUE) 32 | URL: https://github.com/Novartis/tidymodules, 33 | http://opensource.nibr.com/tidymodules/ 34 | BugReports: https://github.com/Novartis/tidymodules/issues 35 | Suggests: 36 | rstudioapi, 37 | testthat, 38 | knitr, 39 | rmarkdown, 40 | ggplot2, 41 | shinycssloaders, 42 | RColorBrewer, 43 | shinyWidgets, 44 | plotly 45 | Remotes: 46 | dgrtwo/snippr@29c1813 47 | VignetteBuilder: knitr 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%->%") 4 | export("%->>%") 5 | export("%1<1%") 6 | export("%2<1%") 7 | export("%3<1%") 8 | export("%4<1%") 9 | export("%5<1%") 10 | export("%6<1%") 11 | export("%7<1%") 12 | export("%8<1%") 13 | export("%9<1%") 14 | export("%10<1%") 15 | export("%1<2%") 16 | export("%2<2%") 17 | export("%3<2%") 18 | export("%4<2%") 19 | export("%5<2%") 20 | export("%6<2%") 21 | export("%7<2%") 22 | export("%8<2%") 23 | export("%9<2%") 24 | export("%10<2%") 25 | export("%1<3%") 26 | export("%2<3%") 27 | export("%3<3%") 28 | export("%4<3%") 29 | export("%5<3%") 30 | export("%6<3%") 31 | export("%7<3%") 32 | export("%8<3%") 33 | export("%9<3%") 34 | export("%10<3%") 35 | export("%1<4%") 36 | export("%2<4%") 37 | export("%3<4%") 38 | export("%4<4%") 39 | export("%5<4%") 40 | export("%6<4%") 41 | export("%7<4%") 42 | export("%8<4%") 43 | export("%9<4%") 44 | export("%10<4%") 45 | export("%1<5%") 46 | export("%2<5%") 47 | export("%3<5%") 48 | export("%4<5%") 49 | export("%5<5%") 50 | export("%6<5%") 51 | export("%7<5%") 52 | export("%8<5%") 53 | export("%9<5%") 54 | export("%10<5%") 55 | export("%1<6%") 56 | export("%2<6%") 57 | export("%3<6%") 58 | export("%4<6%") 59 | export("%5<6%") 60 | export("%6<6%") 61 | export("%7<6%") 62 | export("%8<6%") 63 | export("%9<6%") 64 | export("%10<6%") 65 | export("%1<7%") 66 | export("%2<7%") 67 | export("%3<7%") 68 | export("%4<7%") 69 | export("%5<7%") 70 | export("%6<7%") 71 | export("%7<7%") 72 | export("%8<7%") 73 | export("%9<7%") 74 | export("%10<7%") 75 | export("%1<8%") 76 | export("%2<8%") 77 | export("%3<8%") 78 | export("%4<8%") 79 | export("%5<8%") 80 | export("%6<8%") 81 | export("%7<8%") 82 | export("%8<8%") 83 | export("%9<8%") 84 | export("%10<8%") 85 | export("%1<9%") 86 | export("%2<9%") 87 | export("%3<9%") 88 | export("%4<9%") 89 | export("%5<9%") 90 | export("%6<9%") 91 | export("%7<9%") 92 | export("%8<9%") 93 | export("%9<9%") 94 | export("%10<9%") 95 | export("%1<10%") 96 | export("%2<10%") 97 | export("%3<10%") 98 | export("%4<10%") 99 | export("%5<10%") 100 | export("%6<10%") 101 | export("%7<10%") 102 | export("%8<10%") 103 | export("%9<10%") 104 | export("%10<10%") 105 | export("%1<<1%") 106 | export("%2<<1%") 107 | export("%3<<1%") 108 | export("%4<<1%") 109 | export("%5<<1%") 110 | export("%6<<1%") 111 | export("%7<<1%") 112 | export("%8<<1%") 113 | export("%9<<1%") 114 | export("%10<<1%") 115 | export("%1<<2%") 116 | export("%2<<2%") 117 | export("%3<<2%") 118 | export("%4<<2%") 119 | export("%5<<2%") 120 | export("%6<<2%") 121 | export("%7<<2%") 122 | export("%8<<2%") 123 | export("%9<<2%") 124 | export("%10<<2%") 125 | export("%1<<3%") 126 | export("%2<<3%") 127 | export("%3<<3%") 128 | export("%4<<3%") 129 | export("%5<<3%") 130 | export("%6<<3%") 131 | export("%7<<3%") 132 | export("%8<<3%") 133 | export("%9<<3%") 134 | export("%10<<3%") 135 | export("%1<<4%") 136 | export("%2<<4%") 137 | export("%3<<4%") 138 | export("%4<<4%") 139 | export("%5<<4%") 140 | export("%6<<4%") 141 | export("%7<<4%") 142 | export("%8<<4%") 143 | export("%9<<4%") 144 | export("%10<<4%") 145 | export("%1<<5%") 146 | export("%2<<5%") 147 | export("%3<<5%") 148 | export("%4<<5%") 149 | export("%5<<5%") 150 | export("%6<<5%") 151 | export("%7<<5%") 152 | export("%8<<5%") 153 | export("%9<<5%") 154 | export("%10<<5%") 155 | export("%1<<6%") 156 | export("%2<<6%") 157 | export("%3<<6%") 158 | export("%4<<6%") 159 | export("%5<<6%") 160 | export("%6<<6%") 161 | export("%7<<6%") 162 | export("%8<<6%") 163 | export("%9<<6%") 164 | export("%10<<6%") 165 | export("%1<<7%") 166 | export("%2<<7%") 167 | export("%3<<7%") 168 | export("%4<<7%") 169 | export("%5<<7%") 170 | export("%6<<7%") 171 | export("%7<<7%") 172 | export("%8<<7%") 173 | export("%9<<7%") 174 | export("%10<<7%") 175 | export("%1<<8%") 176 | export("%2<<8%") 177 | export("%3<<8%") 178 | export("%4<<8%") 179 | export("%5<<8%") 180 | export("%6<<8%") 181 | export("%7<<8%") 182 | export("%8<<8%") 183 | export("%9<<8%") 184 | export("%10<<8%") 185 | export("%1<<9%") 186 | export("%2<<9%") 187 | export("%3<<9%") 188 | export("%4<<9%") 189 | export("%5<<9%") 190 | export("%6<<9%") 191 | export("%7<<9%") 192 | export("%8<<9%") 193 | export("%9<<9%") 194 | export("%10<<9%") 195 | export("%1<<10%") 196 | export("%2<<10%") 197 | export("%3<<10%") 198 | export("%4<<10%") 199 | export("%5<<10%") 200 | export("%6<<10%") 201 | export("%7<<10%") 202 | export("%8<<10%") 203 | export("%9<<10%") 204 | export("%10<<10%") 205 | export("%1>1%") 206 | export("%2>1%") 207 | export("%3>1%") 208 | export("%4>1%") 209 | export("%5>1%") 210 | export("%6>1%") 211 | export("%7>1%") 212 | export("%8>1%") 213 | export("%9>1%") 214 | export("%10>1%") 215 | export("%1>2%") 216 | export("%2>2%") 217 | export("%3>2%") 218 | export("%4>2%") 219 | export("%5>2%") 220 | export("%6>2%") 221 | export("%7>2%") 222 | export("%8>2%") 223 | export("%9>2%") 224 | export("%10>2%") 225 | export("%1>3%") 226 | export("%2>3%") 227 | export("%3>3%") 228 | export("%4>3%") 229 | export("%5>3%") 230 | export("%6>3%") 231 | export("%7>3%") 232 | export("%8>3%") 233 | export("%9>3%") 234 | export("%10>3%") 235 | export("%1>4%") 236 | export("%2>4%") 237 | export("%3>4%") 238 | export("%4>4%") 239 | export("%5>4%") 240 | export("%6>4%") 241 | export("%7>4%") 242 | export("%8>4%") 243 | export("%9>4%") 244 | export("%10>4%") 245 | export("%1>5%") 246 | export("%2>5%") 247 | export("%3>5%") 248 | export("%4>5%") 249 | export("%5>5%") 250 | export("%6>5%") 251 | export("%7>5%") 252 | export("%8>5%") 253 | export("%9>5%") 254 | export("%10>5%") 255 | export("%1>6%") 256 | export("%2>6%") 257 | export("%3>6%") 258 | export("%4>6%") 259 | export("%5>6%") 260 | export("%6>6%") 261 | export("%7>6%") 262 | export("%8>6%") 263 | export("%9>6%") 264 | export("%10>6%") 265 | export("%1>7%") 266 | export("%2>7%") 267 | export("%3>7%") 268 | export("%4>7%") 269 | export("%5>7%") 270 | export("%6>7%") 271 | export("%7>7%") 272 | export("%8>7%") 273 | export("%9>7%") 274 | export("%10>7%") 275 | export("%1>8%") 276 | export("%2>8%") 277 | export("%3>8%") 278 | export("%4>8%") 279 | export("%5>8%") 280 | export("%6>8%") 281 | export("%7>8%") 282 | export("%8>8%") 283 | export("%9>8%") 284 | export("%10>8%") 285 | export("%1>9%") 286 | export("%2>9%") 287 | export("%3>9%") 288 | export("%4>9%") 289 | export("%5>9%") 290 | export("%6>9%") 291 | export("%7>9%") 292 | export("%8>9%") 293 | export("%9>9%") 294 | export("%10>9%") 295 | export("%1>10%") 296 | export("%2>10%") 297 | export("%3>10%") 298 | export("%4>10%") 299 | export("%5>10%") 300 | export("%6>10%") 301 | export("%7>10%") 302 | export("%8>10%") 303 | export("%9>10%") 304 | export("%10>10%") 305 | export("%1>>1%") 306 | export("%2>>1%") 307 | export("%3>>1%") 308 | export("%4>>1%") 309 | export("%5>>1%") 310 | export("%6>>1%") 311 | export("%7>>1%") 312 | export("%8>>1%") 313 | export("%9>>1%") 314 | export("%10>>1%") 315 | export("%1>>2%") 316 | export("%2>>2%") 317 | export("%3>>2%") 318 | export("%4>>2%") 319 | export("%5>>2%") 320 | export("%6>>2%") 321 | export("%7>>2%") 322 | export("%8>>2%") 323 | export("%9>>2%") 324 | export("%10>>2%") 325 | export("%1>>3%") 326 | export("%2>>3%") 327 | export("%3>>3%") 328 | export("%4>>3%") 329 | export("%5>>3%") 330 | export("%6>>3%") 331 | export("%7>>3%") 332 | export("%8>>3%") 333 | export("%9>>3%") 334 | export("%10>>3%") 335 | export("%1>>4%") 336 | export("%2>>4%") 337 | export("%3>>4%") 338 | export("%4>>4%") 339 | export("%5>>4%") 340 | export("%6>>4%") 341 | export("%7>>4%") 342 | export("%8>>4%") 343 | export("%9>>4%") 344 | export("%10>>4%") 345 | export("%1>>5%") 346 | export("%2>>5%") 347 | export("%3>>5%") 348 | export("%4>>5%") 349 | export("%5>>5%") 350 | export("%6>>5%") 351 | export("%7>>5%") 352 | export("%8>>5%") 353 | export("%9>>5%") 354 | export("%10>>5%") 355 | export("%1>>6%") 356 | export("%2>>6%") 357 | export("%3>>6%") 358 | export("%4>>6%") 359 | export("%5>>6%") 360 | export("%6>>6%") 361 | export("%7>>6%") 362 | export("%8>>6%") 363 | export("%9>>6%") 364 | export("%10>>6%") 365 | export("%1>>7%") 366 | export("%2>>7%") 367 | export("%3>>7%") 368 | export("%4>>7%") 369 | export("%5>>7%") 370 | export("%6>>7%") 371 | export("%7>>7%") 372 | export("%8>>7%") 373 | export("%9>>7%") 374 | export("%10>>7%") 375 | export("%1>>8%") 376 | export("%2>>8%") 377 | export("%3>>8%") 378 | export("%4>>8%") 379 | export("%5>>8%") 380 | export("%6>>8%") 381 | export("%7>>8%") 382 | export("%8>>8%") 383 | export("%9>>8%") 384 | export("%10>>8%") 385 | export("%1>>9%") 386 | export("%2>>9%") 387 | export("%3>>9%") 388 | export("%4>>9%") 389 | export("%5>>9%") 390 | export("%6>>9%") 391 | export("%7>>9%") 392 | export("%8>>9%") 393 | export("%9>>9%") 394 | export("%10>>9%") 395 | export("%1>>10%") 396 | export("%2>>10%") 397 | export("%3>>10%") 398 | export("%4>>10%") 399 | export("%5>>10%") 400 | export("%6>>10%") 401 | export("%7>>10%") 402 | export("%8>>10%") 403 | export("%9>>10%") 404 | export("%10>>10%") 405 | export("%:>:%") 406 | export("%:>>:%") 407 | export("%:c>:%") 408 | export("%:i:%") 409 | export("%:pi:%") 410 | export("%>1%") 411 | export("%>2%") 412 | export("%>3%") 413 | export("%>4%") 414 | export("%>5%") 415 | export("%>6%") 416 | export("%>7%") 417 | export("%>8%") 418 | export("%>9%") 419 | export("%>10%") 420 | export("%>>1%") 421 | export("%>>2%") 422 | export("%>>3%") 423 | export("%>>4%") 424 | export("%>>5%") 425 | export("%>>6%") 426 | export("%>>7%") 427 | export("%>>8%") 428 | export("%>>9%") 429 | export("%>>10%") 430 | export(ModStore) 431 | export(Store) 432 | export(TidyModule) 433 | export(add_module) 434 | export(add_tm_snippets) 435 | export(callModules) 436 | export(check_and_load) 437 | export(combine_ports) 438 | export(defineEdges) 439 | export(getCacheOption) 440 | export(getMod) 441 | export(getSessionId) 442 | export(iport) 443 | export(listModules) 444 | export(map_ports) 445 | export(mod) 446 | export(oport) 447 | export(port) 448 | export(race_ports) 449 | export(session_type) 450 | export(showExamples) 451 | import(R6) 452 | import(dplyr) 453 | import(shiny) 454 | import(snippr) 455 | importFrom(cli,cat_boxx) 456 | importFrom(cli,cat_bullet) 457 | importFrom(fs,dir_create) 458 | importFrom(fs,dir_exists) 459 | importFrom(fs,file_create) 460 | importFrom(fs,file_exists) 461 | importFrom(fs,path) 462 | importFrom(fs,path_abs) 463 | importFrom(fs,path_ext_set) 464 | importFrom(fs,path_file) 465 | importFrom(fs,path_home_r) 466 | importFrom(methods,is) 467 | importFrom(purrr,discard) 468 | importFrom(purrr,keep) 469 | importFrom(purrr,map) 470 | importFrom(snippr,snippets_read) 471 | importFrom(utils,capture.output) 472 | importFrom(utils,file.edit) 473 | importFrom(utils,menu) 474 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # tidymodules 0.1.6 2 | 3 | - ```deep``` option : optional argument passed to the module ```destroy``` method. Allows the destruction of child module. ```FALSE``` by default. 4 | - Pipes updates : new ```%c>%```, copy all inputs; updated ```%:i:%``` and ```%:pi:%``` pipes. 5 | - Clear some check warnings/notes 6 | - Fix other issues 7 | 8 | # tidymodules 0.1.5 9 | 10 | - ```obser``` attribute : Now all {tm} modules have an obser attribute which is a list of all server observers. This is a convenient location to explore existing observers and helps in garbage collection. Users need to use ```self$obser$[observer_id]``` as a variable name when initializing the observers. All {tm} examples updated with ```obser```. 11 | 12 | - ```destroy``` method : This function destroys the module, it removes all the module's references from the ```ModStore``` (session module and edges) and destroy module observers stored in the ```obser``` attribute mentioned above. Note : This functionality rely on module developers to systematically store observers in the ```obser``` list. 13 | 14 | - ```suspend``` method : This function suspends module's observers stored in the ```obser``` attribute mentioned above. Note : This functionality rely on module developers to systematically store observers in the ```obser``` list. 15 | 16 | - ```resume``` method : This function resumes module's observers stored in the ```obser``` attribute mentioned above. Note : This functionality rely on module developers to systematically store observers in the ```obser``` list. 17 | 18 | # tidymodules 0.1.4 19 | 20 | - ```collision``` option : By default {tidymodules} doesn't allow the creation of two modules with same id at the same time (same timestamp). It fails with a collision error. This option which is ```FALSE``` by default, allows the user to disable collision check. This could be useful when users create module in an observer that get triggered twice at the same time. 21 | 22 | - ```react``` attribute : Now all {tm} modules have a react attribute which is a list to conveniently store server reactive objects (reactive / reactiveVal / reactiveValues). This list help store module reactive objects and facilitate their access from anywhere within the module object. Users need to use ```self$react$[reactive_id]``` as a variable name when initializing the objects. All {tm} examples updated with ```react```. 23 | 24 | - Debug mode with ```TM_DEBUG``` option : display a debug button and highlight module UI. Clicking the button allows to explore module environment in debug mode. try this ```options(TM_DEBUG=TRUE)```. 25 | 26 | - Fix issue with parent module look-up function. Now the code takes the parent option provided by the user as the source of truth. 27 | 28 | - Fix bug in calculating port length and pipe operators 29 | 30 | - Apply styler and fix some issues found with lintr 31 | 32 | # tidymodules 0.1.1 -> 0.1.3 33 | 34 | - Mainly bug fixes and some improvements here and there in the code 35 | 36 | # tidymodules 0.1.0.9007 37 | 38 | - Correct port attibutes assignment 39 | - Rename TidyModule field `parent_ports` to `pass_ports` 40 | - Make `assignPort` function work in dynamic context 41 | - Add `inherit` parameter to `addPort` function to better control ports inheritance 42 | - Add extra warnings and exceptions related to nested modules and port inheritance 43 | - Improve module console printing to highlight inherited ports 44 | 45 | # tidymodules 0.1.0.9006 46 | 47 | - Fix a problem where there is no shiny session argument in app server and calling modules' callModule & callModules. 48 | - Add warning to module get port functions for some specific cases (global vs user session) 49 | - fix module iport & oport functions 50 | - doc fix 51 | 52 | # tidymodules 0.1.0.9005 53 | 54 | - switch to Apache-2.0 Licence 55 | - fix doc 56 | 57 | 58 | # tidymodules 0.1.0.9004 59 | 60 | - add_module function 61 | - snippets file & function to inject them into RStudio configuration 62 | - new defineEdges() function for parsing module communication instructions 63 | 64 | # tidymodules 0.1.0.9003 65 | 66 | - Improve how the ports are moved around 67 | - Restrict port assignment to reactive function only. No more reactiveValues as this can be modified by module. tidymodules derived ports are an exception. 68 | - Clean-up pipe operators code 69 | - New '%->>%' pipe 70 | - Move input (i) and ouput (o) ports lists into public field to facilitate port lookup from a module reference 71 | - Add oport/iport to be consistent with the corresponding utility functions 72 | - Add exec`In/Out`put functions 73 | - Fix for Store module when edges are empty 74 | - Add check in ModStore for duplicated edges 75 | 76 | 77 | # tidymodules 0.1.0.9002 78 | 79 | - Adding shiny module code in example 1 80 | 81 | 82 | # tidymodules 0.1.0.9001 83 | 84 | - Support for nested modules stored in parent module attribute list 85 | - Sanitize namespace and group ID when provided 86 | - Switch to shiny getDefaultReactiveDomain to retrieve ShinySession 87 | - Update namespace vignette 88 | 89 | # tidymodules 0.1.0.9000 90 | 91 | - Add travis-CI for building pkgdsown site 92 | - Remove docs 93 | - Fix & complete docs/vignettes 94 | - Add new TidyModule fields : name & order 95 | - Fix issue for creating nested module in console & setting parent namespace 96 | 97 | # tidymodules 0.1.0 98 | 99 | - Github release 100 | -------------------------------------------------------------------------------- /R/ModStore.R: -------------------------------------------------------------------------------- 1 | 2 | #' R6 Class Representing a ModStore 3 | #' 4 | #' @description 5 | #' This class is used to create a storage for tidymodules objects. 6 | #' 7 | #' @details 8 | #' Manage applications, sessions and modules. 9 | #' 10 | #' @import shiny 11 | #' 12 | #' @export 13 | ModStore <- R6::R6Class( 14 | "ModStore", 15 | public = list( 16 | #' @description 17 | #' Create a new ModStore object. 18 | #' Should be called once by the TidyModule class. 19 | #' Not to be called directly outside TidyModule. 20 | #' The ModStore object can be retrieved from any TidyModule object, see example below. 21 | #' @examples 22 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 23 | #' m <- MyModule$new() 24 | #' s <- m$getStore() 25 | #' @return A new `ModStore` object. 26 | initialize = function() {}, 27 | #' @description 28 | #' Check if a module is stored in the current session. 29 | #' @param m TidyModule object. 30 | #' @examples 31 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 32 | #' m <- MyModule$new() 33 | #' s <- m$getStore() 34 | #' s$isStored(m) 35 | isStored = function(m) { 36 | s <- self$getSession(m) 37 | mod <- isolate(s$collection[[m$module_ns]]) 38 | if (is.null(mod)) { 39 | return(FALSE) 40 | } else { 41 | return(TRUE) 42 | } 43 | }, 44 | #' @description 45 | #' Retrieve the global session 'global_session'. 46 | #' This is the session that exists outside the application server function 47 | getGlobalSession = function() { 48 | sid <- "global_session" 49 | self$getSession(sid) 50 | }, 51 | #' @description 52 | #' Retrieve a module session. 53 | #' This could be the global session or a user session. 54 | #' @param m TidyModule object. 55 | getSession = function(m) { 56 | isolate({ 57 | return(private$getS(m)) 58 | }) 59 | }, 60 | #' @description 61 | #' Retrieve all sessions. 62 | getSessions = function() { 63 | return(private$sessions) 64 | }, 65 | #' @description 66 | #' Retrieve all modules. 67 | #' @param m TidyModule object. 68 | getMods = function(m) { 69 | s <- self$getSession(m) 70 | return(s$collection) 71 | }, 72 | #' @description 73 | #' Retrieve modules connections. 74 | #' @param m TidyModule object. 75 | getEdges = function(m) { 76 | s <- self$getSession(m) 77 | return(s$edges) 78 | }, 79 | #' @description 80 | #' Add modules connections into ModStore. 81 | #' An edge is either a connection between a reactive object and a module 82 | #' or between two modules. 83 | #' @param from list with three elements: m -> module, type -> input or output, port -> port Id. 84 | #' @param to list with three elements: m -> module, type -> input or output, port -> port Id. 85 | #' @param mode The type of edge, default to 'direct'. 86 | #' @param comment Any additional comment. 87 | addEdge = function(from, 88 | to, 89 | mode = "direct", 90 | comment = NA) { 91 | fromId <- fname <- fport <- ftype <- fclass <- NA 92 | toId <- tname <- tport <- ttype <- tclass <- NA 93 | s <- e <- d <- NULL 94 | 95 | isolate({ 96 | if (is(to$m, "TidyModule")) { 97 | s <- to$m$getSession() 98 | e <- self$getEdges(to$m) 99 | 100 | toId <- to$m$module_ns 101 | tport <- to$port 102 | tname <- to$m$getPortName(to$port, to$type) 103 | ttype <- to$type 104 | tclass <- "TidyModule" 105 | } 106 | 107 | if (is(from$m, "TidyModule")) { 108 | if (is.null(s)) { 109 | s <- from$m$getSession() 110 | e <- self$getEdges(from$m) 111 | } 112 | 113 | fromId <- from$m$module_ns 114 | fport <- from$port 115 | fname <- from$m$getPortName(from$port, from$type) 116 | ftype <- from$type 117 | fclass <- "TidyModule" 118 | 119 | # Handle tidymodules derived ports 120 | } else if (!is.null(attr(from$m, "tidymodules")) && 121 | attr(from$m, "tidymodules")) { 122 | mod <- attr(from$m, "tidymodules_operation") 123 | if (!is.null(mod) && mod == "combine") { 124 | mode <- mod 125 | combinedPorts <- reactiveValuesToList(from$m) 126 | for (key in names(combinedPorts)) { 127 | f <- combinedPorts[[key]] 128 | comment <- key 129 | fromId <- attr(f, "tidymodules_module_ns") 130 | fport <- attr(f, "tidymodules_port_id") 131 | ftype <- attr(f, "tidymodules_port_type") 132 | fname <- attr(f, "tidymodules_port_name") 133 | fclass <- "TidyModule" 134 | 135 | comb_row <- data.frame( 136 | from = fromId, 137 | fclass = fclass, 138 | fport = fport, 139 | ftype = ftype, 140 | fname = fname, 141 | to = toId, 142 | tclass = tclass, 143 | tport = tport, 144 | ttype = ttype, 145 | tname = tname, 146 | mode = mode, 147 | comment = comment 148 | ) 149 | 150 | if (is.null(d)) { 151 | d <- comb_row 152 | } else { 153 | d <- rbind(d, comb_row) 154 | } 155 | } 156 | } else { 157 | fromId <- attr(from$m, "tidymodules_module_ns") 158 | fport <- attr(from$m, "tidymodules_port_id") 159 | ftype <- attr(from$m, "tidymodules_port_type") 160 | fname <- attr(from$m, "tidymodules_port_name") 161 | fclass <- "TidyModule" 162 | } 163 | } else if (is.reactive(from$m)) { 164 | fromId <- attr(from$m, "observable")$.reactId 165 | comment <- attr(from$m, "observable")$.label 166 | # support for previous shiny version that don't have reactId (don't work with shiny 1.0.5) 167 | if (is.null(fromId)) { 168 | fromId <- comment 169 | } 170 | fclass <- "reactive" 171 | } else { 172 | stop("Unknown 'from' entity in addEdge function ", class(from$m), "/n") 173 | } 174 | 175 | if (is.null(d)) { 176 | d <- data.frame( 177 | from = fromId, 178 | fclass = fclass, 179 | fport = fport, 180 | ftype = ftype, 181 | fname = fname, 182 | to = toId, 183 | tclass = tclass, 184 | tport = tport, 185 | ttype = ttype, 186 | tname = tname, 187 | mode = mode, 188 | comment = comment, 189 | stringsAsFactors = FALSE 190 | ) 191 | } 192 | 193 | if (is.null(s) || s$sid == "global_session") { 194 | stop("addEdge function error! Module has no session or session is global [", s$sid, "]") 195 | } 196 | 197 | # track update time 198 | s$updated <- Sys.time() 199 | 200 | if (length(s$edges) == 0) { 201 | s$edges <- d 202 | } else { 203 | key <- paste0(as.character(d[1, ]), collapse = "|") 204 | keys <- apply(e, 1, paste0, collapse = "|") 205 | if (key %in% keys) { 206 | warning(paste0("Module mapping already exist!\n", key)) 207 | } else { 208 | s$edges <- rbind(e, d) 209 | } 210 | } 211 | }) 212 | }, 213 | #' @description 214 | #' Remove module edges 215 | #' @param m TidyModule object. 216 | delEdges = function(m){ 217 | isolate({ 218 | s <- private$getS(m) 219 | ns <- as.character(m$module_ns) 220 | if (length(s$edges) != 0) { 221 | s$edges <- s$edges %>% filter(from != ns & to != ns) 222 | } 223 | }) 224 | }, 225 | #' @description 226 | #' Add module into the ModStore. 227 | #' @param m TidyModule object. 228 | addMod = function(m) { 229 | isolate({ 230 | s <- private$getS(m) 231 | ns <- as.character(m$module_ns) 232 | 233 | # if(!is.null(s$collection[[ns]])) 234 | # stop(paste0("Module namespace ",ns," already stored!")) 235 | s$collection[[ns]] <- m 236 | if (!is.null(m$group)) { 237 | g <- as.character(m$group) 238 | if (is.null(s$g_collection[[g]])) { 239 | s$g_collection[[g]] <- list() 240 | } 241 | s$g_collection[[g]][[ns]] <- m 242 | } 243 | if (!is.null(m$parent_ns)) { 244 | p <- as.character(m$parent_ns) 245 | if (is.null(s$n_collection[[p]])) { 246 | s$n_collection[[p]] <- list() 247 | } 248 | s$n_collection[[p]][[ns]] <- m 249 | } 250 | # track update time 251 | s$updated <- Sys.time() 252 | # TODO : Do we really need this line below ? 253 | s$ns <- c(s$ns, as.character(m$module_ns)) 254 | }) 255 | }, 256 | #' @description 257 | #' Delete a module from the ModStore. 258 | #' @param m TidyModule object. 259 | delMod = function(m) { 260 | isolate({ 261 | s <- private$getS(m) 262 | ns <- as.character(m$module_ns) 263 | s$collection[[ns]] <- NULL 264 | if (!is.null(m$group)) { 265 | g <- as.character(m$group) 266 | if (!is.null(s$g_collection[[g]])) 267 | s$g_collection[[g]][[ns]] <- NULL 268 | } 269 | if (!is.null(m$parent_ns)) { 270 | p <- as.character(m$parent_ns) 271 | if (!is.null(s$n_collection[[p]])) 272 | s$n_collection[[p]][[ns]] <- NULL 273 | } 274 | s$ns <- s$ns[-grep(as.character(m$module_ns),s$ns)] 275 | # delete edges 276 | self$delEdges(m) 277 | # track update time 278 | s$updated <- Sys.time() 279 | }) 280 | }, 281 | #' @description 282 | #' Print the ModStore object. 283 | print = function() { 284 | aid <- private$getAID() 285 | isolate({ 286 | str(private$sessions[[aid]]$global_session$collection) 287 | }) 288 | } 289 | ), 290 | private = list( 291 | sessions = reactiveValues(), 292 | sessionExist = function(sid) { 293 | aid <- private$getAID() 294 | return( 295 | !is.null(private$sessions[[aid]]) && 296 | !is.null(private$sessions[[aid]][[sid]]) 297 | ) 298 | }, 299 | addSession = function(sid) { 300 | aid <- private$getAID() 301 | if (is.null(private$sessions[[aid]])) { 302 | private$sessions[[aid]] <- reactiveValues() 303 | } 304 | 305 | if (is.null(private$sessions[[aid]][[sid]])) { 306 | private$sessions[[aid]][[sid]] <- reactiveValues( 307 | aid = aid, 308 | path = getwd(), 309 | sid = sid, 310 | count = 0, 311 | created = Sys.time(), 312 | updated = Sys.time(), 313 | collection = list(), 314 | ns = c(), 315 | edges = data.frame() 316 | ) 317 | } else { 318 | FALSE 319 | } 320 | }, 321 | getS = function(m) { 322 | sid <- m 323 | if (is(m, "TidyModule")) { 324 | sid <- m$getSessionId() 325 | } 326 | aid <- private$getAID() 327 | if (!private$sessionExist(sid)) { 328 | private$addSession(sid) 329 | } 330 | return(private$sessions[[aid]][[sid]]) 331 | }, 332 | getAID = function() { 333 | return(digest::digest(getwd(), algo = "md5")) 334 | } 335 | ) 336 | ) 337 | -------------------------------------------------------------------------------- /R/Store.R: -------------------------------------------------------------------------------- 1 | 2 | #' 3 | #' This TidyModule is used to explore the content of the ModStore. 4 | #' 5 | #' @description 6 | #' Store is a TidyModule that can be used in your application to list existing applications, sessions and display your session's modules and edges. 7 | #' 8 | #' @details 9 | #' Should be initialized and injected in your application. 10 | #' 11 | #' @export 12 | Store <- R6::R6Class( 13 | classname = "Store", 14 | inherit = TidyModule, 15 | public = list( 16 | #' @description 17 | #' Store's ui function. 18 | #' @return UI elements. 19 | ui = function() { 20 | tagList( 21 | tabsetPanel( 22 | id = "store_ID", 23 | type = "tabs", 24 | tabPanel( 25 | "Sessions", 26 | fluidRow( 27 | br(), 28 | DT::dataTableOutput(self$ns("sessions")) 29 | ) 30 | ), 31 | tabPanel( 32 | "Mods", 33 | fluidRow( 34 | br(), 35 | DT::dataTableOutput(self$ns("mods")) 36 | ) 37 | ), 38 | tabPanel( 39 | "Edges", 40 | fluidRow( 41 | br(), 42 | DT::dataTableOutput(self$ns("edges")) 43 | ) 44 | ), 45 | tabPanel( 46 | "Port Mapping", 47 | fluidRow( 48 | br(), 49 | visNetwork::visNetworkOutput(self$ns("portD"), width = "100%", height = "800px") 50 | ) 51 | ) 52 | ) 53 | ) 54 | }, 55 | #' @description 56 | #' Store's server function. 57 | #' @param input Shiny input. 58 | #' @param output Shiny output 59 | #' @param session Shiny session 60 | server = function(input, output, session) { 61 | # Mandatory 62 | super$server(input, output, session) 63 | 64 | self$react$session_df <- reactive({ 65 | s <- self$getStore() 66 | d <- data.frame(aid = NULL, path = NULL, sid = NULL, created = NULL, mod_cnt = NULL, edge_cnt = NULL) 67 | 68 | for (aid in names(s$getSessions())) { 69 | for (sid in names(s$getSessions()[[aid]])) { 70 | ses <- s$getSessions()[[aid]][[sid]] 71 | mcount <- length(ses$collection) 72 | ecount <- nrow(ses$edges) 73 | d <- rbind(d, data.frame( 74 | aid = aid, 75 | path = ses$path, 76 | sid = sid, 77 | created = ses$created, 78 | updated = ses$updated, 79 | mod_cnt = mcount, 80 | edge_cnt = ecount 81 | )) 82 | } 83 | } 84 | rownames(d) <- NULL 85 | 86 | d 87 | }) 88 | 89 | self$react$mods_df <- reactive({ 90 | s <- self$getStore() 91 | d <- do.call( 92 | rbind, 93 | lapply( 94 | s$getMods(self), 95 | function(l) { 96 | data.frame( 97 | namespace = l$module_ns, 98 | class = paste(class(l), collapse = " <- "), 99 | parent = ifelse(is.null(l$parent_ns), "", l$parent_ns), 100 | created = l$created, 101 | in_ports = l$countInputPort(), 102 | out_ports = l$countOutputPort() 103 | ) 104 | } 105 | ) 106 | ) 107 | 108 | rownames(d) <- seq_len(nrow(d)) 109 | 110 | d 111 | }) 112 | 113 | self$react$edges_df <- reactive({ 114 | s <- self$getStore() 115 | e <- s$getEdges(self) 116 | req(nrow(e) != 0) 117 | 118 | e 119 | }) 120 | 121 | output$sessions <- DT::renderDataTable({ 122 | self$react$session_df() 123 | }) 124 | 125 | output$edges <- DT::renderDataTable({ 126 | self$react$edges_df() 127 | }) 128 | 129 | output$mods <- DT::renderDataTable({ 130 | self$react$mods_df() 131 | }) 132 | 133 | output$portD <- visNetwork::renderVisNetwork({ 134 | edges <- self$react$edges_df() 135 | nodes <- self$react$mods_df() 136 | 137 | e <- edges %>% 138 | mutate( 139 | font.size = 5, 140 | label = paste0(fport, " ", mode, ifelse(is.na(comment), "", paste0("(", comment, ")")), " ", tport) 141 | ) %>% 142 | select(from, to, label, font.size) 143 | 144 | # # minimal example 145 | # nodes <- data.frame(id = 1:3) 146 | # edges <- data.frame(from = c(1,2), to = c(1,3)) 147 | 148 | nId <- c(as.vector(e$from), as.vector(e$to), as.vector(nodes$namespace)) %>% unique() 149 | nType <- rbind( 150 | data.frame(name = edges$from, class = edges$fclass), 151 | data.frame(name = edges$to, class = edges$tclass) 152 | ) %>% unique() 153 | nClass <- as.character(nType[match(nId, nType$name), "class"]) 154 | nShape <- ifelse(nClass == "TidyModule" | is.na(nClass), "square", 155 | ifelse(nClass == "reactive", "box", "box") 156 | ) 157 | nColor <- ifelse(nClass == "TidyModule" | is.na(nClass), "lightblue", 158 | ifelse(nClass == "reactive", "orange", "grey") 159 | ) 160 | 161 | nGroup <- ifelse(nClass == "TidyModule" | is.na(nClass), "A", 162 | ifelse(nClass == "reactive", "B", "C") 163 | ) 164 | 165 | visNetwork::visNetwork( 166 | data.frame( 167 | id = nId, 168 | label = nId, 169 | group = nGroup, 170 | shape = nShape, 171 | # color = nColor, 172 | shadow = TRUE, 173 | value = 10 174 | ), 175 | e, 176 | height = "100%", 177 | width = "100%" 178 | ) %>% 179 | visNetwork::visEdges( 180 | shadow = TRUE, 181 | arrows = list(to = list(enabled = TRUE, scaleFactor = 2)), 182 | color = list(color = "lightblue", highlight = "yellow") 183 | ) %>% 184 | # visHierarchicalLayout(direction = "RL", levelSeparation = 500) 185 | visNetwork::visLayout(randomSeed = 12) 186 | }) 187 | } 188 | ) 189 | ) 190 | -------------------------------------------------------------------------------- /R/UtilityModule.R: -------------------------------------------------------------------------------- 1 | 2 | #' TidyModule utility class 3 | #' 4 | #' @description 5 | #' A module used by some of the utility functions to retrieve the ModStore object. 6 | #' 7 | #' @details 8 | #' This utility module is a special TidyModule class that doesn't get registered in the ModStore. 9 | #' It is used to retrieve ModStore objects, like sessions and modules. 10 | #' @examples 11 | #' \dontrun{ 12 | #' # Print current session Id 13 | #' UtilityModule$new()$getSessionId() 14 | #' } 15 | UtilityModule <- R6::R6Class( 16 | "UtilityModule", 17 | inherit = TidyModule, 18 | public = list( 19 | #' @description 20 | #' Initialize function. 21 | #' @return `UtilityModule` object. 22 | initialize = function() { 23 | if (is.null(private$shared$store)) { 24 | private$shared$store <- ModStore$new() 25 | } 26 | 27 | #### Set Shiny Session Here ####### 28 | private$shiny_session <- shiny::getDefaultReactiveDomain() 29 | } 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /R/add_module.R: -------------------------------------------------------------------------------- 1 | #' Create a module 2 | #' 3 | #' This function creates a `{tm}` module class inside the current folder. 4 | #' 5 | #' @param name The class name of the module. 6 | #' @param path Where to created the file. Default is `getwd()`. The function will add `R` to the path if the sub-folder exists. 7 | #' @param prefix filename prefix. Default is `tm`. Set to `NULL`` to disable. 8 | #' @param inherit Parent module class. Default is TidyModule. 9 | #' @param open Should the file be opened? 10 | #' @param dir_create Creates the directory if it doesn't exist, default is `TRUE`. 11 | #' @param export Logical. Should the module be exported? Default is `FALSE`. 12 | #' @note As a convention, this function will automatically capitalize the first character of the `name` argument. 13 | #' 14 | #' @importFrom cli cat_bullet 15 | #' @importFrom utils file.edit 16 | #' @importFrom fs path_abs path file_create 17 | #' @importFrom snippr snippets_read 18 | #' 19 | #' @export 20 | add_module <- function(name, 21 | inherit = "TidyModule", 22 | path = getwd(), 23 | prefix = "tm", 24 | open = TRUE, 25 | dir_create = TRUE, 26 | export = FALSE) { 27 | name <- file_path_sans_ext(name) 28 | # Capitalize 29 | name <- paste0(toupper(substring(name, 1, 1)), substring(name, 2)) 30 | 31 | dir_created <- create_if_needed( 32 | fs::path(path), 33 | type = "directory" 34 | ) 35 | if (!dir_created) { 36 | cat_red_bullet( 37 | "File not added (needs a valid directory)" 38 | ) 39 | return(invisible(FALSE)) 40 | } 41 | 42 | if (dir.exists(fs::path(path, "R"))) { 43 | path <- fs::path(path, "R") 44 | } 45 | 46 | old <- setwd(path_abs(path)) 47 | on.exit(setwd(old)) 48 | 49 | where <- fs::path( 50 | paste0(ifelse(is.null(prefix), "", paste0(prefix, "_")), name, ".R") 51 | ) 52 | 53 | if (!check_file_exist(where)) { 54 | cat_red_bullet( 55 | "File not created (already exists)" 56 | ) 57 | return(invisible(FALSE)) 58 | } 59 | 60 | # make sure the provided parent module is valid 61 | import <- NULL 62 | parent <- inherit 63 | # TidyModule object 64 | if (is(parent, "TidyModule")) { 65 | parent <- class(parent)[1] 66 | } 67 | # Load the class generator from the name 68 | if (class(parent) == "character") { 69 | tryCatch( 70 | { 71 | parent <- eval(parse(text = parent)) 72 | }, 73 | error = function(e) { 74 | cat_red_bullet( 75 | paste0("Could not find module defined with 'inherit' = ", inherit) 76 | ) 77 | return(invisible(FALSE)) 78 | } 79 | ) 80 | } 81 | # Retrieve package dependency and parent module name 82 | if (is(parent, "R6ClassGenerator")) { 83 | clist <- get_R6CG_list(parent) 84 | if ("TidyModule" %in% clist) { 85 | import <- environmentName(parent$parent_env) 86 | if (import == "R_GlobalEnv") { 87 | import <- NULL 88 | } 89 | parent <- clist[1] 90 | } else { 91 | cat_red_bullet( 92 | paste0("Could not find module defined with 'inherit' = ", deparse(substitute(inherit))) 93 | ) 94 | return(invisible(FALSE)) 95 | } 96 | } 97 | 98 | 99 | # Retrieve content from package snippet 100 | file_content <- snippr::snippets_read(path = system.file("rstudio/r.snippets", package = "tidymodules"))$tm.mod.new 101 | file_content <- unlist(strsplit(file_content, "\\n")) 102 | for (l in seq_len(length(file_content))) { 103 | # remove $ escapes \\ 104 | file_content[l] <- sub("\\$", "$", file_content[l], fixed = TRUE) 105 | # remove tabs 106 | file_content[l] <- sub("\t", "", file_content[l]) 107 | # remove snippet placeholders 108 | file_content[l] <- gsub("\\$\\{\\d+:(\\w+)\\}", "%\\1", file_content[l]) 109 | # remove cursor pointer 110 | file_content[l] <- sub("\\$\\{0\\}", "", file_content[l]) 111 | # substitute module name 112 | if (grepl("MyModule", file_content[l])) { 113 | file_content[l] <- gsub("MyModule", "s", file_content[l]) 114 | file_content[l] <- sprintf(file_content[l], name) 115 | } 116 | # substitute parent module 117 | if (grepl("TidyModule", file_content[l])) { 118 | file_content[l] <- gsub("TidyModule", "s", file_content[l]) 119 | file_content[l] <- sprintf(file_content[l], parent) 120 | } 121 | # manage export 122 | if (grepl("@export", file_content[l])) { 123 | if (!export) { 124 | file_content[l] <- "#' @noRd " 125 | } 126 | if (!is.null(import)) { 127 | file_content[l] <- paste0("#'\n#' @import ", import, "\n", file_content[l]) 128 | } 129 | } 130 | } 131 | writeLines(file_content, where, sep = "\n") 132 | 133 | cat_created(fs::path(path, where)) 134 | open_or_go_to(where, open) 135 | } 136 | 137 | # bunch of utility functions copied from golem 138 | # WILL FACILITATE MIGRATING THIS FUNCTION TO GOLEM 139 | 140 | #' @importFrom utils menu 141 | yesno <- function(...) { 142 | cat(paste0(..., collapse = "")) 143 | menu(c("Yes", "No")) == 1 144 | } 145 | 146 | #' @importFrom fs file_exists 147 | check_file_exist <- function(file) { 148 | res <- TRUE 149 | if (file_exists(file)) { 150 | cat_orange_bullet(file) 151 | res <- yesno("This file already exists, override?") 152 | } 153 | return(res) 154 | } 155 | 156 | #' @importFrom fs dir_create file_create 157 | create_if_needed <- function(path, 158 | type = c("file", "directory"), 159 | content = NULL) { 160 | type <- match.arg(type) 161 | # Check if file or dir already exist 162 | if (type == "file") { 163 | dont_exist <- file_not_exist(path) 164 | } else if (type == "directory") { 165 | dont_exist <- dir_not_exist(path) 166 | } 167 | # If it doesn't exist, ask if we are allowed 168 | # to create it 169 | if (dont_exist) { 170 | ask <- yesno( 171 | sprintf( 172 | "The %s %s doesn't exist, create?", 173 | basename(path), 174 | type 175 | ) 176 | ) 177 | # Return early if the user doesn't allow 178 | if (!ask) { 179 | return(FALSE) 180 | } else { 181 | # Create the file 182 | if (type == "file") { 183 | if (dir_not_exist(dirname(path))) { 184 | dir_create(dirname(path), recurse = TRUE) 185 | } 186 | file_create(path) 187 | write(content, path, append = TRUE) 188 | } else if (type == "directory") { 189 | dir_create(path, recurse = TRUE) 190 | } 191 | } 192 | } 193 | 194 | # TRUE means that file exists (either 195 | # created or already there) 196 | return(TRUE) 197 | } 198 | 199 | 200 | #' @importFrom cli cat_bullet 201 | cat_green_tick <- function(...) { 202 | cat_bullet( 203 | ..., 204 | bullet = "tick", 205 | bullet_col = "green" 206 | ) 207 | } 208 | 209 | #' @importFrom cli cat_bullet 210 | cat_red_bullet <- function(...) { 211 | cat_bullet( 212 | ..., 213 | bullet = "bullet", 214 | bullet_col = "red" 215 | ) 216 | } 217 | 218 | #' @importFrom cli cat_bullet 219 | cat_orange_bullet <- function(...) { 220 | cat_bullet( 221 | ..., 222 | bullet = "bullet", 223 | bullet_col = "orange" 224 | ) 225 | } 226 | 227 | #' @importFrom cli cat_bullet 228 | cat_info <- function(...) { 229 | cat_bullet( 230 | ..., 231 | bullet = "arrow_right", 232 | bullet_col = "grey" 233 | ) 234 | } 235 | 236 | #' @importFrom fs path_file 237 | cat_exists <- function(where) { 238 | cat_red_bullet( 239 | sprintf( 240 | "%s already exists, skipping the copy.", 241 | path_file(where) 242 | ) 243 | ) 244 | cat_info( 245 | sprintf( 246 | "If you want replace it, remove the %s file first.", 247 | path_file(where) 248 | ) 249 | ) 250 | } 251 | 252 | cat_created <- function(where, 253 | file = "File") { 254 | cat_green_tick( 255 | sprintf( 256 | "%s created at %s", 257 | file, 258 | where 259 | ) 260 | ) 261 | } 262 | 263 | open_or_go_to <- function(where, 264 | open) { 265 | if ( 266 | rstudioapi::isAvailable() && 267 | open && 268 | rstudioapi::hasFun("navigateToFile") 269 | ) { 270 | rstudioapi::navigateToFile(where) 271 | } else { 272 | cat_red_bullet( 273 | sprintf( 274 | "Go to %s", 275 | where 276 | ) 277 | ) 278 | } 279 | } 280 | 281 | desc_exist <- function(pkg) { 282 | file_exists( 283 | paste0(pkg, "/DESCRIPTION") 284 | ) 285 | } 286 | 287 | 288 | file_created_dance <- function(where, 289 | fun, 290 | pkg, 291 | dir, 292 | name, 293 | open) { 294 | cat_created(where) 295 | 296 | fun(pkg, dir, name) 297 | 298 | open_or_go_to(where, open) 299 | } 300 | 301 | if_not_null <- function(x, ...) { 302 | if (!is.null(x)) { 303 | force(...) 304 | } 305 | } 306 | 307 | set_name <- function(x, y) { 308 | names(x) <- y 309 | x 310 | } 311 | 312 | # FROM tools::file_path_sans_ext() & tools::file_ext 313 | file_path_sans_ext <- function(x) { 314 | sub("([^.]+)\\.[[:alnum:]]+$", "\\1", x) 315 | } 316 | 317 | file_ext <- function(x) { 318 | pos <- regexpr("\\.([[:alnum:]]+)$", x) 319 | ifelse(pos > -1L, substring(x, pos + 1L), "") 320 | } 321 | 322 | #' @importFrom fs dir_exists file_exists 323 | dir_not_exist <- Negate(dir_exists) 324 | file_not_exist <- Negate(file_exists) 325 | -------------------------------------------------------------------------------- /R/examples.R: -------------------------------------------------------------------------------- 1 | #' @title Launcher for the tidymodules examples 2 | #' 3 | #' @description Helper function to launch the tidymodules examples. 4 | #' 5 | #' @param id Example ID. If null display list of examples with ID. 6 | #' @param server boolean. Is this a server call? 7 | #' @param options list of options to be passed to shinyApps or shinyDir 8 | #' 9 | #' @export 10 | #' 11 | #' @examples 12 | #' 13 | #' if (interactive()) { 14 | #' showExamples(1) 15 | #' } 16 | showExamples <- function(id = NULL, server = F, options = NULL) { 17 | examples <- list.dirs(system.file(package = "tidymodules", "shiny/examples"), recursive = F) 18 | if (is.null(id)) { 19 | names(examples) <- seq_len(length(examples)) 20 | basename(examples) 21 | } else { 22 | if (!is.numeric(id)) { 23 | stop("Please provide a numeric value") 24 | } 25 | if (id > length(examples) || id < 1) { 26 | stop("Wrong ID provided") 27 | } 28 | 29 | if (server) { 30 | setwd(examples[id]) 31 | if (!is.null(options)) { 32 | shiny::shinyAppDir(examples[id], options = options) 33 | } else { 34 | shiny::shinyAppDir(examples[id]) 35 | } 36 | } else { 37 | shiny::runApp(examples[id]) 38 | } 39 | } 40 | } 41 | 42 | #' @title check if list of package namespaces exist, load them or display relevant information 43 | #' 44 | #' @description Utility function for managing package dependencies for tidymodules examples 45 | #' 46 | #' @param packages character vector of package names 47 | #' 48 | #' @export 49 | #' 50 | #' @examples 51 | #' 52 | #' check_and_load("ggplot2") 53 | #' 54 | check_and_load <- function(packages) { 55 | missing <- NULL 56 | for (p in packages) { 57 | if (!requireNamespace(p, quietly = TRUE)) { 58 | missing <- c(missing, p) 59 | } else { 60 | library(p, character.only = TRUE) 61 | } 62 | } 63 | 64 | if (!is.null(missing)) { 65 | stop( 66 | "The package(s) above are needed for this shiny example to work, please install them first...\n", 67 | cli::cat_bullet(missing, bullet_col = "red"), 68 | call. = FALSE 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /R/snippets.R: -------------------------------------------------------------------------------- 1 | #' 2 | #' @title Add `{tm}` snippets to RStudio 3 | #' 4 | #' @description This function adds useful `{tm}` code snippets to RStudio. 5 | #' 6 | #' @param force Force the re-installation when the snippets are already installed. 7 | #' 8 | #' @import snippr 9 | #' @import dplyr 10 | #' @importFrom fs path_home_r path_ext_set 11 | #' @importFrom cli cat_bullet 12 | #' @importFrom purrr keep discard map 13 | #' @export 14 | add_tm_snippets <- function(force = FALSE) { 15 | # R snippets file 16 | path <- path_home_r(".R", "snippets", path_ext_set("r", "snippets")) 17 | 18 | if (!create_if_needed(path)) { 19 | cat_bullet("Skip installation of snippets", 20 | bullet_col = "red", 21 | bullet = "bullet" 22 | ) 23 | } 24 | 25 | # retrieve current and new snippets 26 | current_all_snippets <- snippets_get(path = path) 27 | current_non_tm_snippets <- current_all_snippets[!grepl("^tm\\.", names(current_all_snippets), perl = TRUE)] 28 | current_tm_snippets <- current_all_snippets[grepl("^tm\\.", names(current_all_snippets), perl = TRUE)] 29 | new_tm_snippets <- snippets_read(path = system.file("rstudio/r.snippets", package = "tidymodules")) 30 | # calculate differences 31 | del_snippets <- setdiff(names(current_tm_snippets), names(new_tm_snippets)) 32 | keep_snippets <- intersect(names(current_tm_snippets), names(new_tm_snippets)) 33 | add_snippets <- setdiff(names(new_tm_snippets), names(current_tm_snippets)) 34 | # print some informations 35 | if (length(del_snippets) > 0) { 36 | cat_bullet(paste0("Deleting ", length(del_snippets), " snippet(s):"), 37 | bullet_col = "orange", 38 | bullet = "bullet" 39 | ) 40 | invisible(map(del_snippets, cat_bullet, bullet = "dot")) 41 | } 42 | existing_snippets <- current_non_tm_snippets 43 | save_snippets <- NULL 44 | if (length(keep_snippets) > 0) { 45 | if (force) { 46 | cat_bullet(paste0("Re-installing ", length(keep_snippets), " existing snippet(s):"), 47 | bullet_col = "green", 48 | bullet = "tick" 49 | ) 50 | invisible(map(keep_snippets, cat_bullet, bullet = "dot")) 51 | save_snippets <- new_tm_snippets[keep_snippets] 52 | } else { 53 | cat_bullet(paste0("Skip installation of ", length(keep_snippets), " existing snippet(s):"), 54 | bullet_col = "red", 55 | bullet = "bullet" 56 | ) 57 | invisible(map(keep_snippets, cat_bullet, bullet = "dot")) 58 | existing_snippets <- c(existing_snippets, current_tm_snippets[keep_snippets]) 59 | } 60 | } 61 | if (length(add_snippets) > 0) { 62 | cat_bullet(paste0("Installing ", length(add_snippets), " new snippets:"), 63 | bullet_col = "green", 64 | bullet = "tick" 65 | ) 66 | invisible(map(add_snippets, cat_bullet, bullet = "dot")) 67 | save_snippets <- c(save_snippets, new_tm_snippets[add_snippets]) 68 | } 69 | 70 | final_snippets <- existing_snippets 71 | if (!is.null(save_snippets)) { 72 | final_snippets <- c(final_snippets, save_snippets) 73 | } 74 | 75 | snippets_write(final_snippets, path = path) 76 | } 77 | -------------------------------------------------------------------------------- /R/tidymodules.R: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Novartis AG 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #' @importFrom methods is 16 | #' 17 | #' @keywords internal 18 | "_PACKAGE" 19 | -------------------------------------------------------------------------------- /R/utility.R: -------------------------------------------------------------------------------- 1 | 2 | #' 3 | #' @title Retrieve module from ModStore 4 | #' 5 | #' @description This utility function retrieve tidymodules from the central ModStore 6 | #' using module namespace/id and/or group 7 | #' 8 | #' @param id Name or Id of the module 9 | #' @param group Group name 10 | #' 11 | #' @import shiny 12 | #' 13 | #' @export 14 | #' 15 | #' @examples 16 | #' 17 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 18 | #' MyModule$new("MyFirst") 19 | #' MyModule$new("MySecond") 20 | #' MyModule$new("MyThird", group = "B") 21 | #' 22 | #' # MyFirst 23 | #' getMod(1) 24 | #' getMod("MyFirst") 25 | #' 26 | #' # MySecond 27 | #' getMod(2) 28 | #' 29 | #' # MyThird 30 | #' getMod(2) 31 | #' getMod("B-MyThird") 32 | #' getMod(1, group = "B") 33 | #' 34 | getMod <- function(id = 1, group = NULL) { 35 | m <- UtilityModule$new() 36 | mod <- NULL 37 | c <- isolate(m$getSession()$collection) 38 | gc <- isolate(m$getSession()$g_collection) 39 | 40 | if (!is.null(group) && !is.numeric(id)) { 41 | id <- paste0(id, "-G-", group) 42 | } 43 | 44 | if (is.null(group)) { 45 | mod <- c[[id]] 46 | } else { 47 | mod <- gc[[group]][[id]] 48 | } 49 | 50 | if (is.null(mod)) { 51 | warning(paste0("Module ", id, " not found!")) 52 | } 53 | 54 | mod 55 | } 56 | #' 57 | #' @title Alias to getMod 58 | #' 59 | #' @description See \code{\link{getMod}} 60 | #' 61 | #' @param id Name or Id of the module 62 | #' @param group Group name 63 | #' 64 | #' @import shiny 65 | #' 66 | #' @export 67 | mod <- function(id = 1, group = NULL) { 68 | getMod(id, group) 69 | } 70 | 71 | #' 72 | #' @title Retrieve module's port 73 | #' 74 | #' @description This utility function retrieve the tidymodules port specified in the arguments. 75 | #' 76 | #' @param id Name or Id of the module 77 | #' @param g Module group name 78 | #' @param t Port type, in or out 79 | #' @param p Port Id or name 80 | #' 81 | #' @import shiny 82 | #' 83 | #' @export 84 | port <- function(id = 1, p = 1, t = "in", g = NULL) { 85 | m <- getMod(id, g) 86 | if (is.null(m)) { 87 | return(NULL) 88 | } else { 89 | if (t == "in") { 90 | return(m$getInputPort(p)) 91 | } else { 92 | return(m$getOutputPort(p)) 93 | } 94 | } 95 | } 96 | #' 97 | #' @title Retrieve input module's port 98 | #' 99 | #' @description This utility function retrieve the tidymodules input port specified in the arguments. 100 | #' 101 | #' @param id Name or Id of the module 102 | #' @param g Module group name 103 | #' @param p Port Id or name 104 | #' 105 | #' @import shiny 106 | #' 107 | #' @export 108 | iport <- function(id = 1, p = 1, g = NULL) { 109 | port(id, p, "in", g) 110 | } 111 | #' 112 | #' @title Retrieve output module's port 113 | #' 114 | #' @description This utility function retrieve the tidymodules output port specified in the arguments. 115 | #' 116 | #' @param id Name or Id of the module 117 | #' @param g Module group name 118 | #' @param p Port Id or name 119 | #' 120 | #' @import shiny 121 | #' 122 | #' @export 123 | oport <- function(id = 1, p = 1, g = NULL) { 124 | port(id, p, "out", g) 125 | } 126 | 127 | #' 128 | #' @title List modules in current session 129 | #' 130 | #' @description This function list module objects found in the current session 131 | #' 132 | #' @param verbose Display module description as well 133 | #' @param global use the global session? Default to FALSE 134 | #' 135 | #' @importFrom cli cat_bullet cat_boxx 136 | #' @importFrom utils capture.output 137 | #' @import shiny 138 | #' 139 | #' @export 140 | listModules <- function(verbose = FALSE, global = FALSE) { 141 | currentSession <- UtilityModule$new()$getSession() 142 | if(global) 143 | currentSession <- UtilityModule$new()$getGlobalSession() 144 | isolate({ 145 | if (length(currentSession$collection) == 0) { 146 | cat_bullet(paste0("No module found!"), 147 | bullet_col = "orange", 148 | bullet = "cross" 149 | ) 150 | } else { 151 | cat_bullet(paste0("Found ", length(currentSession$collection), " module(s)!"), 152 | bullet_col = "green", 153 | bullet = "tick" 154 | ) 155 | } 156 | invisible(for (mod in currentSession$collection) { 157 | cat_bullet(mod$module_ns, bullet = "circle_dotted") 158 | if (verbose) { 159 | cat_boxx(capture.output(mod)) 160 | } 161 | }) 162 | }) 163 | } 164 | 165 | #' 166 | #' @title Call modules function 167 | #' 168 | #' @description This utility function call all modules initialized in the global session. 169 | #' The global session is the session shared outside the server function of the application. 170 | #' All the modules initialized in the global session can be called with this function in a single call. 171 | #' The function take care of cloning and attaching them to the current user session. 172 | #' 173 | #' Note that this function can only be called in the app server function at the moment. 174 | #' We are working on supporting callModules within module server function for invoking nested modules. 175 | #' 176 | #' 177 | #' @import shiny 178 | #' 179 | #' @export 180 | callModules <- function() { 181 | currentSession <- UtilityModule$new()$getSession() 182 | globalSession <- UtilityModule$new()$getGlobalSession() 183 | disable_cache <- getCacheOption() 184 | 185 | calls <- c() 186 | 187 | isolate({ 188 | # re-initialize current session 189 | currentSession$edges <- data.frame() 190 | currentSession$count <- globalSession$count 191 | 192 | lapply(globalSession$collection, function(mod) { 193 | if (is.null(currentSession$collection[[mod$module_ns]]) || disable_cache) { 194 | ######## Try to capture server function arguments here ######## 195 | serverEnv <- parent.frame(3) 196 | o <- i <- s <- NULL 197 | if (!is.null(serverEnv)) { 198 | if (!is.null(serverEnv$input) && 199 | is(serverEnv$input, "reactivevalues")) { 200 | i <- serverEnv$input 201 | } 202 | if (!is.null(serverEnv$output) && 203 | is(serverEnv$output, "shinyoutput")) { 204 | o <- serverEnv$output 205 | } 206 | if (!is.null(serverEnv$session) && 207 | is(serverEnv$session, "ShinySession")) { 208 | s <- serverEnv$session 209 | } 210 | if (is.null(s)) { 211 | s <- getDefaultReactiveDomain() 212 | } 213 | } 214 | cloned <- mod$deepClone(o, i, s) 215 | } 216 | # Don't invoke nested modules as they will be invoked by parents 217 | # TODO : Change function to allow callModules within Module server (inject nested modules) 218 | if (is.null(currentSession$collection[[mod$module_ns]]$parent_ns)) { 219 | calls <<- c(calls, currentSession$collection[[mod$module_ns]]) 220 | } 221 | }) 222 | }) 223 | lapply(calls, function(m) m$callModule()) 224 | } 225 | #' 226 | #' @title Function wrapper for ports connection expression. 227 | #' 228 | #' @description Used in server functions to define how modules are connected to each other. 229 | #' 230 | #' @param x expression 231 | #' 232 | #' @import shiny 233 | #' 234 | #' @export 235 | defineEdges <- function(x) { 236 | observe({ 237 | isolate(x) 238 | }) 239 | } 240 | 241 | 242 | #' 243 | #' @title Retrieve cache option from the environment 244 | #' 245 | #' @description The cache option `tm_disable_cache` is a global options that enable or disable the use of existing modules from the current session. 246 | #' This option is `FALSE` by default and should be used in concordance with the `tm_session_type` global option. See \code{\link{session_type}} for a list of possible session type. 247 | #' 248 | #' @export 249 | getCacheOption <- function() { 250 | disable_cache <- getOption("tm_disable_cache") 251 | if (is.null(disable_cache)) { 252 | disable_cache <- FALSE 253 | } 254 | disable_cache <- as.logical(disable_cache) 255 | 256 | if (is.na(disable_cache)) { 257 | stop("Option 'tm_disable_cache' should be set to a logical value or unset.") 258 | } 259 | 260 | disable_cache 261 | } 262 | #' 263 | #' @title List of possible session types 264 | #' 265 | #' @description tidymodules offers the ability to manage application sessions. 266 | #' At the moment the three options below are available. 267 | #' 268 | #' \itemize{ 269 | #' 270 | #' \item{SHINY}{ : The default behaviour of shiny application and the default for tidymodules. Every time you access an application 271 | #' you get a new token Id that defines your application user session.} 272 | #' 273 | #' \item{USER}{ : This method defines a session based on the information available in the request object of shiny output. 274 | #' It is a concatenation of the variables REMOTE_ADDR, HTTP_HOST and PATH_INFO like below. 275 | #' 276 | #' \code{sid <- paste0(r$REMOTE_ADDR,"@",r$HTTP_HOST,r$PATH_INFO))} 277 | #' 278 | #' Note that the method is actually not working properly for now as the information available via the request object 279 | #' are not reflecting the actual user. We are working on a better method to uniquely identify a remote user.} 280 | #' 281 | #' \item{CUSTOM}{ : This method allow the developper to provide a custom function for generating the session Id. 282 | #' It relies on the global options `tm_session_custom` being set and pointing to a function taking a shiny output as argument.} 283 | #' 284 | #' } 285 | #' 286 | #' @export 287 | session_type <- list( 288 | SHINY = 1, 289 | USER = 2, 290 | CUSTOM = 3 291 | ) 292 | 293 | #' 294 | #' @title tidymodules options 295 | #' 296 | #' @name global_options 297 | #' 298 | #' @description List of global options used to adjust tidymodules configuration. 299 | #' 300 | #' \itemize{ 301 | #' \item{**tm_session_type**}{ : Define the type of the session, See available session types in \code{\link{session_type}} } 302 | #' \item{**tm_session_custom**}{ : Used to set a custom function for generating the session Id. Used in concordance with the `CUSTOM` session type.} 303 | #' \item{**tm_disable_cache**}{ : Disable caching of modules. This option is set to FALSE by default but is only relevant when user's session is managed properly. See also \code{\link{getCacheOption}}} 304 | #' } 305 | #' 306 | #' @rdname global_options 307 | #' 308 | NULL 309 | 310 | #' 311 | #' @title Function that generates session Id 312 | #' 313 | #' @description tidymodules offers the ability to manage application sessions. 314 | #' This function is the main function used by tidymodules to find the current session Id. 315 | #' It takes an optional ShinySession object as argument. If null, default to the global_session. 316 | #' 317 | #' @param session A shiny session as provide by the shiny server function. 318 | #' 319 | #' @return A session ID 320 | #' 321 | #' @import shiny 322 | #' 323 | #' @export 324 | getSessionId <- function(session = getDefaultReactiveDomain()) { 325 | if (is.null(session)) { 326 | return("global_session") 327 | } else { 328 | stype <- getOption("tm_session_type") 329 | sid <- NULL 330 | if (is.null(stype)) { 331 | stype <- session_type$SHINY 332 | } 333 | switch(stype, 334 | # SHINY 335 | { 336 | sid <- session$token 337 | }, 338 | # USER 339 | { 340 | r <- session$request 341 | sid <- paste0(r$REMOTE_ADDR, "@", r$HTTP_HOST, r$PATH_INFO) 342 | }, 343 | # CUSTOM 344 | { 345 | fct <- getOption("tm_session_custom") 346 | if (is.null(fct) || class(fct) != "function") { 347 | stop("Option 'tm_session_custom' should be set to a function taking a ShinySession object as option and generating a custom session ID used by tidymodules to identify module sessions.") 348 | } 349 | sid <- fct(session) 350 | } 351 | ) 352 | return(sid) 353 | } 354 | } 355 | 356 | 357 | #' 358 | #' @title Recursive function for retrieving R6ClassGenerator inheritance 359 | #' 360 | #' @description This function is used to retrieve a list of class name that a R6ClassGenerator object inherit from. 361 | #' 362 | #' @param r6cg A R6ClassGenerator object. 363 | #' 364 | #' @return vector of class names 365 | #' 366 | #' @keywords internal 367 | get_R6CG_list <- function(r6cg) { 368 | if (!is(r6cg, "R6ClassGenerator")) { 369 | stop("provide a R6ClassGenerator object!") 370 | } 371 | clist <- r6cg$classname 372 | if (!is.null(r6cg$get_inherit())) { 373 | clist <- c(clist, get_R6CG_list(r6cg$get_inherit())) 374 | } 375 | 376 | return(clist) 377 | } 378 | -------------------------------------------------------------------------------- /R/verbs.R: -------------------------------------------------------------------------------- 1 | #' connect ports from two different modules 2 | #' 3 | #' 4 | #' @title Ports mapping function 5 | #' 6 | #' @description This function maps a module's outpout port to another module's input port. 7 | #' 8 | #' @param leftModule The left module object 9 | #' @param leftPort Port name or Id of the left module's output port 10 | #' @param rightModule The right module object 11 | #' @param rightPort Port name or Id of the right module's input port 12 | #' @param reverse ligical value indicating which module to return. Default to FALSE, the right module 13 | #' 14 | #' @export 15 | map_ports <- function(leftModule = NULL, leftPort = 1, 16 | rightModule = NULL, rightPort = 1, 17 | reverse = FALSE) { 18 | if (!is.numeric(leftPort)) { 19 | stop("Left port ID 'leftPort' should be numeric") 20 | } 21 | if (!is.numeric(rightPort)) { 22 | stop("Right port ID 'rightPort' should be numeric") 23 | } 24 | 25 | fct <- make_double_pipe(leftPort, rightPort, rev = reverse) 26 | fct(leftModule, rightModule) 27 | } 28 | 29 | 30 | #' @title Combine ports function 31 | #' 32 | #' @description This function combines ports into a reactive list (reactiveValues) 33 | #' 34 | #' @param ... key/value pairs of ports 35 | #' 36 | #' @examples 37 | #' \dontrun{ 38 | #' # Somewhere in the app... 39 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 40 | #' MyModule$new("Mod1") 41 | #' MyModule$new("Mod2") 42 | #' MyModule$new("Mod3") 43 | #' 44 | #' # Must be in the server code and after calling the modules! 45 | #' callModules() 46 | #' observe({ 47 | #' combine_ports( 48 | #' input_1 = mod(1)$getOutput(1), 49 | #' input_2 = mod(2)$getOutput(1) 50 | #' ) %>1% mod(3) 51 | #' }) 52 | #' } 53 | #' 54 | #' @import shiny 55 | #' 56 | #' @export 57 | combine_ports <- function(...) { 58 | args <- list(...) 59 | r <- NULL 60 | if (length(args)) { 61 | if (is.null(names(args))) { 62 | names(args) <- seq_len(length(args)) 63 | } 64 | r <- do.call(reactiveValues, args) 65 | } else { 66 | r <- reactiveValues() 67 | } 68 | 69 | # Make this reactive aware of its tidymoduleness 70 | attr(r, "tidymodules") <- TRUE 71 | attr(r, "tidymodules_operation") <- "combine" 72 | 73 | return(r) 74 | } 75 | 76 | #' 77 | #' @title Race ports function 78 | #' 79 | #' @description This function collapse ports into a single port and make them race (i.e. always return the last one updated) 80 | #' 81 | #' @param ... List of racing ports 82 | #' 83 | #' @examples 84 | #' \dontrun{ 85 | #' # Somewhere in the app... 86 | #' MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 87 | #' MyModule$new("Mod1") 88 | #' MyModule$new("Mod2") 89 | #' MyModule$new("Mod3") 90 | #' 91 | #' # Must be in the server code and after calling the modules! 92 | #' callModules() 93 | #' observe({ 94 | #' race_ports( 95 | #' mod(1)$getOutput(1), 96 | #' mod(2)$getOutput(1) 97 | #' ) %>1% mod(3) 98 | #' }) 99 | #' } 100 | #' 101 | #' @import shiny 102 | #' 103 | #' @export 104 | race_ports <- function(...) { 105 | racers <- list(...) 106 | 107 | if (is.null(racers) || length(racers) == 0) { 108 | stop("In order to start a race, we need some ports!") 109 | } 110 | 111 | p <- length(racers) + 1 112 | r <- reactiveVal( 113 | label = "race", 114 | value = reactive({ 115 | }) 116 | ) 117 | 118 | lapply(seq_len(length(racers)), function(r) { 119 | reac <- racers[[r]] 120 | observeEvent( 121 | { 122 | reac() 123 | }, 124 | { 125 | req(reac()) 126 | r(reac) 127 | }, 128 | priority = p 129 | ) 130 | p <- p - 1 131 | }) 132 | 133 | reactive_racer <- reactive({ 134 | r() 135 | isolate({ 136 | o <- r() 137 | o() 138 | }) 139 | }) 140 | 141 | # Make this reactive aware of its tidymoduleness 142 | attr(reactive_racer, "tidymodules") <- TRUE 143 | attr(reactive_racer, "tidymodules_operation") <- "race" 144 | 145 | return(reactive_racer) 146 | } 147 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: md_document 3 | always_allow_html: yes 4 | --- 5 | 6 | 7 | 8 | ```{r setup, include = FALSE} 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "man/figures/README-", 13 | out.width = "100%" 14 | ) 15 | ``` 16 | 17 | ```{r, include=FALSE} 18 | htmltools::tagList(rmarkdown::html_dependency_font_awesome()) 19 | ``` 20 | 21 | # tidymodules 22 | 23 | [![build status](https://github.com/Novartis/tidymodules/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/Novartis/tidymodules/actions) 24 | [![lifecycle](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 25 | 26 | The `{tidymodules}` R package is built on top of shiny module using `{R6}` to provide a new object-oriented programming (OOP) approach for module development, new module interface using input/output ports and a set of tidy operators for handling cross-module communication. 27 | 28 | The main features of tidymodules and its comparison with conventional Shiny modules are presented in the table below. 29 | 30 | | Features | | tidymodules | Conventional modules | 31 | |----|----------------------|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| 32 | | [](articles/intro.html) | Programming style | - `{R6}` OOP *
- Semantic reference | Functional | 33 | | [](articles/namespace.html) | Namespace management | - automatic/generated
- ID based lookup
- Grouping | - manual management
- must match between ui and server | 34 | | [](articles/communication.html) | Module communication | - new module input/output port structure
- module ports linked via tidy operators
- automatic network diagram | - parameter passing via module server()
- challenging to manage for complex app | 35 | | [](articles/inheritance.html) | Inheritance | - class inheritance
- port inheritance for nested modules | NA | 36 | | [](articles/session.html) | Session management | - flexible user session management
- Caching of modules coming soon | NA | 37 | 38 | \* OOP = Object Oriented Programming 39 | 40 | ## Installation 41 | 42 | You can install the most recent version of `{tidymodules}` from [GitHub](https://github.com/Novartis/tidymodules) with: 43 | 44 | ``` r 45 | library(devtools) 46 | install_github("Novartis/tidymodules") 47 | ``` 48 | 49 | ## Examples 50 | 51 | You can quickly launch an example after installing the R package by running the following. 52 | ``` r 53 | tidymodules::showExamples(4) 54 | ``` 55 | Some examples have been deployed on shinyapp.io, such as: 56 | 57 | - Example 1: [Simple addition ](https://tidymodules.shinyapps.io/1_simple_addition/) 58 | - Example 2: [Linked scatter ](https://tidymodules.shinyapps.io/2_linked_scatter/) 59 | - Example 2: [Nested module ](https://tidymodules.shinyapps.io/3_nested_modules/) 60 | - Example 4: [Module communication ](https://tidymodules.shinyapps.io/4_communication/) 61 | 62 | ## Learning More 63 | 64 | Please review the [Get Started](https://opensource.nibr.com/tidymodules/articles/tidymodules.html) page for a high level introduction to `{tidymodules}` and its usage in developing Shiny apps. 65 | 66 | If you are interested to develop modules using `{tidymodules}`, we recommend reading the vignettes under "Articles". 67 | 68 | ## Code of Conduct 69 | 70 | Please note that the `{tidymodules}` is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms. 71 | 72 | ## Acknowledgment 73 | 74 | - The SCC team members @ NVS for their valuable feedbacks 75 | - 2019 Summer interns (Marzi, Stephen and Renan) for contributing to testing the framework and implementing the demo example 4 listed above. 76 | - Eric Nantz for accepting to introduce tidymodules in his [e-poster](https://rpodcast.shinyapps.io/highlights-shiny) @ rstudio::conf 2020 77 | 78 | ## Licence 79 | 80 | Copyright 2020 Novartis AG 81 | 82 | Licensed under the Apache License, Version 2.0 (the "License"); 83 | you may not use this file except in compliance with the License. 84 | You may obtain a copy of the License at 85 | 86 | http://www.apache.org/licenses/LICENSE-2.0 87 | 88 | Unless required by applicable law or agreed to in writing, software 89 | distributed under the License is distributed on an "AS IS" BASIS, 90 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | See the License for the specific language governing permissions and 92 | limitations under the License. 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # tidymodules 4 | 5 | [![build 6 | status](https://github.com/Novartis/tidymodules/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/Novartis/tidymodules/actions) 7 | [![lifecycle](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 8 | 9 | The `{tidymodules}` R package is built on top of shiny module using 10 | `{R6}` to provide a new object-oriented programming (OOP) approach for 11 | module development, new module interface using input/output ports and a 12 | set of tidy operators for handling cross-module communication. 13 | 14 | The main features of tidymodules and its comparison with conventional 15 | Shiny modules are presented in the table below. 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 53 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 69 | 70 | 71 | 72 |
FeaturestidymodulesConventional modules
Programming style- {R6} OOP *
- Semantic reference
Functional
Namespace management- automatic/generated
- ID based lookup
- Grouping
- manual management
- must match between ui and server
Module communication- new module input/output port structure
- module ports linked 52 | via tidy operators
- automatic network diagram
- parameter passing via module server()
- challenging to 54 | manage for complex app
Inheritance- class inheritance
- port inheritance for nested modules
NA
Session management- flexible user session management
- Caching of modules coming 68 | soon
NA
73 | 74 | \* OOP = Object Oriented Programming 75 | 76 | ## Installation 77 | 78 | You can install the most recent version of `{tidymodules}` from 79 | [GitHub](https://github.com/Novartis/tidymodules) with: 80 | 81 | library(devtools) 82 | install_github("Novartis/tidymodules") 83 | 84 | ## Examples 85 | 86 | You can quickly launch an example after installing the R package by 87 | running the following. 88 | 89 | tidymodules::showExamples(4) 90 | 91 | Some examples have been deployed on shinyapp.io, such as: 92 | 93 | - Example 1: [Simple addition 94 | ](https://tidymodules.shinyapps.io/1_simple_addition/) 95 | - Example 2: [Linked scatter 96 | ](https://tidymodules.shinyapps.io/2_linked_scatter/) 97 | - Example 2: [Nested module 98 | ](https://tidymodules.shinyapps.io/3_nested_modules/) 99 | - Example 4: [Module communication 100 | ](https://tidymodules.shinyapps.io/4_communication/) 101 | 102 | ## Learning More 103 | 104 | Please review the [Get 105 | Started](https://opensource.nibr.com/tidymodules/articles/tidymodules.html) 106 | page for a high level introduction to `{tidymodules}` and its usage in 107 | developing Shiny apps. 108 | 109 | If you are interested to develop modules using `{tidymodules}`, we 110 | recommend reading the vignettes under “Articles”. 111 | 112 | ## Code of Conduct 113 | 114 | Please note that the `{tidymodules}` is released with a [Contributor 115 | Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this project, 116 | you agree to abide by its terms. 117 | 118 | ## Acknowledgment 119 | 120 | - The SCC team members @ NVS for their valuable feedbacks 121 | - 2019 Summer interns (Marzi, Stephen and Renan) for contributing to 122 | testing the framework and implementing the demo example 4 listed 123 | above. 124 | - Eric Nantz for accepting to introduce tidymodules in his 125 | [e-poster](https://rpodcast.shinyapps.io/highlights-shiny) @ 126 | rstudio::conf 2020 127 | 128 | ## Licence 129 | 130 | Copyright 2020 Novartis AG 131 | 132 | Licensed under the Apache License, Version 2.0 (the "License"); 133 | you may not use this file except in compliance with the License. 134 | You may obtain a copy of the License at 135 | 136 | http://www.apache.org/licenses/LICENSE-2.0 137 | 138 | Unless required by applicable law or agreed to in writing, software 139 | distributed under the License is distributed on an "AS IS" BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 141 | See the License for the specific language governing permissions and 142 | limitations under the License. 143 | -------------------------------------------------------------------------------- /inst/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/inst/.DS_Store -------------------------------------------------------------------------------- /inst/rstudio/r.snippets: -------------------------------------------------------------------------------- 1 | snippet tm.mod.new 2 | #' 3 | #' ${1:MyModule} Module. 4 | #' 5 | #' @description 6 | #' This \href{https://opensource.nibr.com/tidymodules}{`{tm}`} module is a R6 class representing a ${1:MyModule}. 7 | #' 8 | #' @family tm 9 | #' 10 | #' @details 11 | #' More details about your module here. 12 | #' 13 | #' @export 14 | ${1:MyModule} <- R6::R6Class( 15 | classname = "${1:MyModule}", 16 | inherit = ${2:TidyModule}, 17 | public = list( 18 | #' @description 19 | #' Module's initialization function. 20 | #' @param ... options 21 | #' @return An instance of ${1:MyModule} 22 | initialize = function(...){ 23 | # Don't remove the line below 24 | super\$initialize(...) 25 | 26 | # Ports definition starts here... 27 | ${0} 28 | }, 29 | #' @description 30 | #' Module's ui function. 31 | #' @return HTML tags list. 32 | ui = function(){ 33 | # Module's representation starts here ... 34 | tagList() 35 | }, 36 | #' @description 37 | #' Module's server function. 38 | #' @param input Shiny input 39 | #' @param output Shiny output 40 | #' @param session Shiny session 41 | server = function(input, output, session){ 42 | # Don't remove the line below 43 | super\$server(input,output,session) 44 | 45 | # Module server logic starts here ... 46 | 47 | } 48 | ) 49 | ) 50 | snippet tm.port.define 51 | self\$definePort({ 52 | ${0} 53 | }) 54 | snippet tm.port.in 55 | self\$addInputPort( 56 | name = "${1:port_name}", 57 | description = "A clear description for this input port${0}", 58 | sample = data.frame(x = rnorm(10), y = rnorm(10)) 59 | ) 60 | snippet tm.port.out 61 | self\$addOutputPort( 62 | name = "${1:port_name}", 63 | description = "A clear description for this output port${0}", 64 | sample = data.frame(x = rnorm(10), y = rnorm(10)) 65 | ) 66 | snippet tm.port.assign 67 | self\$assignPort({ 68 | ${0} 69 | }) 70 | snippet tm.port.edges 71 | defineEdges({ 72 | ${0} 73 | }) 74 | -------------------------------------------------------------------------------- /inst/shiny/examples/1_simple_addition/Addition.R: -------------------------------------------------------------------------------- 1 | #' 2 | #' Addition module implemented with {tidymodules} 3 | #' 4 | #' @description 5 | #' Take a number as input and add it to the user selection. 6 | #' 7 | #' @details 8 | #' Should be initialized and injected in your application. 9 | #' Input port: 10 | #' - left 11 | #' Output port: 12 | #' - total 13 | #' 14 | #' @example 15 | #' 16 | #' a <- Addition$new() 17 | #' 18 | #' 19 | Addition <- R6::R6Class( 20 | "Addition", 21 | inherit = tidymodules::TidyModule, 22 | public = list( 23 | initialize = function(...) { 24 | # mandatory 25 | super$initialize(...) 26 | 27 | self$definePort({ 28 | self$addInputPort( 29 | name = "left", 30 | description = "input value to add to the user selected number", 31 | sample = 5 32 | ) 33 | 34 | self$addOutputPort( 35 | name = "total", 36 | description = "Sum of the two numbers", 37 | sample = 6 38 | ) 39 | }) 40 | }, 41 | #' @description 42 | #' Store's ui function. 43 | #' @return UI elements. 44 | ui = function() { 45 | div( 46 | style = paste0( 47 | "width:30%;", 48 | "background:lightgrey;", 49 | "border: solid;", 50 | "border-color: grey;", 51 | "padding: 20px;"), 52 | "Module input : ", 53 | textOutput(self$ns("left")), 54 | " + ", 55 | sliderInput( 56 | self$ns("right"), 57 | label = "Number to add", 58 | min = 1, 59 | max = 100, 60 | value = 1), 61 | " = ", 62 | textOutput(self$ns("total")) 63 | ) 64 | }, 65 | #' @description 66 | #' Store's server function. 67 | #' @param input Shiny input. 68 | #' @param output Shiny output 69 | #' @param session Shiny session 70 | server = function(input, output, session) { 71 | # Mandatory 72 | super$server(input, output, session) 73 | 74 | self$react$sum_numbers <- reactive({ 75 | req(input$right) 76 | left <- self$execInput(1) 77 | as.numeric(left) + as.numeric(input$right) 78 | }) 79 | 80 | output$left <- renderText({ 81 | self$execInput(1) 82 | }) 83 | 84 | output$total <- renderText({ 85 | self$react$sum_numbers() 86 | }) 87 | 88 | self$assignPort({ 89 | self$updateOutputPort( 90 | id = "total", 91 | output = self$react$sum_numbers 92 | ) 93 | }) 94 | } 95 | ) 96 | ) 97 | -------------------------------------------------------------------------------- /inst/shiny/examples/1_simple_addition/AdditionSM.R: -------------------------------------------------------------------------------- 1 | #' UI function of the Shiny Addition module 2 | #' 3 | #' @description 4 | #' Generated the UI elements of the Addition module. 5 | #' 6 | AdditionSM_UI <- function(ns_id) { 7 | ns <- NS(ns_id) 8 | 9 | div( 10 | style = paste0( 11 | "width:30%;", 12 | "background:lightgrey;", 13 | "border: solid;", 14 | "border-color: grey;", 15 | "padding: 20px;"), 16 | "Module input : ", 17 | textOutput(ns("left")), 18 | " + ", 19 | sliderInput( 20 | ns("right"), 21 | label = "Number to add", 22 | min = 1, 23 | max = 100, 24 | value = 1), 25 | " = ", 26 | textOutput(ns("total")) 27 | ) 28 | } 29 | 30 | #' Server function of the Shiny Addition module 31 | #' 32 | #' @description 33 | #' Server logic of the Shiny Addition module. 34 | #' Add an input number to a number selected by the user with a slider. 35 | #' 36 | AdditionSM_Server <- function(input, output, session, number) { 37 | sum_numbers <- reactive({ 38 | req(input$right) 39 | req(number) 40 | as.numeric(number()) + as.numeric(input$right) 41 | }) 42 | 43 | output$left <- renderText({ 44 | req(number) 45 | number() 46 | }) 47 | 48 | output$total <- renderText({ 49 | sum_numbers() 50 | }) 51 | 52 | return(sum_numbers) 53 | } 54 | -------------------------------------------------------------------------------- /inst/shiny/examples/1_simple_addition/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: tidymodules - Simple Addition 2 | Author: Mustapha Larbaoui 3 | License: Apache 4 | DisplayMode: Showcase 5 | Tags: module, ggplot 6 | Type: Shiny 7 | -------------------------------------------------------------------------------- /inst/shiny/examples/1_simple_addition/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | # SM -----------> Shiny module 4 | 5 | source( 6 | system.file( 7 | package = "tidymodules", 8 | "shiny/examples/1_simple_addition/AdditionSM.R") 9 | ) 10 | 11 | add_html_1 <- AdditionSM_UI("Addition_1") 12 | add_html_2 <- AdditionSM_UI("Addition_2") 13 | 14 | SM_UI <- shiny::basicPage( 15 | h2("Shiny module : Simple Addition"), 16 | sliderInput( 17 | "first_number", 18 | label = "Enter your first number", 19 | min = 1, 20 | max = 100, 21 | value = 1), 22 | br(), 23 | add_html_1, br(), 24 | add_html_2, br(), 25 | "Total: ", 26 | textOutput("total_result") 27 | ) 28 | 29 | SM_Server <- function(input, output, session) { 30 | first <- reactive({ 31 | req(input$first_number) 32 | }) 33 | 34 | second <- callModule(module = AdditionTM_Server, id = "Addition_1", first) 35 | result <- callModule(module = AdditionTM_Server, id = "Addition_2", second) 36 | 37 | output$total_result <- renderText({ 38 | result() 39 | }) 40 | } 41 | 42 | # TM -----------> tidymodules 43 | 44 | library(tidymodules) 45 | 46 | source( 47 | system.file( 48 | package = "tidymodules", 49 | "shiny/examples/1_simple_addition/Addition.R") 50 | ) 51 | 52 | 53 | Addition$new() 54 | Addition$new() 55 | 56 | TM_UI <- shiny::basicPage( 57 | h2("tidymodules : Simple Addition"), 58 | sliderInput( 59 | "first_number", 60 | label = "Enter your first number", 61 | min = 1, 62 | max = 100, 63 | value = 1), br(), 64 | mod(1)$ui(), br(), 65 | mod(2)$ui(), br(), 66 | "Total: ", textOutput("total_result") 67 | ) 68 | 69 | TM_Server <- function(input, output, session) { 70 | callModules() 71 | 72 | first <- reactive({ 73 | req(input$first_number) 74 | }) 75 | 76 | observe({ 77 | first %>1% mod(1) %1>1% mod(2) 78 | }) 79 | 80 | output$total_result <- renderText({ 81 | mod(2)$execOutput(1) 82 | }) 83 | } 84 | 85 | ### Start the app 86 | 87 | # shinyApp(SM_UI, SM_Server) 88 | shinyApp(TM_UI, TM_Server) 89 | -------------------------------------------------------------------------------- /inst/shiny/examples/2_linked_scatter/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: tidymodules - Linked scatter 2 | Author: Mustapha Larbaoui 3 | License: Apache 4 | DisplayMode: Showcase 5 | Tags: module, ggplot 6 | Type: Shiny 7 | -------------------------------------------------------------------------------- /inst/shiny/examples/2_linked_scatter/app.R: -------------------------------------------------------------------------------- 1 | library(tidymodules) 2 | 3 | check_and_load(c("ggplot2", "dplyr", "shinycssloaders")) 4 | 5 | 6 | source(system.file(package = "tidymodules", "shiny/examples/2_linked_scatter/linked_scatter.R")) 7 | 8 | lsObj1 <- LinkedScatter$new() 9 | lsObj2 <- LinkedScatter$new() 10 | lsObj3 <- LinkedScatter$new() 11 | lsObj4 <- LinkedScatter$new() 12 | store <- Store$new() 13 | 14 | ui <- fixedPage( 15 | h2("tidymodules : linked scatter"), 16 | tabsetPanel( 17 | type = "tabs", 18 | tabPanel( 19 | "App", 20 | br(), 21 | lsObj1$ui(), 22 | textOutput("summary1"), 23 | lsObj2$ui(), 24 | textOutput("summary2"), 25 | lsObj3$ui(), 26 | lsObj4$ui() 27 | ), 28 | tabPanel( 29 | "Store", 30 | br(), 31 | fluidRow( 32 | column(12, store$ui()) 33 | ) 34 | ) 35 | ) 36 | ) 37 | 38 | 39 | server <- function(input, output, session) { 40 | d <- reactive({ 41 | mpg 42 | }) 43 | 44 | o <- reactive({ 45 | list( 46 | left = c("cty", "hwy"), 47 | right = c("drv", "hwy") 48 | ) 49 | }) 50 | 51 | # callModules() 52 | lsObj1$callModule() 53 | lsObj2$callModule() 54 | lsObj3$callModule() 55 | lsObj4$callModule() 56 | store$callModule() 57 | 58 | observe({ 59 | o %>>2% lsObj1 %>>2% lsObj2 %>>2% lsObj3 %>>2% lsObj4 60 | d %>1% lsObj1 %2>1% lsObj2 %2>1% lsObj3 %2>1% lsObj4 61 | }) 62 | 63 | output$summary1 <- renderText({ 64 | df <- getMod(1)$getOutput(1) 65 | sprintf("%d observation(s) selected", nrow(dplyr::filter(df(), selected_))) 66 | }) 67 | 68 | output$summary2 <- renderText({ 69 | df <- getMod(2)$getOutput(1) 70 | sprintf("%d observation(s) selected", nrow(dplyr::filter(df(), selected_))) 71 | }) 72 | } 73 | 74 | shinyApp(ui, server) 75 | -------------------------------------------------------------------------------- /inst/shiny/examples/2_linked_scatter/linked_scatter.R: -------------------------------------------------------------------------------- 1 | 2 | input_sample <- head(mpg) 3 | 4 | LinkedScatter <- R6::R6Class( 5 | "LinkedScatter", 6 | inherit = tidymodules::TidyModule, 7 | public = list( 8 | initialize = function(...) { 9 | super$initialize(...) 10 | 11 | self$definePort({ 12 | self$addInputPort( 13 | name = "data", 14 | description = "Any rectangular data frame", 15 | sample = input_sample 16 | ) 17 | 18 | self$addInputPort( 19 | name = "option", 20 | description = "The module options are two vectors of (x,y) column names defining the left and right plot", 21 | sample = list( 22 | left = c("cty", "hwy"), 23 | right = c("drv", "hwy") 24 | ) 25 | ) 26 | 27 | self$addOutputPort( 28 | name = "selection", 29 | description = "The input data frame with a new column for selected rows", 30 | sample = input_sample 31 | ) 32 | 33 | 34 | self$addOutputPort( 35 | name = "selection_only", 36 | description = "Only the selected rows from ggplot brushing", 37 | sample = input_sample 38 | ) 39 | }) 40 | }, 41 | ui = function() { 42 | fluidRow( 43 | column( 44 | 6, 45 | shinycssloaders::withSpinner( 46 | plotOutput(self$ns("plot1"), brush = self$ns("brush")), 47 | type = 3, color.background = "white" 48 | ) 49 | ), 50 | column( 51 | 6, 52 | shinycssloaders::withSpinner( 53 | plotOutput(self$ns("plot2"), brush = self$ns("brush")), 54 | type = 3, color.background = "white" 55 | ) 56 | ) 57 | ) 58 | }, 59 | server = function(input, output, session, ...) { 60 | super$server(input, output, session, ...) 61 | 62 | args <- list(...) 63 | 64 | self$assignPort({ 65 | self$updateInputPort( 66 | id = "data", 67 | input = args$data 68 | ) 69 | 70 | self$updateInputPort( 71 | id = "option", 72 | input = args$options 73 | ) 74 | }) 75 | 76 | self$react$dataWithSelection <- reactive({ 77 | data <- self$execInput("data") 78 | req(nrow(data) != 0) 79 | brushedPoints(data, input$brush, allRows = TRUE) 80 | }) 81 | 82 | self$react$dataWithSelectionOnly <- reactive({ 83 | data <- self$execInput("data") 84 | req(nrow(data) != 0) 85 | brushedPoints(data, input$brush, allRows = FALSE) 86 | }) 87 | 88 | output$plot1 <- renderPlot({ 89 | o <- self$execInput("option") 90 | req(o) 91 | private$scatterPlot(self$react$dataWithSelection(), o$left) 92 | }) 93 | 94 | output$plot2 <- renderPlot({ 95 | o <- self$execInput("option") 96 | req(o) 97 | private$scatterPlot(self$react$dataWithSelection(), o$right) 98 | }) 99 | 100 | self$assignPort({ 101 | self$updateOutputPort( 102 | id = "selection", 103 | output = self$react$dataWithSelection 104 | ) 105 | 106 | self$updateOutputPort( 107 | id = "selection_only", 108 | output = self$react$dataWithSelectionOnly 109 | ) 110 | }) 111 | 112 | return(self$react$dataWithSelection) 113 | } 114 | ), 115 | private = list( 116 | scatterPlot = function(data, cols) { 117 | ggplot(data, aes_string(x = cols[1], y = cols[2])) + 118 | geom_point(aes(color = selected_)) + 119 | scale_color_manual(values = c("black", "#66D65C"), guide = "none") 120 | } 121 | ) 122 | ) 123 | -------------------------------------------------------------------------------- /inst/shiny/examples/3_nested_modules/ColorPicker.R: -------------------------------------------------------------------------------- 1 | 2 | ColorPicker <- R6::R6Class( 3 | "ColorPicker", 4 | inherit = Panel, 5 | public = list( 6 | initialize = function(...) { 7 | super$initialize(...) 8 | 9 | self$definePort({ 10 | self$addOutputPort( 11 | name = "scheme", 12 | description = "string defining color scheme", 13 | sample = list(scheme = "Dark2", reverse = FALSE, transparency = 1) 14 | ) 15 | }) 16 | }, 17 | ui = function(label = "Coloring") { 18 | super$ui( 19 | status = "primary", 20 | tagList( 21 | selectInput(self$ns("scheme"), label = label, choices = c("Dark2" = "Dark2", "Set1" = "Set1", "Set2" = "Set2"), selected = "Dark2"), 22 | checkboxInput(self$ns("reverse"), label = "Reverse scheme"), 23 | sliderInput(self$ns("transparency"), label = "Transparency", min = 0, max = 1, value = 1) 24 | ) 25 | ) 26 | }, 27 | server = function(input, output, session) { 28 | super$server(input, output, session) 29 | 30 | self$obser$log <- observe({ 31 | msg <- paste("Color scheme was selected", input$scheme) 32 | cat(msg, "\n") 33 | }) 34 | 35 | self$react$input_values <- reactive({ 36 | reactiveValuesToList(input) 37 | }) 38 | 39 | self$assignPort({ 40 | self$updateOutputPort( 41 | id = "scheme", 42 | output = self$react$input_values 43 | ) 44 | }) 45 | 46 | return(input) 47 | } 48 | ) 49 | ) 50 | -------------------------------------------------------------------------------- /inst/shiny/examples/3_nested_modules/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: tidymodules - Nesting modules 2 | Author: Mustapha Larbaoui 3 | License: Apache 4 | DisplayMode: Showcase 5 | Tags: module, ggplot 6 | Type: Shiny 7 | -------------------------------------------------------------------------------- /inst/shiny/examples/3_nested_modules/Kmeans.R: -------------------------------------------------------------------------------- 1 | 2 | Kmeans <- R6::R6Class( 3 | "Kmeans", 4 | inherit = tidymodules::TidyModule, 5 | public = list( 6 | # Example with nested module in list 7 | # nested_mods = list(CP = NULL), 8 | nested_mod = NULL, 9 | initialize = function(...) { 10 | super$initialize(...) 11 | # self$nested_mods$CP <- ColorPicker$new("CP") 12 | self$nested_mod <- ColorPicker$new("CP") 13 | 14 | km_sample <- kmeans(matrix(rnorm(100), ncol = 2), 3) 15 | self$definePort({ 16 | self$addOutputPort( 17 | name = "km", 18 | description = "object of class 'kmeans'", 19 | sample = km_sample 20 | ) 21 | }) 22 | }, 23 | ui = function(label) { 24 | # col <- self$nested_mods$CP$ui("Color scheme") 25 | col <- self$nested_mod$ui("Color scheme") 26 | tags <- tagList( 27 | selectInput(self$ns("xcol"), "X Variable", names(iris)), 28 | selectInput(self$ns("ycol"), "Y Variable", names(iris), selected = names(iris)[[2]]), 29 | numericInput(self$ns("clusters"), "Cluster count", 3, min = 1, max = 9) 30 | ) 31 | 32 | fluidRow( 33 | column(4, tags, col), 34 | column(8, plotOutput(self$ns("plot1"))) 35 | ) 36 | }, 37 | server = function(input, output, session) { 38 | super$server(input, output, session) 39 | 40 | # self$nested_mods$CP$callModule() 41 | self$nested_mod$callModule() 42 | 43 | # Combine the selected variables into a new data frame 44 | self$react$selectedData <- reactive({ 45 | iris[, c(input$xcol, input$ycol)] 46 | }) 47 | 48 | self$react$clusters <- reactive({ 49 | kmeans(self$react$selectedData(), input$clusters) 50 | }) 51 | 52 | output$plot1 <- renderPlot({ 53 | # Get ColorPicker output, default to first output port 54 | # cp <- self$nested_mods$CP$getOutput() 55 | cp <- self$nested_mod$execOutput() 56 | cols <- brewer.pal(input$clusters, cp$scheme) 57 | cols <- adjustcolor(cols, alpha.f = cp$transparency) 58 | if (cp$reverse) { 59 | cols <- rev(cols) 60 | } 61 | cols <- cols[self$react$clusters()$cluster] 62 | par(mar = c(5.1, 4.1, 0, 1)) 63 | plot(self$react$selectedData(), 64 | col = cols, 65 | pch = 20, cex = 3 66 | ) 67 | points(self$react$clusters()$centers, pch = 4, cex = 4, lwd = 4) 68 | }) 69 | 70 | self$assignPort({ 71 | self$updateOutputPort( 72 | id = "km", 73 | output = self$react$clusters 74 | ) 75 | }) 76 | 77 | return(self$react$clusters) 78 | } 79 | ) 80 | ) 81 | -------------------------------------------------------------------------------- /inst/shiny/examples/3_nested_modules/app.R: -------------------------------------------------------------------------------- 1 | library(tidymodules) 2 | 3 | check_and_load("RColorBrewer") 4 | 5 | source(system.file(package = "tidymodules", "shiny/examples/4_communication/module/Panel.R")) 6 | source(system.file(package = "tidymodules", "shiny/examples/3_nested_modules/Kmeans.R")) 7 | source(system.file(package = "tidymodules", "shiny/examples/3_nested_modules/ColorPicker.R")) 8 | 9 | km_module <- Kmeans$new() 10 | store <- Store$new() 11 | 12 | ui <- fixedPage( 13 | h2("tidymodules : Nested module example"), 14 | tabsetPanel( 15 | type = "tabs", 16 | tabPanel( 17 | "App", 18 | br(), 19 | km_module$ui() 20 | ), 21 | tabPanel( 22 | "Store", 23 | br(), 24 | fluidRow( 25 | column(12, store$ui()) 26 | ) 27 | ) 28 | ) 29 | ) 30 | 31 | server <- function(input, output, session) { 32 | store$callModule() 33 | km_module$callModule() 34 | } 35 | 36 | shinyApp(ui, server) 37 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: tidymodules - Orchestrating module communication 2 | Author: Mustapha Larbaoui 3 | License: Apache 4 | DisplayMode: Showcase 5 | Tags: module, ggplot 6 | Type: Shiny 7 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/app.R: -------------------------------------------------------------------------------- 1 | library(tidymodules) 2 | 3 | check_and_load(c("shinyWidgets", "ggplot2", "plotly", "DT")) 4 | 5 | # Load modules from ./module folder 6 | app_dir <- system.file(package = "tidymodules", "shiny/examples/4_communication") 7 | 8 | sapply( 9 | list.files(file.path(app_dir, "module"), include.dirs = F, pattern = ".R", ignore.case = T), 10 | function(f) { 11 | cat(paste0("Sourcing file :", f, "\n")) 12 | source(file.path(app_dir, "module", f)) 13 | } 14 | ) 15 | 16 | Store$new() 17 | Panel$new() 18 | DatasetSelector$new("Marzie") 19 | DataFilter$new("Stefan") 20 | ColSelector$new("Renan") 21 | PlotGenerator$new("Doug") 22 | 23 | ui <- shiny::fluidPage( 24 | h2("tidymodules : Communication is the key to success!"), 25 | tags$br(), 26 | tabsetPanel( 27 | type = "tabs", 28 | tabPanel( 29 | "App", 30 | br(), 31 | fluidRow( 32 | column(12, mod("Marzie")$ui()) 33 | ), 34 | fluidRow( 35 | column(4, mod("Renan")$ui()), 36 | column(8, mod("Stefan")$ui()) 37 | ), 38 | fluidRow( 39 | column(12, mod("Doug")$ui()) 40 | ) 41 | ), 42 | tabPanel( 43 | "Help", 44 | tabsetPanel( 45 | type = "tabs", 46 | tabPanel( 47 | "ModStore", 48 | br(), 49 | fluidRow( 50 | column( 51 | 12, 52 | mod(2)$ui( 53 | status = "warning", 54 | mod(1)$ui() 55 | ) 56 | ) 57 | ) 58 | ), 59 | tabPanel("ERD", img(src = "ERD.svg", align = "center")), 60 | tabPanel("Ports", img(src = "ports.svg", align = "center")) 61 | ) 62 | ) 63 | ) 64 | ) 65 | 66 | server <- function(input, output, session) { 67 | # Add modules server logic 68 | callModules() 69 | # Configure modules communication by connecting ports 70 | defineEdges({ 71 | # dataset selector provides data to 72 | # column mapper and row filter modules 73 | oport("Marzie", "dataset") %->>% 74 | iport("Renan", "data") %->% 75 | iport("Stefan", "data") 76 | 77 | # the mappings are then used by the plot generator 78 | mod("Renan") %1>1% mod("Doug") 79 | # plot generator also takes raw and filtered data as input 80 | # by combining the two output ports in a named reactive list 81 | combine_ports( 82 | raw = oport("Marzie", "dataset"), # output 1 of data selector module 83 | filter = oport("Stefan", "filtered") # output 2 of data filter module 84 | ) %->% iport("Doug", "tables") 85 | }) 86 | } 87 | 88 | 89 | shinyApp(ui, server) 90 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_BasePlot.R: -------------------------------------------------------------------------------- 1 | module/BasePlot.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_BoxPlot.R: -------------------------------------------------------------------------------- 1 | module/BoxPlot.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_ClosablePanel.R: -------------------------------------------------------------------------------- 1 | module/ClosablePanel.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_ColSelector.R: -------------------------------------------------------------------------------- 1 | module/ColSelector.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_DataFilter.R: -------------------------------------------------------------------------------- 1 | module/DataFilter.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_DatasetSelector.R: -------------------------------------------------------------------------------- 1 | module/DatasetSelector.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_LinePlot.R: -------------------------------------------------------------------------------- 1 | module/LinePlot.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_Panel.R: -------------------------------------------------------------------------------- 1 | module/Panel.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_PlotGenerator.R: -------------------------------------------------------------------------------- 1 | module/PlotGenerator.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_Scatter3DPlot.R: -------------------------------------------------------------------------------- 1 | module/Scatter3DPlot.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/mod_ScatterPlot.R: -------------------------------------------------------------------------------- 1 | module/ScatterPlot.R -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/BasePlot.R: -------------------------------------------------------------------------------- 1 | 2 | BasePlot <- R6::R6Class( 3 | "BasePlot", 4 | inherit = ClosablePanel, 5 | public = list( 6 | initialize = function(...) { 7 | # Mandatory 8 | super$initialize(...) 9 | 10 | # Ports definition starts here 11 | self$definePort({ 12 | # At least one input 13 | self$addInputPort( 14 | name = "data", 15 | description = "Any rectangular data frame", 16 | sample = head(mtcars) 17 | ) 18 | 19 | self$addInputPort( 20 | name = "mapping", 21 | description = "vector of column names for the mapping", 22 | sample = colnames(mtcars)[1:3] 23 | ) 24 | 25 | 26 | # Add an output port (optional) 27 | self$addOutputPort( 28 | name = "selection", 29 | description = "data points selected from brushing", 30 | sample = mtcars[5:6, ] 31 | ) 32 | }) 33 | }, 34 | ui = function(outputFunc = NULL, header = NULL) { 35 | super$ui( 36 | fluidRow( 37 | ifelse(is.null(outputFunc), 38 | tagList( 39 | plotOutput(self$ns("plot"), 40 | brush = self$ns("brush") 41 | ) 42 | ), 43 | tagList( 44 | outputFunc(self$ns("plot")), 45 | self$ns("brush") 46 | ) 47 | ) 48 | ), 49 | header = header 50 | ) 51 | }, 52 | server = function(input, output, session) { 53 | # Mandatory 54 | super$server(input, output, session) 55 | 56 | self$react$selection <- reactive({ 57 | d <- self$getInput("data") 58 | data <- d() 59 | brushedPoints(data, input$brush, allRows = TRUE) 60 | }) 61 | 62 | self$react$selectionOnly <- reactive({ 63 | d <- self$getInput("data") 64 | data <- d() 65 | brushedPoints(data, input$brush, allRows = FALSE) 66 | }) 67 | 68 | output$plot <- self$renderPlot( 69 | self$react$selection, 70 | self$getInput("mapping") 71 | ) 72 | 73 | # Ports assignment starts here 74 | self$assignPort({ 75 | self$updateOutputPort( 76 | id = "selection", 77 | output = self$react$selectionOnly 78 | ) 79 | }) 80 | 81 | return(self$react$selectionOnly) 82 | }, 83 | renderPlot = function(data, cols) { 84 | renderPlot({ 85 | self$chart(data, cols)() 86 | }) 87 | }, 88 | aes = function(cols) { 89 | shiny::req(length(cols) > 1) 90 | x <- cols[1] 91 | y <- cols[2] 92 | g <- cols[3] 93 | 94 | aes <- aes_string(x = x, y = y, group = g, color = g) 95 | if (is.na(g)) { 96 | aes <- aes_string(x = x, y = y) 97 | } 98 | 99 | return(aes) 100 | }, 101 | chart = function(...) { 102 | warning("charting function need to be implemented in a child class, SHOULD RETURN A REACTIVE FUNCTION!") 103 | } 104 | ) 105 | ) 106 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/BoxPlot.R: -------------------------------------------------------------------------------- 1 | 2 | BoxPlot <- R6::R6Class( 3 | "BoxPlot", 4 | inherit = BasePlot, 5 | public = list( 6 | chart = function(data, cols) { 7 | return(reactive({ 8 | aes <- self$aes(cols()) 9 | ggplot(data(), aes) + 10 | geom_boxplot() 11 | })) 12 | } 13 | ) 14 | ) 15 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/ClosablePanel.R: -------------------------------------------------------------------------------- 1 | ClosablePanel <- R6::R6Class( 2 | "ClosablePanel", 3 | inherit = Panel, 4 | public = list( 5 | # TODO : Change the way we close closable panel 6 | # Add server logic to remove the module 7 | ui = function(..., status = "default", header = NULL) { 8 | header <- tagList( 9 | header, 10 | tags$span( 11 | style = "float: right;cursor: pointer;", 12 | id = self$ns("close_span"), 13 | actionButton(self$ns("close"),icon = shiny::icon("times", "fa-sm", verify_fa = FALSE), label = "") 14 | ) 15 | ) 16 | content <- tagList( 17 | ..., 18 | tags$head( 19 | tags$script( 20 | type = "text/javascript", 21 | paste0( 22 | "setTimeout(function(){ $('#", self$ns("close_span"), "').click(function(){ 23 | $('#", self$module_ns, "').parent().parent().remove() 24 | }) }, 500);" 25 | ) 26 | ) 27 | ) 28 | ) 29 | 30 | super$ui( 31 | status = status, 32 | header = header, 33 | content 34 | ) 35 | }, 36 | # server logic 37 | server = function(input,output,session) { 38 | # Mandatory 39 | super$server(input, output, session) 40 | 41 | self$obser$close <- observe({ 42 | req(input$close) 43 | self$destroy() 44 | }) 45 | } 46 | ) 47 | ) 48 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/ColSelector.R: -------------------------------------------------------------------------------- 1 | 2 | ColSelector <- R6::R6Class( 3 | "ColSelector", 4 | inherit = Panel, 5 | public = list( 6 | initialize = function(...) { 7 | super$initialize(...) 8 | self$definePort({ 9 | self$addInputPort( 10 | name = "data", 11 | description = "Any data table", 12 | sample = data.frame(a = 1:4, b = 1:4) 13 | ) 14 | self$addOutputPort( 15 | name = "mapping", 16 | description = "dynamic list of column selections", 17 | sample = list( 18 | mapping1 = c("col1", "col2", "col3"), 19 | mapping2 = c("col2", "col4") 20 | ) 21 | ) 22 | }) 23 | }, 24 | ui = function() { 25 | super$ui( 26 | status = "primary", 27 | shiny::actionButton(self$ns("add"), label = "Add mapping", icon = icon("plus")), 28 | br(), 29 | tags$div(id = self$ns("uio_selector")) 30 | ) 31 | }, 32 | server = function(input, output, session) { 33 | # Mandatory 34 | super$server(input, output, session) 35 | 36 | self$react$cols <- reactiveValues( 37 | current = NULL, 38 | names = NULL, 39 | mapping = list() 40 | ) 41 | 42 | self$obser$reset <- observe({ 43 | dataPort <- self$getInput("data") 44 | req(dataPort) 45 | d <- dataPort() 46 | self$react$cols$mapping <- list() 47 | req(!is.null(d)) 48 | self$react$cols$names <- colnames(d) 49 | self$react$cols$current <- 1 50 | shiny::removeUI( 51 | selector = paste0("#", self$ns("uio_selector div")), 52 | multiple = TRUE 53 | ) 54 | }) 55 | 56 | self$obser$add <- observeEvent(input$add, { 57 | d <- self$getInput("data")() 58 | req(!is.null(d)) 59 | insertUI( 60 | selector = paste0("#", self$ns("uio_selector")), 61 | where = "beforeEnd", 62 | session = session, 63 | ui = tagList( 64 | selectizeInput( 65 | inputId = self$ns(paste0("mapping-", self$react$cols$current)), 66 | label = paste0("mapping-", self$react$cols$current), 67 | multiple = T, 68 | choices = self$react$cols$names, 69 | options = list(maxItems = 4L) 70 | ) 71 | ) 72 | ) 73 | 74 | self$react$cols$mapping[[paste0("mapping-", self$react$cols$current)]] <- c("") 75 | self$react$cols$current <- self$react$cols$current + 1 76 | }) 77 | 78 | self$obser$mappingKey <- observe({ 79 | key <- paste0("mapping-", self$react$cols$current - 1) 80 | observeEvent(input[[key]], { 81 | self$react$cols$mapping[[key]] <- input[[key]] 82 | }) 83 | }) 84 | 85 | # Server logic 86 | # Ports assignment starts here 87 | self$assignPort({ 88 | self$updateOutputPort( 89 | id = "mapping", 90 | output = reactive({ 91 | self$react$cols$mapping 92 | }) 93 | ) 94 | }) 95 | 96 | return(reactive(self$react$cols$mapping)) 97 | } 98 | ), 99 | private = list( 100 | # any private functions? 101 | ) 102 | ) 103 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/DataFilter.R: -------------------------------------------------------------------------------- 1 | 2 | DataFilter <- R6::R6Class( 3 | "DataFilter", 4 | inherit = Panel, 5 | public = list( 6 | initialize = function(...) { 7 | # Mandatory 8 | super$initialize(...) 9 | 10 | # Ports definition starts here 11 | self$definePort({ 12 | # At least one input 13 | self$addInputPort( 14 | name = "data", 15 | description = "Any rectangular data frame", 16 | sample = head(cars) 17 | ) 18 | 19 | self$addOutputPort( 20 | name = "selected", 21 | description = "The original data frame with a new boolean column `selected` that indicates whether the new is selected.", 22 | sample = data.frame(a = c(1, 2), selected = c(TRUE, FALSE)) 23 | ) 24 | 25 | self$addOutputPort( 26 | name = "filtered", 27 | description = "The data frame containing only the selected rows.", 28 | sample = mtcars[c(1, 2, 5), ] 29 | ) 30 | }) 31 | }, 32 | ui = function() { 33 | super$ui( 34 | status = "primary", 35 | tags$div( 36 | style = "overflow:auto", 37 | DT::dataTableOutput(self$ns("dtOutput")) 38 | ) 39 | ) 40 | }, 41 | server = function(input, output, session) { 42 | # Mandatory 43 | super$server(input, output, session) 44 | 45 | # Server logic 46 | self$react$modOut <- reactive({ 47 | d <- self$getInput("data") 48 | req(!is.null(d)) 49 | }) 50 | 51 | self$react$dtReturn <- reactive({ 52 | d <- self$getInput("data") 53 | req(d) 54 | DT::datatable(d()) 55 | }) 56 | 57 | DTproxy <- DT::dataTableProxy("dtOutput", session = session) 58 | self$obser$clearRows <- observeEvent(input$clearRows, { 59 | DT::selectRows(DTproxy, NULL) 60 | }) 61 | 62 | # Get a boolean vector indicating whether a row is selected 63 | self$react$selectedRowsBoolean <- reactive({ 64 | d <- self$getInput("data") 65 | req(input$dtOutput_rows_selected, d) 66 | 67 | # Get all the row indices 68 | selected <- seq_len(nrow(d())) %in% input$dtOutput_rows_selected 69 | 70 | selected 71 | }) 72 | 73 | # The data frame with only selected rows 74 | self$react$selectedRows <- reactive({ 75 | d <- self$getInput("data") 76 | d()[self$react$selectedRowsBoolean(), ] 77 | }) 78 | 79 | # The data frame with a `selected` boolean column 80 | self$react$dataSelected <- reactive({ 81 | d <- self$getInput("data") 82 | 83 | cbind(d(), selected = self$react$selectedRowsBoolean()) 84 | }) 85 | 86 | output$dtOutput <- DT::renderDataTable({ 87 | self$react$dtReturn() 88 | }) 89 | 90 | # Ports assignment starts here 91 | self$assignPort({ 92 | self$updateOutputPort( 93 | id = "selected", 94 | output = self$react$dataSelected 95 | ) 96 | 97 | self$updateOutputPort( 98 | id = "filtered", 99 | output = self$react$selectedRows 100 | ) 101 | }) 102 | } 103 | ), 104 | private = list( 105 | # any private functions? 106 | ) 107 | ) 108 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/DatasetSelector.R: -------------------------------------------------------------------------------- 1 | 2 | DatasetSelector <- R6::R6Class( 3 | classname = "DatasetSelector", 4 | inherit = Panel, 5 | public = list( 6 | initialize = function(...) { 7 | # Mandatory 8 | super$initialize(...) 9 | 10 | # Ports definition starts here 11 | self$definePort({ 12 | # Add an output port (optional) 13 | self$addOutputPort( 14 | name = "dataset", 15 | description = "the selected dataset from environment by data()", 16 | sample = head(mtcars) 17 | ) 18 | }) 19 | }, 20 | ui = function() { 21 | super$ui( 22 | status = "primary", 23 | fluidRow( 24 | column(12, uiOutput(outputId = self$ns("selectDatasetUI"))), 25 | column(12, 26 | style = "overflow:auto", 27 | tableOutput( 28 | outputId = self$ns("selectedDataset") 29 | ) 30 | ) 31 | ) 32 | ) 33 | }, 34 | server = function(input, output, session) { 35 | # Mandatory 36 | super$server(input, output, session) 37 | 38 | output$selectDatasetUI <- renderUI({ 39 | allDatasets <- as.data.frame(data()$results) 40 | allDatasets <- as.character(allDatasets$Item) 41 | selectInput( 42 | inputId = self$ns("selectData"), 43 | label = "Select dataset", 44 | choices = allDatasets, 45 | selected = allDatasets[1], 46 | selectize = TRUE 47 | ) 48 | }) 49 | output$selectedDataset <- renderTable({ 50 | allDatasets <- as.data.frame(data()$results) 51 | dataset <- self$react$getData() 52 | if (is(dataset, "data.frame")) { 53 | allDatasets[which(allDatasets$Item == input$selectData), ] 54 | } else { 55 | "Not a table!" 56 | } 57 | }) 58 | 59 | self$react$getData <- reactive({ 60 | tryCatch( 61 | { 62 | dataName <- input$selectData 63 | req(!is.null(dataName)) 64 | print(dataName) 65 | dataset <- get(dataName) 66 | }, 67 | error = function(e) { 68 | NULL 69 | } 70 | ) 71 | }) 72 | 73 | # Server logic 74 | self$react$modOut <- reactive({ 75 | dataset <- self$react$getData() 76 | req(is(dataset, "data.frame")) 77 | print(dim(dataset)) 78 | return(dataset) 79 | }) 80 | 81 | # Ports assignment starts here 82 | self$assignPort({ 83 | self$updateOutputPort( 84 | id = "dataset", 85 | output = self$react$modOut 86 | ) 87 | }) 88 | 89 | return(self$react$modOut) 90 | } 91 | ), 92 | private = list( 93 | # any private functions? 94 | ) 95 | ) 96 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/LinePlot.R: -------------------------------------------------------------------------------- 1 | 2 | LinePlot <- R6::R6Class( 3 | "LinePlot", 4 | inherit = BasePlot, 5 | public = list( 6 | chart = function(data, cols) { 7 | return(reactive({ 8 | aes <- self$aes(cols()) 9 | ggplot(data = data(), aes) + 10 | geom_point() + 11 | geom_line() 12 | })) 13 | } 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/Panel.R: -------------------------------------------------------------------------------- 1 | Panel <- R6::R6Class( 2 | "Panel", 3 | inherit = tidymodules::TidyModule, 4 | public = list( 5 | ui = function(..., status = "default", header = NULL) { 6 | shinyWidgets::panel( 7 | id = self$module_ns, 8 | heading = tagList( 9 | shiny::tags$h3( 10 | class = "panel-title", 11 | tagList( 12 | paste0("#", self$module_ns, " - ", class(self)[1]), 13 | header 14 | ) 15 | ) 16 | ), 17 | status = status, 18 | tagList(...) 19 | ) 20 | } 21 | ) 22 | ) 23 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/PlotGenerator.R: -------------------------------------------------------------------------------- 1 | 2 | PlotGenerator <- R6::R6Class( 3 | classname = "PlotGenerator", 4 | inherit = Panel, 5 | public = list( 6 | plots = list( 7 | line = c("LinePlot", "Xiao"), 8 | box = c("BoxPlot", "David"), 9 | scatter = c("ScatterPlot", "Mustapha"), 10 | scatter3D = c("Scatter3DPlot", "Mustapha") 11 | ), 12 | initialize = function(...) { 13 | # Mandatory 14 | super$initialize(...) 15 | 16 | # Ports definition starts here 17 | self$definePort({ 18 | # At least one input 19 | self$addInputPort( 20 | name = "mappings", 21 | description = "Named list of character vectors defining mapping of columns", 22 | sample = list( 23 | map1 = c("col1", "col2"), 24 | map2 = c("col4", "col7") 25 | ) 26 | ) 27 | 28 | self$addInputPort( 29 | name = "tables", 30 | description = "Named list of data tables forwarded to the plotting modules", 31 | sample = list( 32 | raw = data.frame(id = 1:10, val = 11:20), 33 | filtered = data.frame(id = 5:10, val = 15:20) 34 | ) 35 | ) 36 | 37 | # Add an output port (optional) 38 | self$addOutputPort( 39 | name = "selection", 40 | description = "Data table with user selected data points", 41 | sample = head(mtcars) 42 | ) 43 | }) 44 | }, 45 | ui = function() { 46 | controls <- tagList( 47 | tags$br(), tags$br(), 48 | div(style = "display: inline-block;vertical-align:middle; width: 150px;", shiny::selectInput(self$ns("data"), label = "data", choices = c())), 49 | div(style = "display: inline-block;vertical-align:middle; width: 150px;", shiny::selectInput(self$ns("mapping"), label = "mapping", choices = c())), 50 | div(style = "display: inline-block;vertical-align:middle; width: 150px;", shiny::selectInput(self$ns("plot"), label = "plot type", choices = names(self$plots))), 51 | div(style = "display: inline-block;vertical-align:middle; width: 130px;", shiny::checkboxInput(self$ns("disconect_data"), label = "disconnect data", value = TRUE)), 52 | div(style = "display: inline-block;vertical-align:middle; width: 130px;", shiny::checkboxInput(self$ns("disconect_mapping"), label = "disconnect mapping", value = TRUE)), 53 | div(style = "display: inline-block;vertical-align:middle; width: 150px;", shiny::actionButton(self$ns("add"), label = NULL, icon = icon("plus"))) 54 | ) 55 | 56 | super$ui( 57 | status = "success", 58 | header = controls, 59 | fluidRow( 60 | id = self$ns("plotContainer") 61 | ) 62 | ) 63 | }, 64 | server = function(input, output, session) { 65 | # Mandatory 66 | super$server(input, output, session) 67 | 68 | self$obser$updateData <- observe({ 69 | t <- self$getInput("tables") 70 | shiny::req(t) 71 | shiny::updateSelectInput(session, "data", choices = names(t), selected = "raw") 72 | }) 73 | 74 | self$obser$updateMapping <- observe({ 75 | mPort <- self$getInput("mappings") 76 | req(mPort) 77 | options <- names(mPort()) 78 | if (is.null(options)) { 79 | options <- list() 80 | } 81 | shiny::updateSelectInput(session, "mapping", choices = options) 82 | }) 83 | 84 | self$obser$logAddClick <- observe({ 85 | cat(paste0("\n\n\n", input$add, "\n\n\n")) 86 | }) 87 | 88 | self$obser$add <- observeEvent(input$add, { 89 | reactive_mapping <- self$getInput("mappings") 90 | reactive_table <- self$getInput("tables") 91 | 92 | selected_mapping <- input$mapping 93 | selected_data <- input$data 94 | selected_plot <- input$plot 95 | 96 | req(reactive_mapping) 97 | req(reactive_table) 98 | 99 | current_mapping <- reactive_mapping()[[selected_mapping]] 100 | req(length(current_mapping) != 0) 101 | 102 | current_data <- reactive_table[[selected_data]]() 103 | req(!is.null(current_data)) 104 | 105 | mod <- self$plots[[selected_plot]][1] 106 | author <- self$plots[[selected_plot]][2] 107 | mod <- eval(parse(text = mod)) 108 | 109 | # dynamically create the selected charting module 110 | # Note that these are nested modules (module namespace includes Doug_) 111 | # Also note that the parent module "self" need to be specified when dynamically 112 | # creating a nested module 113 | mod <- mod$new(paste0(author, "_", input$add), parent = self) 114 | 115 | # feed a static version of the data/mapping to the module 116 | if (input$disconect_data) { 117 | reactive(current_data) %->% mod$iport("data") 118 | } else { 119 | reactive_table[[selected_data]] %->% mod$iport("data") 120 | } 121 | 122 | if (input$disconect_mapping) { 123 | reactive(current_mapping) %->% mod$iport("mapping") 124 | } else { 125 | reactive({ 126 | reactive_mapping()[[selected_mapping]] 127 | }) %->% mod$iport("mapping") 128 | } 129 | 130 | # now call the module 131 | mod$callModule() 132 | 133 | mflag <- tagList(shiny::icon("bolt"), " ") 134 | if (input$disconect_mapping) { 135 | mflag <- "" 136 | } 137 | 138 | dflag <- tagList(shiny::icon("bolt"), " ") 139 | if (input$disconect_data) { 140 | dflag <- "" 141 | } 142 | 143 | header <- tagList(" - ", dflag, selected_data, " - ", mflag, selected_mapping) 144 | 145 | # render the module 146 | insertUI( 147 | selector = paste0("#", self$ns("plotContainer")), 148 | where = "afterBegin", 149 | immediate = TRUE, 150 | session = session, 151 | ui = tagList( 152 | column(6, mod$ui(header = header)) 153 | ) 154 | ) 155 | }) 156 | 157 | # Ports assignment starts here 158 | self$assignPort({ 159 | # TODO: Add output port, e.g. brushing selection ? 160 | # self$updateOutputPort( 161 | # id = "dataset", 162 | # output = modOut) 163 | }) 164 | 165 | return({}) 166 | } 167 | ) 168 | ) 169 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/Scatter3DPlot.R: -------------------------------------------------------------------------------- 1 | 2 | Scatter3DPlot <- R6::R6Class( 3 | "Scatter3DPlot", 4 | inherit = BasePlot, 5 | public = list( 6 | ui = function(header = NULL) { 7 | super$ui(plotlyOutput, header) 8 | }, 9 | renderPlot = function(d, cols) { 10 | shiny::req(length(cols()) > 3) 11 | renderPlotly({ 12 | d <- as.data.frame(d()) 13 | cols <- cols() 14 | plot_ly(x = d[, cols[1]], y = d[, cols[2]], z = d[, cols[3]], color = d[, cols[4]]) %>% 15 | layout(scene = list( 16 | xaxis = list(title = cols[1]), 17 | yaxis = list(title = cols[2]), 18 | zaxis = list(title = cols[3]) 19 | )) 20 | }) 21 | } 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/module/ScatterPlot.R: -------------------------------------------------------------------------------- 1 | 2 | ScatterPlot <- R6::R6Class( 3 | "ScatterPlot", 4 | inherit = BasePlot, 5 | public = list( 6 | chart = function(data, cols) { 7 | return(reactive({ 8 | aes <- self$aes(cols()) 9 | ggplot(data(), aes) + 10 | geom_point(aes(color = selected_)) + 11 | scale_color_manual(values = c("black", "#66D65C"), guide = "none") 12 | })) 13 | } 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/www/ERD.svg: -------------------------------------------------------------------------------- 1 | ../model/ERD.svg -------------------------------------------------------------------------------- /inst/shiny/examples/4_communication/www/ports.svg: -------------------------------------------------------------------------------- 1 | ../model/ports.svg -------------------------------------------------------------------------------- /inst/shiny/examples/5_counter/Counter.R: -------------------------------------------------------------------------------- 1 | #' 2 | #' Counter Module. 3 | #' 4 | #' @description 5 | #' This \href{https://opensource.nibr.com/tidymodules}{`{tm}`} module is a R6 class representing a Counter. 6 | #' 7 | #' @family tm 8 | #' 9 | #' @details 10 | #' More details about your module here. 11 | #' 12 | #' 13 | #' @import tidymodules 14 | #' @noRd 15 | Counter <- R6::R6Class( 16 | classname = "Counter", 17 | inherit = TidyModule, 18 | public = list( 19 | #' @description 20 | #' Module's initialization function. 21 | #' @param ... options 22 | #' @return An instance of Counter 23 | initialize = function(...) { 24 | # Don't remove the line below 25 | super$initialize(...) 26 | 27 | # Ports definition starts here... 28 | self$definePort({ 29 | self$addInputPort( 30 | name = "reset", 31 | description = "An integer of class 'shinyActionButtonValue'", 32 | sample = 1 33 | ) 34 | 35 | self$addOutputPort( 36 | name = "counter", 37 | description = "An integer representing the current counter value", 38 | sample = 3 39 | ) 40 | }) 41 | }, 42 | #' @description 43 | #' Module's ui function. 44 | #' @return HTML tags list. 45 | ui = function(label = "Counter") { 46 | tagList( 47 | actionButton(self$ns("button"), label = label), 48 | verbatimTextOutput(self$ns("out")) 49 | ) 50 | }, 51 | #' @description 52 | #' Module's server function. 53 | #' @param input Shiny input 54 | #' @param output Shiny output 55 | #' @param session Shiny session 56 | server = function(input, output, session) { 57 | # Don't remove the line below 58 | super$server(input, output, session) 59 | 60 | # Module server logic starts here ... 61 | self$react$count <- reactiveVal(0) 62 | self$obser$add <- observeEvent(input$button, { 63 | self$react$count(self$react$count() + 1) 64 | }) 65 | self$obser$reset <- observeEvent(self$execInput("reset"), { 66 | self$react$count(0) 67 | }) 68 | output$out <- renderText({ 69 | self$react$count() 70 | }) 71 | 72 | self$assignPort({ 73 | self$updateOutputPort("counter", self$react$count) 74 | }) 75 | } 76 | ) 77 | ) 78 | -------------------------------------------------------------------------------- /inst/shiny/examples/5_counter/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: tidymodules - Counter 2 | Author: Mustapha Larbaoui 3 | License: Apache 4 | DisplayMode: Showcase 5 | Tags: module, shiny 6 | Type: Shiny 7 | -------------------------------------------------------------------------------- /inst/shiny/examples/5_counter/app.R: -------------------------------------------------------------------------------- 1 | 2 | # SM -----------> Shiny module 3 | 4 | library(shiny) 5 | 6 | source(system.file(package = "tidymodules", "shiny/examples/5_counter/counterSM.R")) 7 | 8 | SM_UI <- fluidPage( 9 | br(), 10 | shiny::fluidRow( 11 | column(1, actionButton("reset", label = "Reset")), 12 | column(1, counterButton("counter", "Counter")), 13 | column(1, "counter + 2 = ", textOutput("total_result")) 14 | ) 15 | ) 16 | 17 | SM_Server <- function(input, output, session) { 18 | reset <- reactive(input$reset) 19 | count <- callModule(counter, "counter", reset) 20 | output$total_result <- renderText({ 21 | count() + 2 22 | }) 23 | } 24 | 25 | # TM -----------> tidymodules 26 | 27 | library(tidymodules) 28 | 29 | source(system.file(package = "tidymodules", "shiny/examples/5_counter/Counter.R")) 30 | 31 | cnt <- Counter$new() 32 | 33 | TM_UI <- fluidPage( 34 | br(), 35 | shiny::fluidRow( 36 | column(1, actionButton("reset", label = "Reset")), 37 | column(1, cnt$ui("Counter")), 38 | column(1, "counter + 2 = ", textOutput("total_result")) 39 | ) 40 | ) 41 | 42 | TM_Server <- function(input, output, session) { 43 | reset <- reactive(input$reset) 44 | 45 | callModules() 46 | defineEdges({ 47 | reset %>1% cnt 48 | }) 49 | 50 | output$total_result <- renderText({ 51 | cnt$execOutput("counter") + 2 52 | }) 53 | } 54 | 55 | # shinyApp(SM_UI, SM_Server) 56 | shinyApp(TM_UI, TM_Server) 57 | -------------------------------------------------------------------------------- /inst/shiny/examples/5_counter/counterSM.R: -------------------------------------------------------------------------------- 1 | # Example from Joe' blog (slightly modified) 2 | # https://shiny.rstudio.com/articles/modules.html 3 | 4 | library(shiny) 5 | 6 | counterButton <- function(id, label = "Counter") { 7 | ns <- NS(id) 8 | tagList( 9 | actionButton(ns("button"), label = label), 10 | verbatimTextOutput(ns("out")) 11 | ) 12 | } 13 | 14 | counter <- function(input, output, session, reset) { 15 | count <- reactiveVal(0) 16 | observeEvent(input$button, { 17 | count(count() + 1) 18 | }) 19 | observeEvent(reset(), { 20 | count(0) 21 | }) 22 | output$out <- renderText({ 23 | count() 24 | }) 25 | count 26 | } 27 | -------------------------------------------------------------------------------- /man/ModStore.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ModStore.R 3 | \name{ModStore} 4 | \alias{ModStore} 5 | \title{R6 Class Representing a ModStore} 6 | \description{ 7 | This class is used to create a storage for tidymodules objects. 8 | } 9 | \details{ 10 | Manage applications, sessions and modules. 11 | } 12 | \examples{ 13 | 14 | ## ------------------------------------------------ 15 | ## Method `ModStore$new` 16 | ## ------------------------------------------------ 17 | 18 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 19 | m <- MyModule$new() 20 | s <- m$getStore() 21 | 22 | ## ------------------------------------------------ 23 | ## Method `ModStore$isStored` 24 | ## ------------------------------------------------ 25 | 26 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 27 | m <- MyModule$new() 28 | s <- m$getStore() 29 | s$isStored(m) 30 | } 31 | \section{Methods}{ 32 | \subsection{Public methods}{ 33 | \itemize{ 34 | \item \href{#method-ModStore-new}{\code{ModStore$new()}} 35 | \item \href{#method-ModStore-isStored}{\code{ModStore$isStored()}} 36 | \item \href{#method-ModStore-getGlobalSession}{\code{ModStore$getGlobalSession()}} 37 | \item \href{#method-ModStore-getSession}{\code{ModStore$getSession()}} 38 | \item \href{#method-ModStore-getSessions}{\code{ModStore$getSessions()}} 39 | \item \href{#method-ModStore-getMods}{\code{ModStore$getMods()}} 40 | \item \href{#method-ModStore-getEdges}{\code{ModStore$getEdges()}} 41 | \item \href{#method-ModStore-addEdge}{\code{ModStore$addEdge()}} 42 | \item \href{#method-ModStore-delEdges}{\code{ModStore$delEdges()}} 43 | \item \href{#method-ModStore-addMod}{\code{ModStore$addMod()}} 44 | \item \href{#method-ModStore-delMod}{\code{ModStore$delMod()}} 45 | \item \href{#method-ModStore-print}{\code{ModStore$print()}} 46 | \item \href{#method-ModStore-clone}{\code{ModStore$clone()}} 47 | } 48 | } 49 | \if{html}{\out{
}} 50 | \if{html}{\out{}} 51 | \if{latex}{\out{\hypertarget{method-ModStore-new}{}}} 52 | \subsection{Method \code{new()}}{ 53 | Create a new ModStore object. 54 | Should be called once by the TidyModule class. 55 | Not to be called directly outside TidyModule. 56 | The ModStore object can be retrieved from any TidyModule object, see example below. 57 | \subsection{Usage}{ 58 | \if{html}{\out{
}}\preformatted{ModStore$new()}\if{html}{\out{
}} 59 | } 60 | 61 | \subsection{Returns}{ 62 | A new \code{ModStore} object. 63 | } 64 | \subsection{Examples}{ 65 | \if{html}{\out{
}} 66 | \preformatted{MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 67 | m <- MyModule$new() 68 | s <- m$getStore() 69 | } 70 | \if{html}{\out{
}} 71 | 72 | } 73 | 74 | } 75 | \if{html}{\out{
}} 76 | \if{html}{\out{}} 77 | \if{latex}{\out{\hypertarget{method-ModStore-isStored}{}}} 78 | \subsection{Method \code{isStored()}}{ 79 | Check if a module is stored in the current session. 80 | \subsection{Usage}{ 81 | \if{html}{\out{
}}\preformatted{ModStore$isStored(m)}\if{html}{\out{
}} 82 | } 83 | 84 | \subsection{Arguments}{ 85 | \if{html}{\out{
}} 86 | \describe{ 87 | \item{\code{m}}{TidyModule object.} 88 | } 89 | \if{html}{\out{
}} 90 | } 91 | \subsection{Examples}{ 92 | \if{html}{\out{
}} 93 | \preformatted{MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 94 | m <- MyModule$new() 95 | s <- m$getStore() 96 | s$isStored(m) 97 | } 98 | \if{html}{\out{
}} 99 | 100 | } 101 | 102 | } 103 | \if{html}{\out{
}} 104 | \if{html}{\out{}} 105 | \if{latex}{\out{\hypertarget{method-ModStore-getGlobalSession}{}}} 106 | \subsection{Method \code{getGlobalSession()}}{ 107 | Retrieve the global session 'global_session'. 108 | This is the session that exists outside the application server function 109 | \subsection{Usage}{ 110 | \if{html}{\out{
}}\preformatted{ModStore$getGlobalSession()}\if{html}{\out{
}} 111 | } 112 | 113 | } 114 | \if{html}{\out{
}} 115 | \if{html}{\out{}} 116 | \if{latex}{\out{\hypertarget{method-ModStore-getSession}{}}} 117 | \subsection{Method \code{getSession()}}{ 118 | Retrieve a module session. 119 | This could be the global session or a user session. 120 | \subsection{Usage}{ 121 | \if{html}{\out{
}}\preformatted{ModStore$getSession(m)}\if{html}{\out{
}} 122 | } 123 | 124 | \subsection{Arguments}{ 125 | \if{html}{\out{
}} 126 | \describe{ 127 | \item{\code{m}}{TidyModule object.} 128 | } 129 | \if{html}{\out{
}} 130 | } 131 | } 132 | \if{html}{\out{
}} 133 | \if{html}{\out{}} 134 | \if{latex}{\out{\hypertarget{method-ModStore-getSessions}{}}} 135 | \subsection{Method \code{getSessions()}}{ 136 | Retrieve all sessions. 137 | \subsection{Usage}{ 138 | \if{html}{\out{
}}\preformatted{ModStore$getSessions()}\if{html}{\out{
}} 139 | } 140 | 141 | } 142 | \if{html}{\out{
}} 143 | \if{html}{\out{}} 144 | \if{latex}{\out{\hypertarget{method-ModStore-getMods}{}}} 145 | \subsection{Method \code{getMods()}}{ 146 | Retrieve all modules. 147 | \subsection{Usage}{ 148 | \if{html}{\out{
}}\preformatted{ModStore$getMods(m)}\if{html}{\out{
}} 149 | } 150 | 151 | \subsection{Arguments}{ 152 | \if{html}{\out{
}} 153 | \describe{ 154 | \item{\code{m}}{TidyModule object.} 155 | } 156 | \if{html}{\out{
}} 157 | } 158 | } 159 | \if{html}{\out{
}} 160 | \if{html}{\out{}} 161 | \if{latex}{\out{\hypertarget{method-ModStore-getEdges}{}}} 162 | \subsection{Method \code{getEdges()}}{ 163 | Retrieve modules connections. 164 | \subsection{Usage}{ 165 | \if{html}{\out{
}}\preformatted{ModStore$getEdges(m)}\if{html}{\out{
}} 166 | } 167 | 168 | \subsection{Arguments}{ 169 | \if{html}{\out{
}} 170 | \describe{ 171 | \item{\code{m}}{TidyModule object.} 172 | } 173 | \if{html}{\out{
}} 174 | } 175 | } 176 | \if{html}{\out{
}} 177 | \if{html}{\out{}} 178 | \if{latex}{\out{\hypertarget{method-ModStore-addEdge}{}}} 179 | \subsection{Method \code{addEdge()}}{ 180 | Add modules connections into ModStore. 181 | An edge is either a connection between a reactive object and a module 182 | or between two modules. 183 | \subsection{Usage}{ 184 | \if{html}{\out{
}}\preformatted{ModStore$addEdge(from, to, mode = "direct", comment = NA)}\if{html}{\out{
}} 185 | } 186 | 187 | \subsection{Arguments}{ 188 | \if{html}{\out{
}} 189 | \describe{ 190 | \item{\code{from}}{list with three elements: m -> module, type -> input or output, port -> port Id.} 191 | 192 | \item{\code{to}}{list with three elements: m -> module, type -> input or output, port -> port Id.} 193 | 194 | \item{\code{mode}}{The type of edge, default to 'direct'.} 195 | 196 | \item{\code{comment}}{Any additional comment.} 197 | } 198 | \if{html}{\out{
}} 199 | } 200 | } 201 | \if{html}{\out{
}} 202 | \if{html}{\out{}} 203 | \if{latex}{\out{\hypertarget{method-ModStore-delEdges}{}}} 204 | \subsection{Method \code{delEdges()}}{ 205 | Remove module edges 206 | \subsection{Usage}{ 207 | \if{html}{\out{
}}\preformatted{ModStore$delEdges(m)}\if{html}{\out{
}} 208 | } 209 | 210 | \subsection{Arguments}{ 211 | \if{html}{\out{
}} 212 | \describe{ 213 | \item{\code{m}}{TidyModule object.} 214 | } 215 | \if{html}{\out{
}} 216 | } 217 | } 218 | \if{html}{\out{
}} 219 | \if{html}{\out{}} 220 | \if{latex}{\out{\hypertarget{method-ModStore-addMod}{}}} 221 | \subsection{Method \code{addMod()}}{ 222 | Add module into the ModStore. 223 | \subsection{Usage}{ 224 | \if{html}{\out{
}}\preformatted{ModStore$addMod(m)}\if{html}{\out{
}} 225 | } 226 | 227 | \subsection{Arguments}{ 228 | \if{html}{\out{
}} 229 | \describe{ 230 | \item{\code{m}}{TidyModule object.} 231 | } 232 | \if{html}{\out{
}} 233 | } 234 | } 235 | \if{html}{\out{
}} 236 | \if{html}{\out{}} 237 | \if{latex}{\out{\hypertarget{method-ModStore-delMod}{}}} 238 | \subsection{Method \code{delMod()}}{ 239 | Delete a module from the ModStore. 240 | \subsection{Usage}{ 241 | \if{html}{\out{
}}\preformatted{ModStore$delMod(m)}\if{html}{\out{
}} 242 | } 243 | 244 | \subsection{Arguments}{ 245 | \if{html}{\out{
}} 246 | \describe{ 247 | \item{\code{m}}{TidyModule object.} 248 | } 249 | \if{html}{\out{
}} 250 | } 251 | } 252 | \if{html}{\out{
}} 253 | \if{html}{\out{}} 254 | \if{latex}{\out{\hypertarget{method-ModStore-print}{}}} 255 | \subsection{Method \code{print()}}{ 256 | Print the ModStore object. 257 | \subsection{Usage}{ 258 | \if{html}{\out{
}}\preformatted{ModStore$print()}\if{html}{\out{
}} 259 | } 260 | 261 | } 262 | \if{html}{\out{
}} 263 | \if{html}{\out{}} 264 | \if{latex}{\out{\hypertarget{method-ModStore-clone}{}}} 265 | \subsection{Method \code{clone()}}{ 266 | The objects of this class are cloneable with this method. 267 | \subsection{Usage}{ 268 | \if{html}{\out{
}}\preformatted{ModStore$clone(deep = FALSE)}\if{html}{\out{
}} 269 | } 270 | 271 | \subsection{Arguments}{ 272 | \if{html}{\out{
}} 273 | \describe{ 274 | \item{\code{deep}}{Whether to make a deep clone.} 275 | } 276 | \if{html}{\out{
}} 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /man/add_module.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_module.R 3 | \name{add_module} 4 | \alias{add_module} 5 | \title{Create a module} 6 | \usage{ 7 | add_module( 8 | name, 9 | inherit = "TidyModule", 10 | path = getwd(), 11 | prefix = "tm", 12 | open = TRUE, 13 | dir_create = TRUE, 14 | export = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{name}{The class name of the module.} 19 | 20 | \item{inherit}{Parent module class. Default is TidyModule.} 21 | 22 | \item{path}{Where to created the file. Default is \code{getwd()}. The function will add \code{R} to the path if the sub-folder exists.} 23 | 24 | \item{prefix}{filename prefix. Default is \code{tm}. Set to `NULL`` to disable.} 25 | 26 | \item{open}{Should the file be opened?} 27 | 28 | \item{dir_create}{Creates the directory if it doesn't exist, default is \code{TRUE}.} 29 | 30 | \item{export}{Logical. Should the module be exported? Default is \code{FALSE}.} 31 | } 32 | \description{ 33 | This function creates a \code{{tm}} module class inside the current folder. 34 | } 35 | \note{ 36 | As a convention, this function will automatically capitalize the first character of the \code{name} argument. 37 | } 38 | -------------------------------------------------------------------------------- /man/add_tm_snippets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/snippets.R 3 | \name{add_tm_snippets} 4 | \alias{add_tm_snippets} 5 | \title{Add \code{{tm}} snippets to RStudio} 6 | \usage{ 7 | add_tm_snippets(force = FALSE) 8 | } 9 | \arguments{ 10 | \item{force}{Force the re-installation when the snippets are already installed.} 11 | } 12 | \description{ 13 | This function adds useful \code{{tm}} code snippets to RStudio. 14 | } 15 | -------------------------------------------------------------------------------- /man/callModules.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{callModules} 4 | \alias{callModules} 5 | \title{Call modules function} 6 | \usage{ 7 | callModules() 8 | } 9 | \description{ 10 | This utility function call all modules initialized in the global session. 11 | The global session is the session shared outside the server function of the application. 12 | All the modules initialized in the global session can be called with this function in a single call. 13 | The function take care of cloning and attaching them to the current user session. 14 | 15 | Note that this function can only be called in the app server function at the moment. 16 | We are working on supporting callModules within module server function for invoking nested modules. 17 | } 18 | -------------------------------------------------------------------------------- /man/check_and_load.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/examples.R 3 | \name{check_and_load} 4 | \alias{check_and_load} 5 | \title{check if list of package namespaces exist, load them or display relevant information} 6 | \usage{ 7 | check_and_load(packages) 8 | } 9 | \arguments{ 10 | \item{packages}{character vector of package names} 11 | } 12 | \description{ 13 | Utility function for managing package dependencies for tidymodules examples 14 | } 15 | \examples{ 16 | 17 | check_and_load("ggplot2") 18 | 19 | } 20 | -------------------------------------------------------------------------------- /man/combine_ports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/verbs.R 3 | \name{combine_ports} 4 | \alias{combine_ports} 5 | \title{Combine ports function} 6 | \usage{ 7 | combine_ports(...) 8 | } 9 | \arguments{ 10 | \item{...}{key/value pairs of ports} 11 | } 12 | \description{ 13 | This function combines ports into a reactive list (reactiveValues) 14 | } 15 | \examples{ 16 | \dontrun{ 17 | # Somewhere in the app... 18 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 19 | MyModule$new("Mod1") 20 | MyModule$new("Mod2") 21 | MyModule$new("Mod3") 22 | 23 | # Must be in the server code and after calling the modules! 24 | callModules() 25 | observe({ 26 | combine_ports( 27 | input_1 = mod(1)$getOutput(1), 28 | input_2 = mod(2)$getOutput(1) 29 | ) \%>1\% mod(3) 30 | }) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /man/defineEdges.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{defineEdges} 4 | \alias{defineEdges} 5 | \title{Function wrapper for ports connection expression.} 6 | \usage{ 7 | defineEdges(x) 8 | } 9 | \arguments{ 10 | \item{x}{expression} 11 | } 12 | \description{ 13 | Used in server functions to define how modules are connected to each other. 14 | } 15 | -------------------------------------------------------------------------------- /man/getCacheOption.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{getCacheOption} 4 | \alias{getCacheOption} 5 | \title{Retrieve cache option from the environment} 6 | \usage{ 7 | getCacheOption() 8 | } 9 | \description{ 10 | The cache option \code{tm_disable_cache} is a global options that enable or disable the use of existing modules from the current session. 11 | This option is \code{FALSE} by default and should be used in concordance with the \code{tm_session_type} global option. See \code{\link{session_type}} for a list of possible session type. 12 | } 13 | -------------------------------------------------------------------------------- /man/getMod.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{getMod} 4 | \alias{getMod} 5 | \title{Retrieve module from ModStore} 6 | \usage{ 7 | getMod(id = 1, group = NULL) 8 | } 9 | \arguments{ 10 | \item{id}{Name or Id of the module} 11 | 12 | \item{group}{Group name} 13 | } 14 | \description{ 15 | This utility function retrieve tidymodules from the central ModStore 16 | using module namespace/id and/or group 17 | } 18 | \examples{ 19 | 20 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 21 | MyModule$new("MyFirst") 22 | MyModule$new("MySecond") 23 | MyModule$new("MyThird", group = "B") 24 | 25 | # MyFirst 26 | getMod(1) 27 | getMod("MyFirst") 28 | 29 | # MySecond 30 | getMod(2) 31 | 32 | # MyThird 33 | getMod(2) 34 | getMod("B-MyThird") 35 | getMod(1, group = "B") 36 | 37 | } 38 | -------------------------------------------------------------------------------- /man/getSessionId.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{getSessionId} 4 | \alias{getSessionId} 5 | \title{Function that generates session Id} 6 | \usage{ 7 | getSessionId(session = getDefaultReactiveDomain()) 8 | } 9 | \arguments{ 10 | \item{session}{A shiny session as provide by the shiny server function.} 11 | } 12 | \value{ 13 | A session ID 14 | } 15 | \description{ 16 | tidymodules offers the ability to manage application sessions. 17 | This function is the main function used by tidymodules to find the current session Id. 18 | It takes an optional ShinySession object as argument. If null, default to the global_session. 19 | } 20 | -------------------------------------------------------------------------------- /man/get_R6CG_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{get_R6CG_list} 4 | \alias{get_R6CG_list} 5 | \title{Recursive function for retrieving R6ClassGenerator inheritance} 6 | \usage{ 7 | get_R6CG_list(r6cg) 8 | } 9 | \arguments{ 10 | \item{r6cg}{A R6ClassGenerator object.} 11 | } 12 | \value{ 13 | vector of class names 14 | } 15 | \description{ 16 | This function is used to retrieve a list of class name that a R6ClassGenerator object inherit from. 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/global_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{global_options} 4 | \alias{global_options} 5 | \title{tidymodules options} 6 | \description{ 7 | List of global options used to adjust tidymodules configuration. 8 | 9 | \itemize{ 10 | \item{\strong{tm_session_type}}{ : Define the type of the session, See available session types in \code{\link{session_type}} } 11 | \item{\strong{tm_session_custom}}{ : Used to set a custom function for generating the session Id. Used in concordance with the \code{CUSTOM} session type.} 12 | \item{\strong{tm_disable_cache}}{ : Disable caching of modules. This option is set to FALSE by default but is only relevant when user's session is managed properly. See also \code{\link{getCacheOption}}} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /man/grapes-colon-c-greater-than-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%:c>:\%} 4 | \alias{\%:c>:\%} 5 | \title{Multi-port mapping function} 6 | \usage{ 7 | l \%:c>:\% r 8 | } 9 | \arguments{ 10 | \item{l}{left module.} 11 | 12 | \item{r}{right module.} 13 | } 14 | \value{ 15 | The right module 16 | } 17 | \description{ 18 | This pipe copy all the left output ports to the right input ports. 19 | This is a copy not a mapping. The right module input ports will be created. 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-colon-greater-than-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%:>:\%} 4 | \alias{\%:>:\%} 5 | \title{Multi-port mapping function} 6 | \usage{ 7 | l \%:>:\% r 8 | } 9 | \arguments{ 10 | \item{l}{left module.} 11 | 12 | \item{r}{right module.} 13 | } 14 | \value{ 15 | The right module 16 | } 17 | \description{ 18 | This pipe maps all the left output ports to the right input ports. 19 | The right module input ports should match the number of the left output ports. 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-colon-greater-than-greater-than-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%:>>:\%} 4 | \alias{\%:>>:\%} 5 | \title{Multi-port mapping function} 6 | \usage{ 7 | l \%:>>:\% r 8 | } 9 | \arguments{ 10 | \item{l}{left module.} 11 | 12 | \item{r}{right module.} 13 | } 14 | \value{ 15 | The left module. 16 | } 17 | \description{ 18 | This pipe maps all the left output ports to the right input ports. 19 | } 20 | -------------------------------------------------------------------------------- /man/grapes-colon-i-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%:i:\%} 4 | \alias{\%:i:\%} 5 | \title{Multi-port mapping function} 6 | \usage{ 7 | l \%:i:\% r 8 | } 9 | \arguments{ 10 | \item{l}{left module.} 11 | 12 | \item{r}{right module.} 13 | } 14 | \value{ 15 | The right module. 16 | } 17 | \description{ 18 | This pipe copy all the left input ports to the right input ports. 19 | } 20 | -------------------------------------------------------------------------------- /man/grapes-colon-pi-colon-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%:pi:\%} 4 | \alias{\%:pi:\%} 5 | \title{Multi-port mapping function} 6 | \usage{ 7 | l \%:pi:\% r 8 | } 9 | \arguments{ 10 | \item{l}{parent module.} 11 | 12 | \item{r}{child module.} 13 | } 14 | \value{ 15 | The child module. 16 | } 17 | \description{ 18 | This pipe copy all the left input ports to the right input ports in a parent to child mode. 19 | } 20 | -------------------------------------------------------------------------------- /man/grapes-greater-than-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%->\%} 4 | \alias{\%->\%} 5 | \title{Port mapping function (port level)} 6 | \usage{ 7 | lp \%->\% rp 8 | } 9 | \arguments{ 10 | \item{lp}{Left port.} 11 | 12 | \item{rp}{Right port.} 13 | } 14 | \value{ 15 | The right port 16 | } 17 | \description{ 18 | This pipe works at the port level where left and right object are ports not modules. 19 | Take the left port and maps it to the right port. 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-greater-than-greater-than-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%->>\%} 4 | \alias{\%->>\%} 5 | \title{Port mapping function (port level) - forward version} 6 | \usage{ 7 | lp \%->>\% rp 8 | } 9 | \arguments{ 10 | \item{lp}{Left port.} 11 | 12 | \item{rp}{Right port.} 13 | } 14 | \value{ 15 | The left port 16 | } 17 | \description{ 18 | This pipe works at the port level where left and right object are ports not modules. 19 | Take the left port and maps it to the right port. This is a forward version, i.e. return the left item. 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-greater-than-greater-than-y-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%>>y\%} 4 | \alias{\%>>y\%} 5 | \alias{\%>>1\%} 6 | \alias{\%>>2\%} 7 | \alias{\%>>3\%} 8 | \alias{\%>>4\%} 9 | \alias{\%>>5\%} 10 | \alias{\%>>6\%} 11 | \alias{\%>>7\%} 12 | \alias{\%>>8\%} 13 | \alias{\%>>9\%} 14 | \alias{\%>>10\%} 15 | \title{Input port mapping function} 16 | \description{ 17 | This pipe maps the left object (must be a reactive function 18 | or a reactivevalues object) to the right module's input port defined by 19 | the number in the operator (y). 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-greater-than-y-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%>y\%} 4 | \alias{\%>y\%} 5 | \alias{\%>1\%} 6 | \alias{\%>2\%} 7 | \alias{\%>3\%} 8 | \alias{\%>4\%} 9 | \alias{\%>5\%} 10 | \alias{\%>6\%} 11 | \alias{\%>7\%} 12 | \alias{\%>8\%} 13 | \alias{\%>9\%} 14 | \alias{\%>10\%} 15 | \title{Input port mapping function} 16 | \description{ 17 | This pipe maps the left object (must be a reactive function 18 | or a reactivevalues object) to the right module's input port 19 | defined by the number in the operator (y). 20 | } 21 | -------------------------------------------------------------------------------- /man/grapes-x-greater-than-greater-than-y-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%x>>y\%} 4 | \alias{\%x>>y\%} 5 | \alias{\%1>>1\%} 6 | \alias{\%2>>1\%} 7 | \alias{\%3>>1\%} 8 | \alias{\%4>>1\%} 9 | \alias{\%5>>1\%} 10 | \alias{\%6>>1\%} 11 | \alias{\%7>>1\%} 12 | \alias{\%8>>1\%} 13 | \alias{\%9>>1\%} 14 | \alias{\%10>>1\%} 15 | \alias{\%1>>2\%} 16 | \alias{\%2>>2\%} 17 | \alias{\%3>>2\%} 18 | \alias{\%4>>2\%} 19 | \alias{\%5>>2\%} 20 | \alias{\%6>>2\%} 21 | \alias{\%7>>2\%} 22 | \alias{\%8>>2\%} 23 | \alias{\%9>>2\%} 24 | \alias{\%10>>2\%} 25 | \alias{\%1>>3\%} 26 | \alias{\%2>>3\%} 27 | \alias{\%3>>3\%} 28 | \alias{\%4>>3\%} 29 | \alias{\%5>>3\%} 30 | \alias{\%6>>3\%} 31 | \alias{\%7>>3\%} 32 | \alias{\%8>>3\%} 33 | \alias{\%9>>3\%} 34 | \alias{\%10>>3\%} 35 | \alias{\%1>>4\%} 36 | \alias{\%2>>4\%} 37 | \alias{\%3>>4\%} 38 | \alias{\%4>>4\%} 39 | \alias{\%5>>4\%} 40 | \alias{\%6>>4\%} 41 | \alias{\%7>>4\%} 42 | \alias{\%8>>4\%} 43 | \alias{\%9>>4\%} 44 | \alias{\%10>>4\%} 45 | \alias{\%1>>5\%} 46 | \alias{\%2>>5\%} 47 | \alias{\%3>>5\%} 48 | \alias{\%4>>5\%} 49 | \alias{\%5>>5\%} 50 | \alias{\%6>>5\%} 51 | \alias{\%7>>5\%} 52 | \alias{\%8>>5\%} 53 | \alias{\%9>>5\%} 54 | \alias{\%10>>5\%} 55 | \alias{\%1>>6\%} 56 | \alias{\%2>>6\%} 57 | \alias{\%3>>6\%} 58 | \alias{\%4>>6\%} 59 | \alias{\%5>>6\%} 60 | \alias{\%6>>6\%} 61 | \alias{\%7>>6\%} 62 | \alias{\%8>>6\%} 63 | \alias{\%9>>6\%} 64 | \alias{\%10>>6\%} 65 | \alias{\%1>>7\%} 66 | \alias{\%2>>7\%} 67 | \alias{\%3>>7\%} 68 | \alias{\%4>>7\%} 69 | \alias{\%5>>7\%} 70 | \alias{\%6>>7\%} 71 | \alias{\%7>>7\%} 72 | \alias{\%8>>7\%} 73 | \alias{\%9>>7\%} 74 | \alias{\%10>>7\%} 75 | \alias{\%1>>8\%} 76 | \alias{\%2>>8\%} 77 | \alias{\%3>>8\%} 78 | \alias{\%4>>8\%} 79 | \alias{\%5>>8\%} 80 | \alias{\%6>>8\%} 81 | \alias{\%7>>8\%} 82 | \alias{\%8>>8\%} 83 | \alias{\%9>>8\%} 84 | \alias{\%10>>8\%} 85 | \alias{\%1>>9\%} 86 | \alias{\%2>>9\%} 87 | \alias{\%3>>9\%} 88 | \alias{\%4>>9\%} 89 | \alias{\%5>>9\%} 90 | \alias{\%6>>9\%} 91 | \alias{\%7>>9\%} 92 | \alias{\%8>>9\%} 93 | \alias{\%9>>9\%} 94 | \alias{\%10>>9\%} 95 | \alias{\%1>>10\%} 96 | \alias{\%2>>10\%} 97 | \alias{\%3>>10\%} 98 | \alias{\%4>>10\%} 99 | \alias{\%5>>10\%} 100 | \alias{\%6>>10\%} 101 | \alias{\%7>>10\%} 102 | \alias{\%8>>10\%} 103 | \alias{\%9>>10\%} 104 | \alias{\%10>>10\%} 105 | \title{Single-port mapping function} 106 | \description{ 107 | This pipe works at the module level. 108 | It maps the left module's output port defined by the left number (x) 109 | of the pipe operator to the right module's input port defined by the 110 | right number (y). 111 | } 112 | -------------------------------------------------------------------------------- /man/grapes-x-greater-than-y-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%x>y\%} 4 | \alias{\%x>y\%} 5 | \alias{\%1>1\%} 6 | \alias{\%2>1\%} 7 | \alias{\%3>1\%} 8 | \alias{\%4>1\%} 9 | \alias{\%5>1\%} 10 | \alias{\%6>1\%} 11 | \alias{\%7>1\%} 12 | \alias{\%8>1\%} 13 | \alias{\%9>1\%} 14 | \alias{\%10>1\%} 15 | \alias{\%1>2\%} 16 | \alias{\%2>2\%} 17 | \alias{\%3>2\%} 18 | \alias{\%4>2\%} 19 | \alias{\%5>2\%} 20 | \alias{\%6>2\%} 21 | \alias{\%7>2\%} 22 | \alias{\%8>2\%} 23 | \alias{\%9>2\%} 24 | \alias{\%10>2\%} 25 | \alias{\%1>3\%} 26 | \alias{\%2>3\%} 27 | \alias{\%3>3\%} 28 | \alias{\%4>3\%} 29 | \alias{\%5>3\%} 30 | \alias{\%6>3\%} 31 | \alias{\%7>3\%} 32 | \alias{\%8>3\%} 33 | \alias{\%9>3\%} 34 | \alias{\%10>3\%} 35 | \alias{\%1>4\%} 36 | \alias{\%2>4\%} 37 | \alias{\%3>4\%} 38 | \alias{\%4>4\%} 39 | \alias{\%5>4\%} 40 | \alias{\%6>4\%} 41 | \alias{\%7>4\%} 42 | \alias{\%8>4\%} 43 | \alias{\%9>4\%} 44 | \alias{\%10>4\%} 45 | \alias{\%1>5\%} 46 | \alias{\%2>5\%} 47 | \alias{\%3>5\%} 48 | \alias{\%4>5\%} 49 | \alias{\%5>5\%} 50 | \alias{\%6>5\%} 51 | \alias{\%7>5\%} 52 | \alias{\%8>5\%} 53 | \alias{\%9>5\%} 54 | \alias{\%10>5\%} 55 | \alias{\%1>6\%} 56 | \alias{\%2>6\%} 57 | \alias{\%3>6\%} 58 | \alias{\%4>6\%} 59 | \alias{\%5>6\%} 60 | \alias{\%6>6\%} 61 | \alias{\%7>6\%} 62 | \alias{\%8>6\%} 63 | \alias{\%9>6\%} 64 | \alias{\%10>6\%} 65 | \alias{\%1>7\%} 66 | \alias{\%2>7\%} 67 | \alias{\%3>7\%} 68 | \alias{\%4>7\%} 69 | \alias{\%5>7\%} 70 | \alias{\%6>7\%} 71 | \alias{\%7>7\%} 72 | \alias{\%8>7\%} 73 | \alias{\%9>7\%} 74 | \alias{\%10>7\%} 75 | \alias{\%1>8\%} 76 | \alias{\%2>8\%} 77 | \alias{\%3>8\%} 78 | \alias{\%4>8\%} 79 | \alias{\%5>8\%} 80 | \alias{\%6>8\%} 81 | \alias{\%7>8\%} 82 | \alias{\%8>8\%} 83 | \alias{\%9>8\%} 84 | \alias{\%10>8\%} 85 | \alias{\%1>9\%} 86 | \alias{\%2>9\%} 87 | \alias{\%3>9\%} 88 | \alias{\%4>9\%} 89 | \alias{\%5>9\%} 90 | \alias{\%6>9\%} 91 | \alias{\%7>9\%} 92 | \alias{\%8>9\%} 93 | \alias{\%9>9\%} 94 | \alias{\%10>9\%} 95 | \alias{\%1>10\%} 96 | \alias{\%2>10\%} 97 | \alias{\%3>10\%} 98 | \alias{\%4>10\%} 99 | \alias{\%5>10\%} 100 | \alias{\%6>10\%} 101 | \alias{\%7>10\%} 102 | \alias{\%8>10\%} 103 | \alias{\%9>10\%} 104 | \alias{\%10>10\%} 105 | \title{Single-port mapping function} 106 | \description{ 107 | This pipe works at the module level. 108 | It maps the left module's output port defined by the left 109 | number (x) of the pipe operator to the right module's 110 | input port defined by the right number (y). 111 | } 112 | -------------------------------------------------------------------------------- /man/grapes-x-less-than-less-than-y-grapes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{\%x<>' or '<<' signs to return the item at the opposite of the arrow.} 15 | 16 | \item{rev}{Reverse operation. Boolean that indicates if this is a reverse operation, i.e. '<' or '<<'.} 17 | } 18 | \description{ 19 | Create a pipe function for mapping a module output to a module input 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/make_single_pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{make_single_pipe} 4 | \alias{make_single_pipe} 5 | \title{make_single_pipe: Pipe function generator} 6 | \usage{ 7 | make_single_pipe(p = NULL, f = FALSE, rev = FALSE) 8 | } 9 | \arguments{ 10 | \item{f}{fast boolean. Used with the '>>' or '<<' signs to return the item at the opposite of the arrow.} 11 | 12 | \item{rev}{Reverse operation. Boolean that indicates if this is a reverse operation, i.e. '<' or '<<'.} 13 | } 14 | \description{ 15 | Create a pipe function for mapping a reactive expression/value to a module input 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/map_ports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/verbs.R 3 | \name{map_ports} 4 | \alias{map_ports} 5 | \title{Ports mapping function} 6 | \usage{ 7 | map_ports( 8 | leftModule = NULL, 9 | leftPort = 1, 10 | rightModule = NULL, 11 | rightPort = 1, 12 | reverse = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{leftModule}{The left module object} 17 | 18 | \item{leftPort}{Port name or Id of the left module's output port} 19 | 20 | \item{rightModule}{The right module object} 21 | 22 | \item{rightPort}{Port name or Id of the right module's input port} 23 | 24 | \item{reverse}{ligical value indicating which module to return. Default to FALSE, the right module} 25 | } 26 | \description{ 27 | This function maps a module's outpout port to another module's input port. 28 | } 29 | \details{ 30 | connect ports from two different modules 31 | } 32 | -------------------------------------------------------------------------------- /man/mod.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{mod} 4 | \alias{mod} 5 | \title{Alias to getMod} 6 | \usage{ 7 | mod(id = 1, group = NULL) 8 | } 9 | \arguments{ 10 | \item{id}{Name or Id of the module} 11 | 12 | \item{group}{Group name} 13 | } 14 | \description{ 15 | See \code{\link{getMod}} 16 | } 17 | -------------------------------------------------------------------------------- /man/multi_port_map.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{multi_port_map} 4 | \alias{multi_port_map} 5 | \title{multi_port_map} 6 | \usage{ 7 | multi_port_map( 8 | l_mod, 9 | r_mod, 10 | f = FALSE, 11 | t = NULL, 12 | is_parent = FALSE, 13 | is_copy = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{l_mod}{Left module.} 18 | 19 | \item{r_mod}{Right module.} 20 | 21 | \item{f}{fast boolean. If TRUE return the right module.} 22 | 23 | \item{t}{mapping type. Default to NULL. Could also be 'input' for mapping input to input.} 24 | 25 | \item{is_parent}{Is the left module a parent module? Default to FALSE.} 26 | 27 | \item{is_copy}{boolean. Default FALSE. Force the copy of the output ports.} 28 | } 29 | \description{ 30 | Pipe function for sequentially mapping/copying left module ports to 31 | right module inputs 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /man/oport.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{oport} 4 | \alias{oport} 5 | \title{Retrieve output module's port} 6 | \usage{ 7 | oport(id = 1, p = 1, g = NULL) 8 | } 9 | \arguments{ 10 | \item{id}{Name or Id of the module} 11 | 12 | \item{p}{Port Id or name} 13 | 14 | \item{g}{Module group name} 15 | } 16 | \description{ 17 | This utility function retrieve the tidymodules output port specified in the arguments. 18 | } 19 | -------------------------------------------------------------------------------- /man/port.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \name{port} 4 | \alias{port} 5 | \title{Retrieve module's port} 6 | \usage{ 7 | port(id = 1, p = 1, t = "in", g = NULL) 8 | } 9 | \arguments{ 10 | \item{id}{Name or Id of the module} 11 | 12 | \item{p}{Port Id or name} 13 | 14 | \item{t}{Port type, in or out} 15 | 16 | \item{g}{Module group name} 17 | } 18 | \description{ 19 | This utility function retrieve the tidymodules port specified in the arguments. 20 | } 21 | -------------------------------------------------------------------------------- /man/port_map.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipes.R 3 | \name{port_map} 4 | \alias{port_map} 5 | \title{port_map} 6 | \usage{ 7 | port_map(lp, rp, f = FALSE) 8 | } 9 | \arguments{ 10 | \item{lp}{Left port} 11 | 12 | \item{rp}{Right port} 13 | 14 | \item{f}{Forward operartion. Boolean.} 15 | } 16 | \description{ 17 | port mapping function. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/race_ports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/verbs.R 3 | \name{race_ports} 4 | \alias{race_ports} 5 | \title{Race ports function} 6 | \usage{ 7 | race_ports(...) 8 | } 9 | \arguments{ 10 | \item{...}{List of racing ports} 11 | } 12 | \description{ 13 | This function collapse ports into a single port and make them race (i.e. always return the last one updated) 14 | } 15 | \examples{ 16 | \dontrun{ 17 | # Somewhere in the app... 18 | MyModule <- R6::R6Class("MyModule", inherit = tidymodules::TidyModule) 19 | MyModule$new("Mod1") 20 | MyModule$new("Mod2") 21 | MyModule$new("Mod3") 22 | 23 | # Must be in the server code and after calling the modules! 24 | callModules() 25 | observe({ 26 | race_ports( 27 | mod(1)$getOutput(1), 28 | mod(2)$getOutput(1) 29 | ) \%>1\% mod(3) 30 | }) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /man/session_type.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utility.R 3 | \docType{data} 4 | \name{session_type} 5 | \alias{session_type} 6 | \title{List of possible session types} 7 | \format{ 8 | An object of class \code{list} of length 3. 9 | } 10 | \usage{ 11 | session_type 12 | } 13 | \description{ 14 | tidymodules offers the ability to manage application sessions. 15 | At the moment the three options below are available. 16 | 17 | \itemize{ 18 | 19 | \item{SHINY}{ : The default behaviour of shiny application and the default for tidymodules. Every time you access an application 20 | you get a new token Id that defines your application user session.} 21 | 22 | \item{USER}{ : This method defines a session based on the information available in the request object of shiny output. 23 | It is a concatenation of the variables REMOTE_ADDR, HTTP_HOST and PATH_INFO like below. 24 | 25 | \code{sid <- paste0(r$REMOTE_ADDR,"@",r$HTTP_HOST,r$PATH_INFO))} 26 | 27 | Note that the method is actually not working properly for now as the information available via the request object 28 | are not reflecting the actual user. We are working on a better method to uniquely identify a remote user.} 29 | 30 | \item{CUSTOM}{ : This method allow the developper to provide a custom function for generating the session Id. 31 | It relies on the global options \code{tm_session_custom} being set and pointing to a function taking a shiny output as argument.} 32 | 33 | } 34 | } 35 | \keyword{datasets} 36 | -------------------------------------------------------------------------------- /man/showExamples.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/examples.R 3 | \name{showExamples} 4 | \alias{showExamples} 5 | \title{Launcher for the tidymodules examples} 6 | \usage{ 7 | showExamples(id = NULL, server = F, options = NULL) 8 | } 9 | \arguments{ 10 | \item{id}{Example ID. If null display list of examples with ID.} 11 | 12 | \item{server}{boolean. Is this a server call?} 13 | 14 | \item{options}{list of options to be passed to shinyApps or shinyDir} 15 | } 16 | \description{ 17 | Helper function to launch the tidymodules examples. 18 | } 19 | \examples{ 20 | 21 | if (interactive()) { 22 | showExamples(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /man/tidymodules-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidymodules.R 3 | \docType{package} 4 | \name{tidymodules-package} 5 | \alias{tidymodules} 6 | \alias{tidymodules-package} 7 | \title{tidymodules: A robust framework for developing shiny modules} 8 | \description{ 9 | tidymodules offers a robust framework for developing shiny modules based on R6 classes which should facilitates inter-modules communication. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/Novartis/tidymodules} 15 | \item \url{http://opensource.nibr.com/tidymodules/} 16 | \item Report bugs at \url{https://github.com/Novartis/tidymodules/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Mustapha Larbaoui \email{mustapha.larbaoui@novartis.com} 22 | 23 | Other contributors: 24 | \itemize{ 25 | \item Douglas Robinson \email{douglas.robinson@novartis.com} [contributor] 26 | \item Xiao Ni \email{xiao.ni@novartis.com} [contributor] 27 | \item David Granjon \email{david.granjon@novartis.com} [contributor] 28 | \item Stephen Eng \email{stefaneng13@gmail.com} [contributor] 29 | \item Marzieh Eslami Rasekh \email{marzie.rasekh@gmail.com} [contributor] 30 | \item Renan Sauteraud \email{rxs575@psu.edu} [contributor] 31 | } 32 | 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | 2 | url: https://opensource.nibr.com/tidymodules/ 3 | home: 4 | links: 5 | - text: Github 6 | href: https://github.com/Novartis/tidymodules.git 7 | - text: Site 8 | href: https://opensource.nibr.com/tidymodules/ 9 | 10 | toc: 11 | depth: 2 12 | 13 | navbar: 14 | components: 15 | articles: 16 | text: Articles 17 | menu: 18 | - text: 'Writing R6 classes and tidymodules' 19 | href: articles/intro.html 20 | - text: 'Namespace management' 21 | href: articles/namespace.html 22 | - text: 'Cross module communication' 23 | href: articles/communication.html 24 | - text: 'Inheritance' 25 | href: articles/inheritance.html 26 | - text: 'Session management' 27 | href: articles/session.html 28 | 29 | reference: 30 | - title: R6 Modules 31 | desc: List of R6 Classes provided by tidymodules 32 | contents: 33 | - '`ModStore`' 34 | - '`Store`' 35 | - '`TidyModule`' 36 | - title: Utility functions 37 | desc: List of utility functions 38 | contents: 39 | - '`showExamples`' 40 | - '`getSessionId`' 41 | - '`callModules`' 42 | - '`listModules`' 43 | - '`add_module`' 44 | - '`add_tm_snippets`' 45 | - '`check_and_load`' 46 | - '`getMod`' 47 | - '`mod`' 48 | - '`port`' 49 | - '`oport`' 50 | - '`iport`' 51 | - title: Configuration 52 | desc: List of available configurations 53 | contents: 54 | - '`global_options`' 55 | - '`session_type`' 56 | - '`getCacheOption`' 57 | - title: Port functions and operators 58 | desc: List of functions and pipe operators used to manage modules communication 59 | contents: 60 | - '`defineEdges`' 61 | - '`map_ports`' 62 | - '`combine_ports`' 63 | - '`race_ports`' 64 | - '`%>y%`' 65 | - '`%>>y%`' 66 | - '`%->%`' 67 | - '`%->>%`' 68 | - '`%x>y%`' 69 | - '`%x>>y%`' 70 | - '`%x:%`' 73 | - '`%:>>:%`' 74 | - '`%:c>:%`' 75 | - '`%:i:%`' 76 | - '`%:pi:%`' 77 | - title: internal 78 | contents: 79 | - '`UtilityModule`' 80 | 81 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(tidymodules) 3 | 4 | test_check("tidymodules") 5 | -------------------------------------------------------------------------------- /tests/testthat/test_dummy.R: -------------------------------------------------------------------------------- 1 | context("tests") 2 | 3 | 4 | # Configure this test to fit your need 5 | test_that( 6 | "my test", 7 | { 8 | TRUE 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /tidymodules.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 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 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | PackageRoxygenize: rd,collate,namespace 19 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/communication.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Cross-module communication" 3 | title: "Cross-module communication" 4 | subtitle: "
" 5 | author: "Mustapha Larbaoui" 6 | date: "`r Sys.Date()`" 7 | output: rmarkdown::html_vignette 8 | vignette: > 9 | %\VignetteIndexEntry{Cross-module communication} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | comment = "#>" 18 | ) 19 | ``` 20 | 21 | Coming soon... 22 | -------------------------------------------------------------------------------- /vignettes/figures/ERD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/vignettes/figures/ERD.png -------------------------------------------------------------------------------- /vignettes/figures/mod_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/vignettes/figures/mod_network.png -------------------------------------------------------------------------------- /vignettes/figures/ports_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Novartis/tidymodules/daa948f31910686171476865051dcee9e6f5b10f/vignettes/figures/ports_intro.png -------------------------------------------------------------------------------- /vignettes/inheritance.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Inheritance" 3 | title: "Inheritance" 4 | subtitle: "
" 5 | author: "Mustapha Larbaoui" 6 | date: "`r Sys.Date()`" 7 | output: rmarkdown::html_vignette 8 | vignette: > 9 | %\VignetteIndexEntry{Inheritance} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | comment = "#>" 18 | ) 19 | ``` 20 | 21 | Coming soon... 22 | -------------------------------------------------------------------------------- /vignettes/intro.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Getting started with writing `{tidymodules}`" 3 | title: "Getting started with writing `{tidymodules}`" 4 | subtitle: "
" 5 | author: "Xiao Ni, Mustapha Larbaoui" 6 | date: "`r Sys.Date()`" 7 | output: rmarkdown::html_vignette 8 | vignette: > 9 | %\VignetteIndexEntry{Getting started with writing `{tidymodules}`} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | eval = TRUE, 18 | comment = "#>" 19 | ) 20 | ``` 21 | 22 | ## A quick introduction to R6 and Object Oriented Programming (OOP) 23 | 24 | `{tidymodules}` or `{tm}` in short, is based on R6 ([https://r6.r-lib.org/](https://r6.r-lib.org/)), which is an implementation of encapsulated object-oriented programming for R. Therefore knowledge of R6 is a prerequisite to develop `{tm}` modules. 25 | 26 | R6 provides a framework for OOP in R. Unlike the functional programming style, R6 encapsulates methods and fields in classes that instantiate into objects. R6 classes are similar to R's reference classes, but are more efficient and do not depend on S4 classes and the methods package. 27 | 28 | This vignette provides a brief overview of R6 for developers new to R6. For more information, developers are recommended to review the R6 packagedown site ([https://r6.r-lib.org/](https://r6.r-lib.org/)), as well as Chapter 14 of the Advanced R book ([https://adv-r.hadley.nz/r6.html](https://adv-r.hadley.nz/r6.html)) 29 | 30 | ### R6 classes and methods 31 | 32 | `{tm}` depends on the R6 package which you can install from CRAN and load: 33 | ```{r , eval=FALSE} 34 | library(R6) 35 | ``` 36 | 37 | R6 classes are created using the [`R6::R6Class()` function](https://r6.r-lib.org/reference/R6Class.html), which is the only function from the R6 package that is typically used. The following is a simple example of defining an R6 class: 38 | ```{r, eval=TRUE} 39 | Calculator <- R6::R6Class( 40 | classname = "Calculator", 41 | public = list( 42 | value = NA, 43 | initialize = function(value) { 44 | self$value <- value 45 | }, 46 | add = function(x = 1) { 47 | self$value <- self$value + x 48 | invisible(self) 49 | } 50 | ) 51 | ) 52 | ``` 53 | The first argument `classname` by convention uses `UpperCamelCase`. The second argument `public` encapsulates a list of methods (functions) and fields (objects) that make up the public interface of the object. By convention methods and fields use `snake_case`. Methods can access the methods and fields of the current object using `self$`. One should always assign the result of `R6Class()` into a variable with the same names as the `classname` because `R6Class()` returns an R6 object that defines the class. 54 | 55 | 56 | You can print the class definition: 57 | ```{r} 58 | Calculator 59 | ``` 60 | 61 | 62 | To create a new instance of `Calculator`, use the `$new()` method. The `$initialize()` is an important method, which overrides the default behavior of `$new()`. In the above example, the `$initialize()` method initializes the `calculator1` object with `value = 0`. 63 | ```{r} 64 | calculator1 <- Calculator$new(0) 65 | calculator1 66 | ``` 67 | 68 | 69 | You can then call the methods and access fields using `$`: 70 | ```{r} 71 | calculator1$add(10) 72 | calculator1$value 73 | ``` 74 | 75 | You can also add methods after class creation as illustrated below for the existing `Calculator` R6 class, although new methods and fields are only available to new objects. 76 | ```{r} 77 | Calculator$set("public", "subtract", function(x = 1) { 78 | self$value <- self$value - x 79 | invisible(self) 80 | }) 81 | Calculator 82 | ``` 83 | 84 | 85 | Below are some key features of R6. 86 | 87 | - **Reference semantics**: objects are not copied when modified. R6 provides a `$clone()` method for making copy of an object. For more details, refer to https://r6.r-lib.org/reference/R6Class.html#cloning-objects. 88 | - **Public vs. private members**: `R6Class()` has a `private` argument for you to define private methods and fileds that can only be accessed from within the class, not from the outside. 89 | - **Inheritance**: as in classical OOP, one R6 class can inherit from another R6 class. Superclass methods can be accessed with `super$`. 90 | 91 | ## `tidymodules::TidyModule` class 92 | 93 | The `tidymodules::TidyModule` class is a R6 class and the parent of all `{tm}` modules. 94 | 95 | Below is partial code of the `TidyModule` class for illustration purpose. The `TidyModule` class includes many public methods. There are utility functions such as `callModules()`, `definePorts()`, `assignPort()` as well as functions that need to be overwritten such as `ui()`, `server()`, etc. 96 | 97 | Unlike conventional Shiny modules in funtional programming style, `{tm}` encapsulates functions such as ui() and server() as methods in a TidyModule class object. Module namespace ID is seamlessly managed within the module class for the ui and server. For complete technical documentation that includes other methods and fields, see `?TidyModule`. 98 | ```{r, eval=FALSE} 99 | TidyModule <- R6::R6Class( 100 | "TidyModule", 101 | public = list( 102 | id = NULL, 103 | module_ns = NULL, 104 | parent_ns = NULL, 105 | parent_mod = NULL, 106 | parent_ports = NULL, 107 | group = NULL, 108 | created = NULL, 109 | o = NULL, 110 | i = NULL, 111 | initialize = function(id = NULL, inherit = TRUE, group = NULL) { 112 | # details omitted 113 | }, 114 | # Other methods such 115 | ui = function() { 116 | return(shiny::tagList()) 117 | }, 118 | server = function(input, 119 | output, 120 | session) { 121 | # Need to isolate this block to avoid unecessary triggers 122 | shiny::isolate({ 123 | private$shiny_session <- session 124 | private$shiny_input <- input 125 | private$shiny_output <- output 126 | }) 127 | }, 128 | definePort = function(x) { 129 | shiny::isolate(x) 130 | }, 131 | assignPort = function(x) { 132 | shiny::observe({ 133 | shiny::isolate(x) 134 | }) 135 | }, 136 | # Other public methods omitted 137 | ), 138 | private = list( 139 | # Details omitted 140 | ) 141 | ) 142 | ``` 143 | 144 | 145 | 146 | ## Writing your first `{tm}` module 147 | 148 | You can develop new `{tm}` modules by inheriting and extending the `tidymodules::TidyModule` class. 149 | 150 | Below is a minimal example, `RandomNumberGenerator`, defined with one input port and one output port. The input port is a random number seed that feeds into a random number generator, whose result serves as the module output. 151 | 152 | ```{r} 153 | # Module definition 154 | RandomNumMod <- R6::R6Class( 155 | "RandomNumGenerator", 156 | inherit = tidymodules::TidyModule, 157 | public = list( 158 | initialize = function(id = NULL) { 159 | super$initialize(id) 160 | 161 | self$definePort({ 162 | self$addInputPort( 163 | name = "seed", 164 | description = "random number seed", 165 | sample = 123 166 | ) 167 | 168 | self$addOutputPort( 169 | name = "number", 170 | description = "Random number", 171 | sample = 123 172 | ) 173 | }) 174 | }, 175 | ui = function() { 176 | tagList( 177 | verbatimTextOutput(self$ns("text")) 178 | ) 179 | }, 180 | server = function(input, output, session) { 181 | super$server(input, output, session) 182 | 183 | result <- reactive({ 184 | s <- self$getInput("seed") 185 | set.seed(s()) 186 | floor(runif(1) * 1e5) 187 | }) 188 | 189 | output$text <- renderPrint({ 190 | s <- self$getInput("seed") 191 | print(paste0("seed = ", s())) 192 | print(paste0("number = ", result())) 193 | }) 194 | 195 | self$assignPort({ 196 | self$updateOutputPort( 197 | id = "number", 198 | output = result 199 | ) 200 | }) 201 | return(result) 202 | } 203 | ) 204 | ) 205 | ``` 206 | 207 | Cross-communication between two `{tm}` modules is established using several flavours of the pipe `%>%` operator, as illustrated in the following code. The first module's output is fed as the random number seed for the second module. 208 | ```{r, eval=FALSE} 209 | ## Calling app 210 | randomNumMod1 <- RandomNumMod$new() 211 | randomNumMod2 <- RandomNumMod$new() 212 | 213 | ui <- tagList( 214 | fluidPage( 215 | randomNumMod1$ui(), 216 | randomNumMod2$ui() 217 | ) 218 | ) 219 | server <- function(input, output, session) { 220 | randomNumMod1$callModule() 221 | randomNumMod2$callModule() 222 | 223 | seed_1 <- reactive(123) 224 | 225 | observe({ 226 | seed_1 %>1% randomNumMod1 %1>1% randomNumMod2 227 | }) 228 | } 229 | 230 | shinyApp(ui = ui, server = server) 231 | ``` 232 | 233 | ## Next steps 234 | 235 | To learn more about writing `{tm}` modules, read the examples. 236 | 237 | -------------------------------------------------------------------------------- /vignettes/namespace.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Namespace management" 3 | title: "Namespace management" 4 | subtitle: "
" 5 | author: "Mustapha Larbaoui" 6 | date: "`r Sys.Date()`" 7 | output: rmarkdown::html_vignette 8 | vignette: > 9 | %\VignetteIndexEntry{Namespace management} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | comment = "#>" 18 | ) 19 | ``` 20 | 21 | 22 | As a reminder, the namespace is an important aspect of shiny module that prevents collision of input and output IDs in your application. The namespace identify each unique instance of the modules and need to be provided by the caller. 23 | 24 | `{tidymodules}` can be considered as an object-oriented organisational layer on top of shiny that does conventional module in the background. 25 | It does use namespace but make it optional to the users. In other words, you don't need to remember it! 26 | 27 | ## Optional namespace Id 28 | 29 | In the example below a `{tm}` module `MyMod` is defined and called with the `new()` function and with no arguments supplied. 30 | Printing the instance `m` to the console shows the structure of the module. 31 | 32 | ```{r, eval=TRUE} 33 | library(tidymodules) 34 | 35 | MyMod <- R6::R6Class( 36 | "MyMod", 37 | inherit = TidyModule 38 | ) 39 | 40 | m <- MyMod$new() 41 | 42 | m 43 | ``` 44 | 45 | The namespace Id for module `m` above is `MyMod-1`. It is a unique Id generated by `{tidymodules}` and composed of the class name and the initialisation order of the module. 46 | 47 | 48 | ## User defined namespace Id 49 | 50 | The user can also provide a more informative name when initialising a module. 51 | 52 | 53 | ```{r, eval=TRUE} 54 | 55 | m <- MyMod$new("Tom") 56 | 57 | m 58 | ``` 59 | 60 | `Tom` end up being the namespace Id of `m`. 61 | 62 | The provided name must begin with a letter `[A-Za-z]` and may be followed by any number of letters, digits `[0-9]`, hyphens `-`, underscores `_`. 63 | You should not use the following characters as they have a special meaning on the UI ( CSS / jQuery ). 64 | 65 | > ~ ! @ $ % ^ & * ( ) + = , . / ' ; : " ? > < [ ] \ { } | ` # 66 | 67 | 68 | ## Namespace and nested modules 69 | 70 | The code below illustrates a very simple example of nested module and the corresponding namespace Ids. Here are few things to note about this example: 71 | 72 | * `ModA` and `ModB` are both `{tm}` classes as they both inherit from `tidymodules::TidyModule` 73 | * `ModB` is used as a nested class of `ModA` 74 | * `ModA` has a public field named `nested_mod` 75 | * The namespace of a nested module is always prefixed with its parent 76 | 77 | 78 | ```{r, eval=TRUE} 79 | ModB <- R6::R6Class( 80 | "ModB", 81 | inherit = TidyModule 82 | ) 83 | 84 | ModA <- R6::R6Class( 85 | "ModA", 86 | inherit = TidyModule, 87 | public = list( 88 | nested_mod = NULL, 89 | initialize = function(...) { 90 | super$initialize(...) 91 | self$nested_mod <- ModB$new() 92 | } 93 | ) 94 | ) 95 | 96 | m <- ModA$new() 97 | n <- m$nested_mod 98 | 99 | m 100 | n 101 | ``` 102 | 103 | ## Grouping modules 104 | 105 | This option allows the grouping of `{tm}` modules together to facilitate their retrieval from the `ModStore` and to better visualize them later in a network diagram. 106 | 107 | 108 | ```{r, eval=TRUE} 109 | 110 | ta <- MyMod$new("Tom", group = "A") 111 | tb <- MyMod$new("Tom", group = "B") 112 | 113 | ta 114 | tb 115 | ``` 116 | 117 | As you can see above the group argument is used to define the final namespace Id of the modules. 118 | Those modules share the same name but have different namespace IDs. 119 | 120 | ## module's IDs and UI function 121 | 122 | A `{tm}` module namespace is a concatenation of many fields. 123 | 124 | * Some are optional fields: `name` and `group` 125 | * And some are managed by `{tidymodules}`: `id`, `parent_ns` and `module_ns` 126 | 127 | the module's namespace is built like this 128 | 129 | ``` 130 | id = _ 131 | module_ns = _ 132 | ``` 133 | 134 | ```{r,eval=TRUE} 135 | 136 | #' @field name Name of the module, either generated for or provided by the user. 137 | n$name 138 | ta$name 139 | 140 | #' @field group Group name of the module. 141 | n$group 142 | ta$group 143 | 144 | #' @field id ID of the module. 145 | n$id 146 | ta$id 147 | 148 | #' @field parent_ns Parent module namespace in case of nested modules. 149 | n$parent_ns 150 | ta$parent_ns 151 | 152 | #' @field module_ns Module namespace, unique identifier for the module. 153 | n$module_ns 154 | ta$module_ns 155 | ``` 156 | 157 | Like in conventional module, `{tm}` module also requires wrapping UI elements with the namespace. 158 | The function named `ns()` available in all `{tm}` modules should be used to namespace the inputs. 159 | As you can see in the example below, you simply need to call the function using the `self` R6 keyword. 160 | There is no need to remember the modules's namespace anymore. 161 | 162 | 163 | ```{r,eval=TRUE} 164 | 165 | MyMod <- R6::R6Class( 166 | "MyMod", 167 | inherit = TidyModule, 168 | public = list( 169 | ui = function() { 170 | shiny::tagList( 171 | shiny::numericInput(self$ns("inputId"), NULL, 0) 172 | ) 173 | } 174 | ) 175 | ) 176 | 177 | m <- MyMod$new() 178 | as.character(m$ui()) 179 | ``` 180 | 181 | ## `ModStore` and module lookup 182 | 183 | The `ModStore` is an internal repository for all `{tm}` modules and connections (see [communication article](communication.html) for learning how to connect modules). It is a shared environment created by `{tidymodules}` that orginizes the objects (modules and edges) by applications and sessions. 184 | This allows to track and easily retrieve the modules anywhere in the application. 185 | 186 | All the examples above show the creation of modules with the `new()` R6 function and the assignment to variables (pointers to the R6 objects). 187 | However `{tidymodules}` also offers the choice to not save module references and instead use the `getMod()` or `mod()` utility functions to retrieve existing module. Note that `mod()` is just an alias of `getMod()`. 188 | 189 | ```{r,eval=TRUE} 190 | 191 | MyMod$new("SaveMeInStore") 192 | 193 | # look-up by namespace ID 194 | mod("SaveMeInStore") 195 | 196 | # look-up by index 197 | mod(2) 198 | 199 | # look-up by index within a group 200 | mod(1, group = "A") 201 | ``` 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /vignettes/session.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Session management" 3 | title: "Session management" 4 | subtitle: "
" 5 | author: "Mustapha Larbaoui" 6 | date: "`r Sys.Date()`" 7 | output: rmarkdown::html_vignette 8 | vignette: > 9 | %\VignetteIndexEntry{Session management} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | %\VignetteEncoding{UTF-8} 12 | --- 13 | 14 | ```{r setup, include = FALSE} 15 | knitr::opts_chunk$set( 16 | collapse = TRUE, 17 | comment = "#>" 18 | ) 19 | ``` 20 | 21 | Coming soon... 22 | -------------------------------------------------------------------------------- /vignettes/tidymodules.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Introduction to tidymodules" 3 | title: "Introduction to tidymodules" 4 | author: "Xiao Ni, Mustapha Larbaoui" 5 | date: "`r Sys.Date()`" 6 | output: rmarkdown::html_vignette 7 | vignette: > 8 | %\VignetteIndexEntry{Introduction to tidymodules} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | %\VignetteEncoding{UTF-8} 11 | --- 12 | 13 | ```{r setup, include = FALSE} 14 | knitr::opts_chunk$set( 15 | collapse = TRUE, 16 | comment = "#>" 17 | ) 18 | ``` 19 | 20 | ## Overview 21 | 22 | This vignette aims to provide a high level introduction to tidymodules. We recommend reading this article for anyone who is new to tidymodules, especially those tidymodules module "consumers", who use existing module classes as a "black-box" in their Shiny app development. This article includes the following topics: 23 | 24 | - TidyModule ports: key infrastructure for managing cross-module communication 25 | - How to use tidymodules modules as a Shiny app developer and tidymodules consumer 26 | - Existing examples 27 | 28 | If you would like to develop new tidymodules modules, please refer to the vignettes under "Articles". 29 | 30 | ## TidyModule structure: ports 31 | 32 | ### Introducing ports 33 | 34 | In conventional Shiny modules, communication between modules is realized through the following 35 | 36 | - **Input:** passing reactives as functional parameters into the Shiny `callModule()` function 37 | - **Output:** returning reactives in the module `server()` function. 38 | 39 | It can be challenging to keep track of the arbitrary number of input and output reactives for complex apps with many interacting modules. 40 | 41 | To address this challenge, we introduced input/output ports as fields of the TidyModule class object in tidymodules. 42 | The concept is illustrated in the following diagram. In this example, Module1 and Module2 each have input and output ports that hold reactives. 43 | The ports defined in each modules provides a data structure that allow TidyModule to establish a communication between them. 44 | 45 | ![](figures/ports_intro.png) 46 | 47 | The two modules are connected via a tidymodules pipe operator `%x>y%` where x and y could be any numbers from 1 to 10. For example,`%1>1%` means the left module's first output port (x = 1) is mapped to the first input port (y = 1) of the right module. Multiple connected tidymodules modules with such directed edges form a directed graph network, which will be further discussed later in this article. 48 | 49 | ### Finding out ports of a tidymodules module 50 | 51 | To find out the port structure, simply print the module object on the console. The following example shows that the `Addition` module has one input port named "left" and one output port named "total" that are both empty, i.e. not being assigned an input reactive (input ports) or injected into the server code (output ports). 52 | ```{r} 53 | library(shiny) 54 | library(tidymodules) 55 | source(system.file(package = "tidymodules", "shiny/examples/1_simple_addition/Addition.R")) 56 | Addition$new() 57 | ``` 58 | 59 | 60 | ## Using tidymodules modules 61 | 62 | The basic workflow of using tidymodules modules in a Shiny app is the following 63 | 64 | - Load tidymodules module class definition 65 | - Identify module structure such as input/output ports, ui(), and server() functionalities 66 | - Instantiate new module objects from tidymodules classes 67 | - Construct app `ui()` using module `ui()` methods 68 | - In app `server()` inject tidymodules module `server()` logic using `callModules()` or the `callModule()` function of the module object, like `myMod$callModule()`. 69 | - Set up module communication/relationship via tidymodules pipe operators and functions. 70 | 71 | The workflow is illustrated in the following example, which is available at 72 | 73 | Example 1: Simple addition [](https://tidymodules.shinyapps.io/1_simple_addition/) 74 | 75 | ### Load module definition 76 | 77 | ```{r, eval=FALSE} 78 | library(tidymodules) 79 | # source tidymodules Addition module definition 80 | source(system.file(package = "tidymodules", "shiny/examples/1_simple_addition/Addition.R")) 81 | ``` 82 | 83 | ### Instantiate module objects 84 | 85 | Notice that the namespace argument in `$new()` is optional and `tidymodules` will automatically generate a namespace ID if not provided. 86 | ```{r, eval=T} 87 | # Instantiate two Addition module objects 88 | Addition$new() 89 | Addition$new() 90 | ``` 91 | Also notice that it is not necessary to give a name to the `Addition$new()` object. `tidymodules` provides `mod()` or `getMod()` function to help users conveniently retrieve module objects via their numerical ID or namespace ID. 92 | 93 | ### Adding `ui()` 94 | 95 | In the app `ui()`, we call the `ui()` method of each module object. 96 | ```{r, eval=FALSE} 97 | ui <- fixedPage( 98 | h2("tidymodules : Addition example"), 99 | shiny::fluidRow( 100 | sliderInput("first_number", label = "Enter your first number", min = 1, max = 100, value = 1), br(), 101 | 102 | # Calling module ui() methods 103 | mod(1)$ui(), br(), 104 | mod(2)$ui(), br(), 105 | "Total: ", textOutput("total_result") 106 | ) 107 | ) 108 | ``` 109 | 110 | ### Add module `server()` logic using `callModules()` 111 | Here we use the `callModules()` function to call the server() methods for the two modules that we created. 112 | ```{r, eval=FALSE} 113 | server <- function(input, output, session) { 114 | # call the server() functions for all existing tidymodules modules in the global environment 115 | callModules() 116 | } 117 | ``` 118 | 119 | ### Establish cross-module communication via 'tidy' pipe operators 120 | 121 | The module communication is established through the pipe operators: `first %>1% mod(1) %1>1% mod(2)`. Note that in `first` must be a Shiny reactive value or endpoint in order to server as an input to other tidymodules modules. 122 | ```{r, eval=FALSE} 123 | server <- function(input, output, session) { 124 | # call the server() functions for all existing tidymodules modules in the global environment 125 | callModules() 126 | 127 | first <- reactive({ 128 | req(input$first_number) 129 | }) 130 | 131 | # Setting up module commmunication 132 | observe({ 133 | first %>1% mod(1) %1>1% mod(2) 134 | }) 135 | 136 | output$total_result <- renderText({ 137 | result <- mod(2)$getOutput(1) 138 | result() 139 | }) 140 | } 141 | 142 | shinyApp(ui, server) 143 | ``` 144 | 145 | We also provide utility functions to help identify and connect ports using the port names. For more information about the pipe operators, refer to the functional documentation under "Reference" tab. 146 | 147 | ### Module relational network 148 | 149 | Tidymodules module objects are mananged by `ModStore` in tidymodules. For example, in the 150 | example 2 - Linkled scatter [](https://tidymodules.shinyapps.io/2_linked_scatter/) you can find the sessions, module objects, edges and port mapping diagram in the "Help | ModStore" tab. 151 | 152 | Below is the module relationship network digram generated by `tidymodules` using the `visNetwork` package. 153 | 154 | 155 | 156 | ### Inheritance and Entity Relational Diagram 157 | 158 | For more details about class and ports inheritance, see article [inheritance](inheritance.html). 159 | The diagram below illustrates the relation between the classes defined in the example 4 of tidymodules [](https://tidymodules.shinyapps.io/4_communication/). 160 | 161 | ![](figures/ERD.png) 162 | 163 | ## Other examples 164 | 165 | You can list all Shiny examples that come with the `tidymodules` package by the `showExamples()` function. We recommend going through these examples to help you understand the use patterns. 166 | ```{r} 167 | showExamples() 168 | ``` 169 | 170 | We have already used the first example to illustrate the basic usage of tidymodules, below we briefly describe the other examples. 171 | 172 | ### Example 2: linked scatter plot 173 | 174 | This example illustrates the tidymodules implementation of the classical Shiny module example of [two linked scatter plots](https://shiny.rstudio.com/gallery/module-example.html). 175 | 176 | ### Example 3: nested modules 177 | 178 | This example [](https://tidymodules.shinyapps.io/3_nested_modules/) illustrates constructing and using nested modules in the `tidymodules` framework, as well as dynamically creating tidymodules modules. 179 | 180 | ### Example 4: module communication 181 | 182 | This is a comprehensive example [](https://tidymodules.shinyapps.io/4_communication/) to illustrate mutiple advanced features such as 183 | 184 | - Inheritance 185 | - Port operations: `combine_ports()` 186 | - Enable/disable module communication 187 | --------------------------------------------------------------------------------