Javascript dynamism
This tutorial should follow the first one on Javascript. We will continue the leaflet app by adding new features which require JS to communicate with R, namely:
- A button to reset the view of leaflet to its default;
- A label that will display where the user clicked on the map, by using R.
Note
Please note that the button resetting the view should be implemented directly in Javascript as R is not required for that. But for this tutorial, we will go through R to learn how to communicate between the two languages.
How communication work
For security reasons and good practices, communication between R/Pythong and Javascript are possible through an event system. A packet is send with:
- a
message
, which is a string to indicate which operation to perform; - some
values
in an array or a list, which act as arguments to the message.
When the receiving language gets a packet (message
and values
), an event
is emitted so that the developper can catch the packet and handle it
by executing the corresponding code.
You will likely have a fonction handling a packet, that will call the correct
function of your code depending on the message
and with the values in the values
part.
Resetting the view
In this section, we will learn how to send things from R to JS. First, create a new R script and call it main.R. Add it before the GUI in the sequence file main.pseq:
Now open the main.R file. The main way to communicate with Javascript
is the rpgm.sendToJavascript(message, rData)
function to send a packet
to Javascript with some data that is automatically translated to JS.
Create a setDefaultView()
function and call rpgm.sendToJavascript
.
setDefaultView <- function(){
rpgm.sendToJavascript('resetView'); # resetView doesn't need data, so we did not set the value argument
}
resetView
is just a name we chose, we will do a specific action when
receiving a resetView
message in Javascript.
Go into main.pgui
and add a button calling the R function:
Now we have to receive the message in Javascript by using the didReceiveMessage
event.
Edit the main.js
file and add a function to reset the default view:
function setDefaultView(){
// Check the map exists
if(window.mapInstance){
window.mapInstance.setView([51.505, -0.09], 13);
window.mapInstance.invalidateSize(); // This fixes some grey area sometimes showing in leaflet
}
}
The function simply checks if the map exists and set the view to its default location.
Now we have to call this setDefaultView
function when JS receives the resetView
message:
RPGM.on('didReceiveMessage', (message, rData)=>{
if(message === 'resetView'){
setDefaultView();
}
});
You can test! Execute your app, drag the map and click on the button: the map will center itself on the initial location.
Detecting clicks in R
We will now learn how to send data from JS to R by detecting clicks on the map
and tell R where the click happened. Just like the didReceiveMessage
, we will
create a special function in R that will be called when data is sent from
Javascript.
In main.pgui, create a new label with id lastClick
and a default value:
In the main.js file, add this code at the end of the initializeMap
function
to listen to the click on the map:
// Detect clicks
window.mapInstance.on('click', (e)=>{
RPGM.sendMessage('r', 'mapClick', {latlng: e.latlng.toString()});
});
This code call the function rpgm.sendMessage(language, message, data)
:
- The first argument is the language to send the message to. Either
"r"
or"python"
; - The second argument is the message name that we can choose. Here we chose
"mapClick"
; - The third and last argument is some data, here it's a string representation of the longitude and latitude of the click.
Info
Why is there no rpgm.executeInLanguage(language, code)
function in Javascript?
Because this would open a huge security vulnerability: users could execute any
R or Python code on your computer or in RPGM Server, where even a beginner
computer science student could have a complete control of the server.
We now have to catch this message in R. For that, RPGM will emit an event called
didReceiveMessage
, like in Javascript. To catch event, we give a fonction to
rpgm.on(message, func)
. So, let's create a function in main.R:
rpgm.on('didReceiveMessage', function(message, jsData){
if(message == 'mapClick'){
gui.setValue('this', 'lastClick', jsData$latlng);
}
}
The function checks if the message is called lastClick
, which is what we have
set in the main.js file. If so, set the value of the label to the latlng
data from JS which is a string representation of where the user clicked.
Now test the app:
It works!
Final files
Your R code should look like this:
setDefaultView <- function(){
rpgm.sendToJavascript('resetView'); # resetView doesn't need data, so we did not set the value argument
}
rpgm.on('didReceiveMessage', function(message, jsData){
if(message == 'mapClick'){
gui.setValue('this', 'lastClick', jsData$latlng);
}
}
And the Javascript code:
window.mapInstance = null;
function initializeMap(currentStep){
// Stop if the user is not in the correct GUI
if(currentStep !== 'leaflet'){
return;
}
// Stop if already initialized
if(window.mapInstance !== null){
return;
}
// Initialize map
window.mapInstance = L.map('map');
window.mapInstance.setView([51.505, -0.09], 13);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(window.mapInstance);
// Detect clicks
window.mapInstance.on('click', (e)=>{
RPGM.sendMessage('r', 'mapClick', {latlng: e.latlng.toString()});
});
}
function setDefaultView(){
// Check the map exists
if(window.mapInstance){
window.mapInstance.setView([51.505, -0.09], 13);
window.mapInstance.invalidateSize(); // This fixes some grey area sometimes showing in leaflet
}
}
RPGM.on('didEnterStep', initializeMap); // Function called when entered a new step
RPGM.on('didReceiveMessage', (message, rData)=>{
if(message === 'resetView'){
setDefaultView();
}
});
Advanced topics
You can find advanced topics about the Javascript integration in the next tutorial.