Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

...

What's this for?

This article demonstrate the implementation of the is a guide for implementing web socket support as function into a pluginJoget application.

...

Prerequisites

  1. Identify the type of plugin you want to develop. Refer: Introduction to Plugin Architecture
  2. You are required to have a plugin project ready. Refer: Guideline for Developing a Plugin

Plugin Info

The following is an example of implementing the WebSocket feature inside a radio form element. It showcases one of the ways to create a WebSocket plugin and the code belongs to the plugin class. 

Info

There are 4 key web socket methods that you are required to pay attention to:

public void onOpen(Session session)
- This method will be invoked when a WebSocket connection is opened. It send a specified text message to client side.

public void onMessage(String message, Session session) 
- This method will be invoked when a message is received on the WebSocket. It will send a response back to the client to indicate that the server has received the message.

public void onClose(Session session) 
- This method will be invoked when WebSocket connection is closed. It will log an information message indicating that the connection is closed.

public void onError(Session session, Throwable throwable)
- This method will be invoked when an error occurs in the WebSocket communication. It will log an error message.

Code Block
languagejava
package org.joget;

import java.io.IOException;
import java.util.Map;

import javax.websocket.Session;

import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.form.lib.Radio;
import org.joget.apps.form.model.FormBuilderPalette;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.apps.form.service.FormUtil;
import org.joget.commons.util.LogUtil;
import org.joget.plugin.base.PluginWebSocket;
import org.joget.workflow.model.service.WorkflowUserManager;

public class WebSocketPlugin extends Radio implements PluginWebSocket {

    private final static String MESSAGE_PATH = "message/form/WebSocketPlugin";

    @Override
    public String getName() {
        return "WebSocketPlugin";
    }

    @Override
    public String getVersion() {
        return "5.0.0";
    }

    @Override
    public String getClassName() {
        return getClass().getName();
    }

    @Override
    public String getLabel() {

Examples

Pay attention to the onMessage method, 

Code Block
languagejava
package org.joget;

import java.io.IOException;
import java.util.Map;

import javax.websocket.Session;

import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.form.lib.Radio;
import org.joget.apps.form.model.FormBuilderPalette;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.apps.form.service.FormUtil;
import org.joget.commons.util.LogUtil;
import org.joget.plugin.base.PluginWebSocket;
import org.joget.workflow.model.service.WorkflowUserManager;

public class WebSocketPlugin extends Radio implements PluginWebSocket {

    private final static String MESSAGE_PATH = "message/form/WebSocketPlugin";

    @Override
    public String getName() {
        return "WebSocketPlugin";
    }

    @Override
    public String getVersion() {
        return "5.0.0";
    }

    @Override
    public String getClassName() {
        return getClass().getName();
    }

    @Override
    public String getLabel() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.WebSocketPlugin.pluginLabel", getClassName(), MESSAGE_PATH);
    }

    @Override
    public String getDescription() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.WebSocketPlugin.pluginDesc", getClassName(), MESSAGE_PATH);
    }

    @Override
    public String getPropertyOptions() {
        return AppUtil.readPluginResource(getClass().getName(), "/properties/form/" + getName() + ".json", null, true, MESSAGE_PATH);

    }

