-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathREADME.Rmd
265 lines (218 loc) · 6.72 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```
# shinyComponent
<!-- badges: start -->
[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
[![R-CMD-check](https://github.com/RinteRface/shinyComponent/workflows/R-CMD-check/badge.svg)](https://github.com/RinteRface/shinyComponent/actions)
<!-- badges: end -->
App powered by {golem}, webpack, Framework7 components (esm). WIP
App is deployed on [shinyapps.io](https://dgranjon.shinyapps.io/shinyComponent/).
## Installation
You can install the released version of shinyComponent from [CRAN](https://CRAN.R-project.org) with:
``` r
remotes::install_github("RinteRface/shinyComponent")
```
## Proof of Concept
Interface is built with Framework 7 from JS, powered by webpack. We leverage
Template components to create the app root and subroutes. On the server side, we process data from R and send them back to JS to update the components.
### UI
Components may be written with JSX (supported by the framework7-loader, no need to install `React`!), which is more convenient than the classic template syntax. Components have either the old/new syntax so you can compare both approaches.
### Main page
App component is driven by:
```jsx
import ListItem from './custom-list.f7.jsx';
export default (props, {$f7, $f7ready, $on, $update }) => {
const title = 'Hello World';
let names = ['John', 'Vladimir', 'Timo'];
Shiny.addCustomMessageHandler('init', function(message) {
names = message;
$update();
});
// App events callback
$on('click', () => {
// callback
});
// This method need to be used only when you use Main App Component
// to make sure to call Framework7 APIs when app initialized.
$f7ready(() => {
// do stuff
console.log('Hello');
});
const openAlert = () => {
$f7.dialog.alert(title, function() {
// ok button callback
Shiny.setInputValue('alert_opened', false);
});
Shiny.setInputValue('alert_opened', true);
Shiny.setInputValue(
'alert',
{
message: 'Alert dialog was triggered!',
title: title,
},
{priority: 'event'}
);
}
const openPanel = () => {
$f7.panel.open('.panel-left');
}
return () => (
<div id="app">
<div class="panel panel-left panel-init">
<div class="block">"Hello"</div>
</div>
<div class="view view-main view-init safe-areas">
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">{title}</div>
</div>
</div>
<div class="toolbar toolbar-bottom">
<div class="toolbar-inner">
<a onClick={() => openAlert()}>Open Alert</a>
<a class="button button-fill" href="#" class="panel-open" data-panel=".panel-left">Left Panel</a>
<a href="/extra/" data-transition="f7-cover">New Page</a>
</div>
</div>
<div class="page-content">
<ul>
<ListItem title="Item 1" />
<ListItem title="Item 2" />
<ListItem title="Item 3" />
</ul>
<div class="list simple-list">
<ul>
{names.map((name) =>
<li>{name}</li>
)}
</ul>
</div>
</div>
</div>
</div>
</div>
)
}
```
Below is the code a of local sub-component, being invoked in the main App template:
```jsx
const ListItem = (props) => {
return () => <li>{props.title}</li>;
}
export default ListItem
```
### Router
This app requires a router to navigate between pages. This is done with the Framework7 builtin feature. You may pass entire app components to the router, as shown below.
```js
import Extra from '../components/extra.f7.jsx';
export default [
//{
// path: '/'
//},
{
path: '/extra/',
asyncComponent: () => Extra
}
]
```
### App init
Importantly, we only import Framework7 modules we need, to lighten the final bundle:
```js
import Dialog from 'framework7/esm/components/dialog/dialog.js';
import Gauge from 'framework7/esm/components/gauge/gauge.js';
import Panel from 'framework7/esm/components/panel/panel.js';
import View from 'framework7/esm/components/view/view.js';
Framework7.use([Dialog, Gauge, Panel, View]);
```
App UI is initialized passing the main app component, the routes and targeting the `#app` element, located within the `app_ui()` function:
```r
app_ui <- function(request) {
tagList(
# Leave this function for adding external resources
golem_add_external_resources(),
# Your application UI logic
tags$body(
div(id = "app"),
tags$script(src = "www/index.js")
)
)
}
```
Since the JS assets have to go after the `#app` element in the `body` tag, we had to comment out the `{golem}` predefined script:
```r
tags$head(
favicon(),
#bundle_resources(
# path = app_sys('app/www'),
# app_title = 'shinyFramework7'
#)
# Add here other external resources
# for example, you can add shinyalert::useShinyalert()
)
```
Whole `index.js` code:
```js
import 'shiny';
// Import Framework7
import Framework7 from 'framework7';
// Import Framework7 Styles
import 'framework7/framework7-bundle.min.css';
// Install F7 Components using .use() method on class:
import Dialog from 'framework7/esm/components/dialog/dialog.js';
import Gauge from 'framework7/esm/components/gauge/gauge.js';
import Panel from 'framework7/esm/components/panel/panel.js';
import View from 'framework7/esm/components/view/view.js';
Framework7.use([Dialog, Gauge, Panel, View]);
// Import App component
import App from './components/app.f7.jsx';
// Import other routes
import routes from './modules/routes.js';
// Initialize app
var app = new Framework7({
el: '#app',
theme: 'ios',
// specify main app component
routes: routes,
component: App
});
```
### Server
On the server side (R):
```r
observeEvent(TRUE, {
session$sendCustomMessage("init", colnames(mtcars))
})
observeEvent(input$alert, {
message(sprintf("Received from JS: %s", input$alert$message))
message(sprintf("App title is %s", input$alert$title))
})
observe({print(input$alert_opened)})
```
## Example
This is a basic example which shows you how to solve a common problem:
### Run app
```{r, eval=FALSE}
library(shinyComponent)
## basic example code
run_app()
```
### Dev mode
```{r, eval=FALSE}
library(shinyComponent)
## basic example code
packer::bundle_dev()
devtools::load_all()
run_app()
```