Skip to content

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:

Added R file to sequence

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:

R Button

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:

Click label

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:

Click working

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: '&copy; <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.