Deep Dive Into Building OutSystems React Plugins

Welcome to Part III of our blog series on OutSystems development and how to create plugins. You can read Part I here and part II here.
 

Over the course of this series you will have seen the various options that OutSystems provides to build plugins. There are a heap of benefits to the using Reactive Web plugin, not least speed and performance and a better UX and in this instalment I’ll walk you through the process of converting a WebForms OutSystems forge plugin to a Reactive Web plugin. Let's get started!

What is different in React?

Aside from being a SPA framework, there are many differences. The ones affecting the way the plugin is written are:
 
  • Pages and blocks do not have a preparation running synchronously when the page loads, they have events which run asynchronously.
  • Client actions are available which run in the browser, giving more compile time checking to JavaScript code.
  • Client actions may contain JavaScript blocks which can also execute JavaScript from other components, such as another library.
  • Client actions may be passed to JavaScript functions and can then be called as a callback, passing another JavaScript function as another callback using the Object data type.
  • GetSessionId() does not return the session ID. This meant I could not restrict uploads to a single session for a user.
  • In client-side JavaScript blocks, when assigning a string to a Binary Data variable, the string is assumed to be in base64 format, and is converted to binary data. Very helpful!

What does the plugin do?

The ReactFilePondUpload plugin essentially it integrates the awesome FilePond javascript library written by Rik Schennink. It uploads files asynchronously. So multiple files will upload at the same time. There are several options including image preview and editing.

It works by uploading a file to temporary database storage and returns a unique token identifying the file. Then the consumer component passes the tokens to a server action which retrieves the uploaded files by specifying the tokens.

There are two web blocks. The UploadBlock requires the user to click submit to trigger file processing. The AutoUploadBlock has a callback that fires when all files are uploaded, so the user does not have to do anything more than dropping the files on the widget.

RFPU_UserInteraction-Stuart-Harris-(1).png

 

The WebForms Plugin

First, let’s look at the FilePondUpload plugin. In this plugin, the tokens are shared with the consumer via a hidden input box. When the user submits the form, the value of the input box is also submitted. So the consumer can then use the variable associated with the input box to retrieve uploaded files.

As you might expect, the JavaScript library requires a server-side endpoint to accept the uploaded files. The WebForms plugin was able to use a web page as the target of the standard HTTP upload method via a slightly misused Preparation.

The React Plugin

The React Plugin has the same web blocks and configuration, but no placeholder for an input control to retrieve the tokens. As we have client actions, we can interact with the FilePond JavasSript library by calling functions directly, or at least via the FilePondUploadJS integration script.

Rather than tell what would undoubtedly be a gripping tale of the curious things I tried, how I failed many times, only to triumph in the end; maybe I should spare you with just the short version. So this is how it ended up.

Initialise the plugin

The configuration is a FilePondUploadConfig structure supplied as a parameter to the UploadBlock web block. To pass the structure to the integration script, convert it to a JSON string with the JSONSerialize widget. Pass the output of JSONSerialize into a JavaScript block as Text. Then convert it to a JavaScript Object with JSON.parse(). This might seem a bit weird, but if we just passed the object directly we would be working with the internals of an OutSystems structure object. I avoided relying on internals to avoid breaking changes.

Var configObject = JSON.parse($parameters.UploadConfigJson);
var containerId = $parameters.UploadContainerId;
var isAuto = $parameters.IsAutoUpload;
var uploadCallback = $actions.UploadFile;
var uploadedCallback = $actions.NotifyUploaded;
var rejectCallback = $actions.RejectFile;
FilePondUpload.configure(containerId, configObject, isAuto, uploadCallback, uploadedCallback, rejectCallback);

The FilePondUpload JavaScript object is the integration layer between OutSystems widgets and the FilePond javascript library. The configure function initialises the FilePond control and sets up all the events and callbacks.

Upload files

When a file is ready for upload, the FilePond library uses the process configuration option. This can be a string specifying an endpoint that can accept uploaded files. However, this does not work for React Web Apps, as pages do not have Preparations, and APIs do not return simple text responses. Thanks to the flexibility offered by FilePond, for React I was able to use a custom javascript function to perform the upload.

The process function has to read the file using a standard FileReader object. The file is retrieved in base64 format which is passed to an OutSystems client action. I have removed error handling in the below code to simply it.

var fileReader = new FileReader();
// Set up callback when the file is loaded
fileReader.onload = function(frEvt) {
  var base64 = fileReader.result;
  // Remove the base64 prefix
  base64 = base64.replace(/^data:[^;]+;base64,/,'');

  var successCallback = function(token) {
    load(token);
  };

  // Callback to actually upload the file
  sendFileCallback(file.name, file.type, base64, successCallback, errorCallback);
};
// Now read the file!
fileReader.readAsDataURL(file);


Once we have the file, the UploadFile client action is called. We can call this because a reference to this function was passed into the FilePondUpload configure function.

UploadFile calls SendFile which is a server action. The SendFile action accepts a BinaryData object. Fortunately, assigning a base64 encoded string to a BinaryData object converts the string into binary.  Side note – a big thanks to Miguel Vincente’s forge plugin to show me the way!

SendFile stores the uploaded file and generates a unique token. Hooray, we’re half-way there! 

We now just have to get the token back to the FilePond library. The call to SendFile is synchronous, so UploadFile has been waiting for a response. SendFile returns the token. UploadFile was given a success callback, which accepts a token and will in turn call load(token) telling FilePond that the file has been safely uploaded.

Unfortunately, as the call to SendFile is synchronous, we have no way of reporting progress as the file is uploaded. So the control will just show that the file is uploading, but it will not show what percentage has been uploaded.

You can see a full summary of this upload sequence in the below image. 

RFPU_Upload_Sequence-Diagram-Stuart-Harris-(1).png


How to retrieve the file

A significant benefit Reactive Web Apps gives us is the ability to integrate directly with JavaScript code without having to resort to indirect practices such as using JSON in hidden input controls as an interface.

Files are retrieved by calling FilePondUpload.getTokens(), which is nicely wrapped behind a client action called FilePondUpload_GetTokens(). The consumer then calls a server function to perform its processing and calls the FilePondUpload_GetUploadedFiles() server action to retrieve the files.

Reset the widget

The final step is to reset the widget. The consumer must call the FilePondUpload_Reset() client action which calls the FilePondUpload.reset() function to clear the uploaded files from the widget. Congratulations - we're all done! 

React makes it easier! As you can seethere were a few hoops to jump through, but you can see the interface to the plugin is much simpler for the developer using the plugin. Being able to work within OutSystems in client side code offers a cleaner and safer way to integrate with javascript controls. Hopefully, as React Web Apps mature in the OutSystems platform, there will also be a way to monitor progress of a call to the server.

I hope you have found this helpful in building your own plugins or at least understanding a bit more about React Web Apps. Happy coding!