    @Override
    public FormRowSet formatData(FormData formData) {

        FormRowSet rowSet = null;

        // get value
        String id = getPropertyString(FormUtil.PROPERTY_ID);
        if (id != null) {
            String value = FormUtil.getElementPropertyValue(this, formData);
        //support i18n
   if (value != null) {
 return AppPluginUtil.getMessage("org.joget.WebSocketPlugin.pluginLabel", getClassName(), MESSAGE_PATH);
    }

    @Override
    //public setString value into Properties and FormRowSet objectgetDescription() {
        //support i18n
       FormRow result = new FormRow(return AppPluginUtil.getMessage("org.joget.WebSocketPlugin.pluginDesc", getClassName(), MESSAGE_PATH);
    }

    @Override
    public String   result.setProperty(id, value);getPropertyOptions() {
        return        rowSet = new FormRowSet();
AppUtil.readPluginResource(getClass().getName(), "/properties/form/" + getName() + ".json", null, true, MESSAGE_PATH);

    }

    @Override
    public FormRowSet formatData(FormData  rowSet.add(result);formData) {

        FormRowSet rowSet =  }null;

        }
// get value
      return rowSet;
 String id  }
= getPropertyString(FormUtil.PROPERTY_ID);
    @Override
    public Stringif renderTemplate(FormDataid formData, Map dataModel!= null) {
            String templatevalue = "webSocketPlugin.ftl"FormUtil.getElementPropertyValue(this, formData);

        WorkflowUserManager wum = (WorkflowUserManager) AppUtil.getApplicationContext().getBean("workflowUserManager"); if (value != null) {
        String    username = wum.getCurrentUsername();
  // set value into Properties  dataModel.put("username", username);

and FormRowSet object
        String html = FormUtil.generateElementHtml(this, formData, template, dataModel      FormRow result = new FormRow();
        return html;
    }

    @Overrideresult.setProperty(id, value);
    public String getFormBuilderTemplate() {
        return "<labelrowSet class='label'>" +new getLabelFormRowSet();
 + "</label>";
    }

    @Override
    public String getFormBuilderCategoryrowSet.add(result) {;
        return FormBuilderPalette.CATEGORY_CUSTOM;
    }

    @Override
    public}
 void onOpen(Session session) {
    return rowSet;
   try {}

    @Override
    public String renderTemplate(FormData formData, Map session.getBasicRemote().sendText("Connection established");dataModel) {
        }String catchtemplate (IOException e) {= "webSocketPlugin.ftl";

        WorkflowUserManager wum = (WorkflowUserManager) eAppUtil.printStackTracegetApplicationContext().getBean("workflowUserManager");
        }
String username =  }wum.getCurrentUsername();
        dataModel.put("username", username);

    @Override
    String publichtml void= onMessage(String message, Session session) {FormUtil.generateElementHtml(this, formData, template, dataModel);
        tryreturn {html;
    }

    @Override
    public String session.getBasicRemotegetFormBuilderTemplate().sendText("Server received: " + message);
 {
        return "<label  } catch (IOException e) {class='label'>" + getLabel() + "</label>";
    }

    @Override
     e.printStackTracepublic String getFormBuilderCategory(); {
         }return FormBuilderPalette.CATEGORY_CUSTOM;
    }

    @Override
    public void onCloseonOpen(Session session) {
        try {
   LogUtil.info(getClassName(), "Webscoket connection closed         session.getBasicRemote().sendText("Connection established");
    }
    
} catch (IOException e) @Override{
    public void onError(Session session, Throwable throwable) {
  e.printStackTrace();
      LogUtil.error(getClassName(), throwable, ""); }
    }

    @Override
    public booleanvoid isEnabled(onMessage(String message, Session session) {
        try if ("true".equalsIgnoreCase(getPropertyString("enableWebsocket"))) {
{
            session.getBasicRemote().sendText("Server received: " return+ truemessage);
        } else catch (IOException e) {
            return falsee.printStackTrace();
        }
    }


}

Figure 1: 

Code Block
languagejs
<div class="form-cell" ${elementMetaData!}>
    <label class="label">    @Override
    public void onClose(Session session) {
        LogUtil.info(getClassName(), "Webscoket connection closed");
    }
    ${element.properties.label} <span class="form-cell-validator">${decoration}</span>
    @Override
    public void onError(Session  <#if error??>session, Throwable throwable) {
        LogUtil.error(getClassName(), throwable, "");
   <span class="form-error-message">${error}</span>}

    public boolean   </#if>isEnabled() {
    </label>
    <#ifif (element.properties.enableWebsocket! == 'true')>
"true".equalsIgnoreCase(getPropertyString("enableWebsocket"))) {
          <input id="isEnabled" name="isEnabled" type="hidden" /> return true;
    </#if>

    <#if (element.properties.readonly! == 'true' && element.properties.readonlyLabel! == 'true') >
} else {
            return false;
 <div class="form-cell-value"><span>${value!?html}</span></div>
      }
  <input id="${elementParamName!}" }

}

FTL file is required for rendering the HTML content for the WebSocket plugin, you may copy the following code as a starting template.

Info

The endpoint is specified at this line:

Code Block
const ws = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "${request.contextPath}/web/socket/plugin/org.joget.WebSocketPlugin");

The following 4 functions are event handlers that handles different WebSocket Events :

ws.onopen = function(event)
ws.onmessage = function(event)
ws.onclose = function(event)
ws.onError = function(event)

