-
Notifications
You must be signed in to change notification settings - Fork 58
/
mobile-going-further.Rmd
154 lines (128 loc) · 5.35 KB
/
mobile-going-further.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
# Fine-tune {shinyMobile} {#mobile-going-further}
## Enhance the disconnect screen
As depicted in Figure \@ref(fig:mobile-disconnect), having the classic Shiny __disconnect__
screen in a mobile device is not that beautiful, especially knowing about all the Framework7
capabilities.
```{r mobile-disconnect, echo=FALSE, fig.cap='Vanilla Shiny disconnect screen.', out.width='40%', fig.align = 'center'}
knitr::include_graphics("images/mobile/mobile-disconnect.png")
```
Let's do better! Upon disconnection, we want to display a toast with two buttons:
- A __reload__ button that reloads the window and re-initializes the app. This button
calls `location.reload()` upon click.
- A __reconnect__ button, that tries to reconnect with the server websocket,
so that we don't lose any input, output elements. This button calls `Shiny.shinyapp.reconnect()` upon click.
How do we know when shiny is disconnected? As described in Chapter \@ref(shiny-intro),
whenever the client socket connection is closed, for any reason, the `shiny:disconnected` [event](https://shiny.rstudio.com/articles/js-events.html) is raised:
```{r, echo=FALSE, results='asis'}
js_code <- "socket.onclose = function() {
// These things are needed only if we've successfully
// opened the websocket.
if (hasOpened) {
$(document).trigger({
type: 'shiny:disconnected',
socket: socket
});
self.$notifyDisconnected();
}
self.onDisconnected(); // run before self.$removeSocket()
self.$removeSocket();
}"
code_chunk_custom(js_code, "js")
```
This allows us to listen to that event on the JS side:
```{r, echo=FALSE, results='asis'}
js_code <- "$(document).on('shiny:disconnected', function(event) {
// Do things
});"
code_chunk_custom(js_code, "js")
```
In the next step, we to remove the default Shiny reconnect elements. They are inserted by the `onDisconnected` method, which adds a disconnect overlay (gray-out screen) and optionally a reconnect notification:
```{r, echo=FALSE, results='asis'}
js_code <- "// From within Shiny.shinyapp...
this.onDisconnected = function() {
// Add gray-out overlay, if not already present
var $overlay = $('#shiny-disconnected-overlay');
if ($overlay.length === 0) {
$(document.body)
.append('<div id=\"shiny-disconnected-overlay\"></div>');
}
// To try a reconnect, both the app (this.$allowReconnect)
// and the server (this.$socket.allowReconnect) must allow
// reconnections, or session$allowReconnect(\"force\") was
// called. The \"force\" option should only be used for
// testing.
if (
(this.$allowReconnect === true &&
this.$socket.allowReconnect === true) ||
this.$allowReconnect === 'force')
{
var delay = reconnectDelay.next();
exports.showReconnectDialog(delay);
this.$scheduleReconnect(delay);
}
}"
code_chunk_custom(js_code, "js")
```
To remove default shiny reconnect elements, there are multiple alternatives. The easiest way is to wait
for the client to be connected, that is listening to `shiny:connected`, and set the `Shiny.shinyapp.onDisconnected` method to only add the gray overlay.
::: {.importantblock data-latex=""}
Before modifying any vanilla shiny elements, make sure to check all the possible
side effects.
:::
```{r, echo=FALSE, results='asis'}
js_code <- "// remove shiny reconnect stuff;
$(document).on('shiny:connected', function(event) {
Shiny.shinyapp.onDisconnected = function() {
// Add gray-out overlay, if not already present
let $overlay = $('#shiny-disconnected-overlay');
if ($overlay.length === 0) {
$(document.body)
.append('<div id=\"shiny-disconnected-overlay\"></div>');
}
};
});"
code_chunk_custom(js_code, "js")
```
We edit the previous disconnected event listener to add a custom Framework7 toast, which closes upon click:
```{r, echo=FALSE, results='asis'}
js_code <- "$(document).on('shiny:disconnected', function(event) {
let reconnectToast = app.toast
.create({
position: 'center',
text:
`Oups... disconnected </br> </br>
<div class=\"row\">
<button
onclick=\"Shiny.shinyapp.reconnect();\"
class=\"toast-button button color-green col\">
Reconnect
</button>
<button
onclick=\"location.reload();\"
class=\"toast-button button color-red col\">
Reload
</button>
</div>`
})
.open();
// close toast whenever a choice is made ...
$('.toast-button').on('click', function() {
reconnectToast.close();
});
});"
code_chunk_custom(js_code, "js")
```
The result is shown in Figure \@ref(fig:mobile-disconnect-custom).
```{r mobile-disconnect-custom, echo=FALSE, fig.cap='Vanilla Shiny disconnect screen.', out.width='40%', fig.align = 'center'}
knitr::include_graphics("images/mobile/mobile-disconnect-custom.png")
```
The above JS code ignores the user reconnect [setup](https://shiny.rstudio.com/articles/reconnecting.html) and proposes reconnecting regardless of the `session$allowReconnect` configuration. If you want to keep the original behavior, you may include the following condition before showing the toast:
```{r, echo=FALSE, results='asis'}
js_code <- "if (
(Shiny.shinyapp.$allowReconnect === true &&
Shiny.shinyapp.$socket.allowReconnect === true) ||
Shiny.shinyapp.$allowReconnect === 'force') {
// Toast logic
}"
code_chunk_custom(js_code, "js")
```