I am currently trying to develop a minimal working CustomMessageHandling dashbord in R. What I am doing is simply sending a message from my R client side to my Javascript file, which is then run in an html file. The error is as follows:
jQuery.Deferred exception: Shiny is not defined ReferenceError: Shiny is not defined
In my JavaScript file, which is called in my .html file, I simply add it at the bottom of the file, which looks like:
$(document).on('shiny:connected', function() {
console.log("Hello, I am executing!");
const clientID = "e800d12fc12c4d60960778b2bc4370af";
var urlToBase64PDF;
Shiny.addCustomMessageHandler('handler1', function()
{
doUpdate();
}
);
function base64ToArrayBuffer(base64)
{
var bin = window.atob(base64);
var len = bin.length;
var uInt8Array = new Uint8Array(len);
for (var i = 0; i < len; i++)
{
uInt8Array[i] = bin.charCodeAt(i);
}
return uInt8Array.buffer;
}
function doUpdate(message1)
{
urlToBase64PDF = message1;
}
document.write(urlToBase64PDF);
console.log(urlToBase64PDF);
document.addEventListener("adobe_dc_view_sdk.ready", function()
{
var adobeDCView = new AdobeDC.View({clientId: clientID, divId: "adobe-dc-view"});
document.write(urlToBase64PDF);
console.log(urlToBase64PDF);
adobeDCView.previewFile({content:{ promise: Promise.resolve(base64ToArrayBuffer(urlToBase64PDF))}, metaData:{fileName: "check.pdf"}},
{});
});
});
In my .html file, I call it in the following fashion:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1"/>
<meta id="viewport" name="viewport" content="width=device-width, initial-scale=1"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
The rest of the functionality is built in Shiny in R, which are extremely simple UI and Server parts:
app_ui <- function() {
shiny::addResourcePath(
'www', system.file('app/www', package = 'test')
)
tags$iframe(src="www/index.html", height = 600, width = 600)
and lastly the server part:
app_server <- function(input, output, session){
shinyjs::useShinyjs()
message1 = "test"
session$sendCustomMessage("handler1", message1)
}
I have literally tried everything, searched everywhere, and even the documentation on CustomMessageHandling sends messages in the above fashion. Yet I still get the Shiny undefined Error in my console.
Edit: Exact error:
No output to console at all.
You can't use variables from the parent frame in embedded iframe
. This is due to security reasons. So Shiny
is never defined in iframe
and any shiny
event cannot be listened in iframe
.
what you see is following:
console.log("script starts")
$(document).on('shiny:connected', function() {
console.log("oh yeah")
// do some other things
});
console.log("script ends")
// index.js:1 script starts
// index.js:6 script ends
You can see the middle part is never run, because there is no shiny:connected
event at all. When you are listening to unknown events, sadly it doesn't report any error messages.
Change to this makes it clearer:
console.log("script starts")
$(function(){
console.log(Shiny)
})
console.log("script ends")
index.js:1 script starts
index.js:5 script ends
jquery.min.js:2 jQuery.Deferred exception: Shiny is not defined ReferenceError: Shiny is not defined
at HTMLDocument.<anonymous> (http://127.0.0.1:3168/www/index.js:3:17)
at e (https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js:2:30005)
at t (https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js:2:30307) undefined
jquery.min.js:2 Uncaught ReferenceError: Shiny is not defined
at HTMLDocument.<anonymous> (index.js:3:17)
at e (jquery.min.js:2:30005)
at t (jquery.min.js:2:30307)
Even we waited for the document to be ready, you can see there is still no Shiny
.
Then you will ask so where does the jquery
comes from. Well, you re-imported in your index.html
: <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
. That's why you can use jquery
but not Shiny
.
A simple solution is to use serverside rendering, so it makes sure the iframe is set up after Shiny is initialized.
library(shiny)
addResourcePath('www', "./")
ui <- fluidPage(
uiOutput("iframe")
)
server <- function(input, output, session) {
output$iframe <- renderUI({
tags$iframe(src="www/index.html", height = 600, width = 600)
})
}
shinyApp(ui, server)
This only solves part of your problem. I see you have a custom handler that sends out an update command based on some other things that are controlled by Shiny. In this case, there is no easy solution, you need cross-origin communication.
In the next example, I use a button to simulate your update event and it is controlled by shiny observeEvent
. Once clicked, it sends the sendCustomMessage
. On UI, we add some script to listen to this message and then, dispatch the event to iframe
by postMessage
.
library(shiny)
addResourcePath('www', "./")
ui <- fluidPage(
uiOutput("iframe"),
actionButton("update", "update"),
tags$script(HTML(
"
Shiny.addCustomMessageHandler('handler1', function(data){
if(data.msg !== 'update') return ;
$('#myiframe')[0].contentWindow.postMessage(data.msg, '*');
});
"
))
)
server <- function(input, output, session) {
output$iframe <- renderUI({
tags$iframe(id = "myiframe", src="www/index.html", height = 600, width = 600)
})
observeEvent(input$update, {
session$sendCustomMessage("handler1", list(msg = "update"))
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
In iframe index.js
we use window.onmessage
listener to catch our message
console.log("script starts")
$(function(){
window.addEventListener("message", (e) => {
if (e.data === 'update') $('body').append('<h1>Oh yeah !</h1>');
});
})
console.log("script ends")
This example appends a h1
every time you click update
from the parent site to the iframe.