27 Going further

27.1 Improved disconnect screen

As depicted on Figure 27.1, having the classic Shiny disconnect screen in a mobile device is not that beautiful, especially knowing about all the Framework7 capabilities.

Vanilla Shiny disconnect screen.

FIGURE 27.1: Vanilla Shiny disconnect screen.

Let’s do better! Upon disconnection, we want to display a toast with two buttons:

  • A reload button that reloads the window and re-initialize 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 11, whenever the client socket connection is closed, for any reason, the shiny:disconnected event is raised:

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(); // Must be run before self.$removeSocket()
  self.$removeSocket();
}

This allows us to listen to that event on the JS side:

$(document).on("shiny:disconnected", function(event) {
  // Do things
});

In the next step, we to remove the default shiny reconnect elements. They are inserted by the onDisconnected method, that adds a disconnect overlay (gray-out screen) and optionally a reconnect notification:

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);
  }
}

To remove them, 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.

Before modifying any vanilla shiny elements, make sure to check all the possible side effects!

// 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>');
    }
  };
});

We edit the previous disconnected event listener to add a custom Framework7 toast, which closes upon click:

$(document).on("shiny:disconnected", function(event) {    
  let reconnectToast = app.toast
    .create({
      icon: '<i class="icon f7-icons">bolt_fill</i>',
      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();
  });
});

Result is shown Figure 27.2.

Vanilla Shiny disconnect screen.

FIGURE 27.2: Vanilla Shiny disconnect screen.

The above JS code ignores the user reconnect setup and proposes to reconnect regardless of the session$allowReconnect configuration. If you want to keep the original behavior, you may include the following condition before showing the toast:

if (
  (Shiny.shinyapp.$allowReconnect === true && 
  Shiny.shinyapp.$socket.allowReconnect === true) ||
      Shiny.shinyapp.$allowReconnect === "force") {
  // Toast logic
}