You may make changes to any of the FTL code as it suits.


Code Block
languagejs
<div class="form-cell" ${elementMetaData!}>
    <label class="label">
        ${element.properties.label} <span class="form-cell-validator">${decoration}</span>
name="${elementParamName!}" type="hidden" value="${value!?html}" />
    <#else>    
        <input type="text" id="messageInput" placeholder="Enter message">
        <button id="sendButton">Send</button>
        <button id="closeButton">Close</button><br>
        <div id='output'></div>
    </#if>

    <script>
    $(document).ready(function() {
        $("#sendButton").off("click");
        $("#closeButton").off("click");

        if ($("#isEnabled").length > 0) {

<#if error??>
            const<span ws = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "${request.contextPath}/web/socket/plugin/org.joget.WebSocketPlugin");

            ws.onopen = function(event) {class="form-error-message">${error}</span>
        </#if>
    </label>
    <#if (element.properties.enableWebsocket! == 'true')>
        <input id="isEnabled" name="isEnabled" type="hidden" />
    console.log(event);</#if>

    <#if (element.properties.readonly! == 'true' && element.properties.readonlyLabel! == 'true') >
    $("#output").append('Connection opened with timeStamp: ' + event.timeStamp + '<br/>');<div class="form-cell-value"><span>${value!?html}</span></div>
   
     <input           $("#sendButton").on("click", function(){
   id="${elementParamName!}" name="${elementParamName!}" type="hidden" value="${value!?html}" />
    <#else>    
        <input type="text" id="messageInput" placeholder="Enter message">
  const message = $("#messageInput").val();
    <button id="sendButton">Send</button>
        <button id="closeButton">Close</button><br>
           //send message to endpoint<div id='output'></div>
    </#if>

    <script>
            ws.send(message);  $(document).ready(function() {
        $("#sendButton").off("click");
            $("#output#closeButton").appendoff("${username} send " + message + '<br/>'); click");

                    if ($("#messageInput#isEnabled").val("");
   length > 0) {

            const ws = new  return false;
        WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "${request.contextPath}/web/socket/plugin/org.joget.WebSocketPlugin");

            ws.onopen = });

function(event) {
                $("#closeButton").on("click", function(){console.log(event);
                $("#output").append('Connection opened with timeStamp: if (ws.readyState === WebSocket.OPEN) {' + event.timeStamp + '<br/>');
   
                     //close the endpoint connection$("#sendButton").on("click", function(){
                    const message   ws.close= $("#messageInput").val();

                    } //send message to endpoint
                    $("#sendButton").hide(ws.send(message);  
                    $("#closeButton#output").hide();append("${username} send " + message + '<br/>'); 
                    return false;$("#messageInput").val("");
         
           return false;
    });
            }); 

            ws.onmessage   = function$(event) "#closeButton").on("click", function(){
                $("#output").append(event.data + '<br/>');    if (ws.readyState === WebSocket.OPEN) {
            }; 

            ws.onclose = function(event) {//close the endpoint connection
                $("#output").append('Connection closed with timeStamp: ' + event.timeStamp + '<br/>' ws.close();
                $("#output").append('WebSocket closed<br/>');
   }
         }; 

          $("#sendButton").hide();
  ws.onError = function(event) {
                $("#output#closeButton").append("Error: " + event.data + '<br/>'hide();
            };  
      return false; 
      
  } else {
            $("#output").html('WebSocket is not enable.<br>');
});
            }; 

        }
    ws.onmessage = });
function(event) {
       </script>
    <div style="clear:both;"></div>
</div>     $("#output").append(event.data + '<br/>');
            }; 

            ws.onclose = function(event) {
                $("#output").append('Connection closed with timeStamp: ' + event.timeStamp + '<br/>');
                $("#output").append('WebSocket closed<br/>');
            }; 

            ws.onError = function(event) {
                $("#output").append("Error: " + event.data + '<br/>');
            };  
        
        } else {
            $("#output").html('WebSocket is not enable.<br>');
        }
    });
    </script>
    <div style="clear:both;"></div>
</div>

Sample 

This sample plugin code is available at https://github.com/jogetoss/sample-web-socket-plugin. JogetOSS is a community-led team for open source software related to the Joget no-code/low-code application platform. Projects under JogetOSS are community-driven and community-supported, and you are welcome to contribute to the projects.