<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Explorer</title>
    <description>The latest articles on DEV Community by Explorer (@exploringmylifeworks).</description>
    <link>https://dev.to/exploringmylifeworks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3593062%2F8342d187-9126-488c-8cf5-aa6e3da66453.png</url>
      <title>DEV Community: Explorer</title>
      <link>https://dev.to/exploringmylifeworks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/exploringmylifeworks"/>
    <language>en</language>
    <item>
      <title>Dynamic BeanShell Hash Invocation in Joget</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 26 Apr 2026 12:00:09 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/dynamic-beanshell-hash-invocation-in-joget-8kp</link>
      <guid>https://dev.to/exploringmylifeworks/dynamic-beanshell-hash-invocation-in-joget-8kp</guid>
      <description>&lt;h2&gt;
  
  
  1. Why This Is Useful
&lt;/h2&gt;

&lt;p&gt;When you need to call another BeanShell helper dynamically, hash invocation keeps your main script small and reusable.&lt;/p&gt;

&lt;p&gt;This pattern builds a hash string at runtime, executes it with &lt;code&gt;AppUtil.processHashVariable&lt;/code&gt;, and captures the output for reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where to Use in Joget
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Workflow Tool scripts that need reusable helper logic.&lt;/li&gt;
&lt;li&gt;Form/Post-processing scripts that call another BeanShell utility.&lt;/li&gt;
&lt;li&gt;Notification-related scripts where output from another function is needed first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Core Idea
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build the hash string with required parameters.&lt;/li&gt;
&lt;li&gt;Execute it through &lt;code&gt;AppUtil.processHashVariable(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use returned value in logs, payloads, or downstream logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example hash format used:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#beanshell.captureResponseNotification[process_id=YOUR_PROCESS_ID]#&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Full Working Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.model.AppDefinition&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.WorkflowAssignment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.dao.UserReplacementDao&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.model.UserReplacement&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.directory.model.User&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.directory.model.service.DirectoryManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.WorkflowProcess&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.service.WorkflowManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.util.WorkflowUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Collection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONObject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONArray&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.HttpURLConnection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.URL&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.OutputStream&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.charset.StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.InputStreamReader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.BufferedReader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.File&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.service.FileUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Base64&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.file.Files&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.file.Path&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.nio.file.Paths&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;


&lt;span class="cm"&gt;/*
// c_code #variable.branch_code# and locale
public String getBranchName(String locale){
    String callHashToInsertResponse = 
    "#beanshell.getComplaintDetailsForEmail[c_code=" + "#variable.branch_code#" + "&amp;amp;locale="+ locale + "]#";

    // Evaluate hash
    String result = AppUtil.processHashVariable(callHashToInsertResponse, null, null, null);

    return result;
}
*/&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nc"&gt;ProcessID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"testing the process id"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// build valid hash format&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;callHashToInsertResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="s"&gt;"#beanshell.captureResponseNotification[process_id="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;ProcessID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"]#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Evaluate hash&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processHashVariable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callHashToInsertResponse&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Email Integration - API"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Evaluated Hash = "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Quick Validation Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Verify the target BeanShell function name is correct.&lt;/li&gt;
&lt;li&gt;Confirm all required parameters are included in the hash.&lt;/li&gt;
&lt;li&gt;Log output once to validate returned value.&lt;/li&gt;
&lt;li&gt;Test with safe sample process IDs before production use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Security and Publishing Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replace real process IDs, variable names, and function names with placeholders in public content.&lt;/li&gt;
&lt;li&gt;Avoid exposing internal workflow naming conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Final Note
&lt;/h2&gt;

&lt;p&gt;This is a strong pattern for reusable Joget scripting: one script orchestrates, another script handles details, and hash invocation connects both.&lt;/p&gt;

&lt;p&gt;Follow-up topic: Building a shared BeanShell utility library for multi-process reuse.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Convert Joget Signature Field to Image (Generic Dynamic Table, Field, and ID)</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 26 Apr 2026 11:55:35 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/convert-joget-signature-field-to-image-generic-dynamic-table-field-and-id-4bg3</link>
      <guid>https://dev.to/exploringmylifeworks/convert-joget-signature-field-to-image-generic-dynamic-table-field-and-id-4bg3</guid>
      <description>&lt;h2&gt;
  
  
  1. Why This Is Useful
&lt;/h2&gt;

&lt;p&gt;Joget signature fields store stroke data (JSON), but many use cases need a real image output.&lt;/p&gt;

&lt;p&gt;This script converts signature JSON into a PNG image and returns it as a Base64 data URL.&lt;/p&gt;

&lt;p&gt;The best part is that it uses dynamic &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;field&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt; parameters, so you can reuse the same code anywhere without rewriting logic.&lt;/p&gt;

&lt;p&gt;Use this when you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preview a signature directly in a form or report,&lt;/li&gt;
&lt;li&gt;embed signature image in email/PDF templates,&lt;/li&gt;
&lt;li&gt;avoid custom external conversion services.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Where to Use in Joget
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Form Builder: BeanShell script in a formatter/element that needs image output.&lt;/li&gt;
&lt;li&gt;Userview/Report: render returned data URL as image source.&lt;/li&gt;
&lt;li&gt;Workflow tools: generate signature image before notifications or documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Required Input Parameters
&lt;/h2&gt;

&lt;p&gt;Pass these parameters to the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;table&lt;/code&gt;: form table name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;field&lt;/code&gt;: signature field column&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;: record ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because these are dynamic inputs, the same script can work across multiple forms and modules.&lt;/p&gt;

&lt;p&gt;Example usage pattern:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#beanshell.generateSignature[table=your_table&amp;amp;field=your_signature_field&amp;amp;id=your_record_id]#&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Full Working Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.Connection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.PreparedStatement&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.ResultSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.sql.DataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.awt.BasicStroke&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.awt.Color&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.awt.Graphics2D&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.awt.RenderingHints&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.awt.image.BufferedImage&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONArray&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONObject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.imageio.ImageIO&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.URLDecoder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Base64&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;// Input parameters&lt;/span&gt;
&lt;span class="c1"&gt;// #beanshell.generateSignature[&lt;/span&gt;
&lt;span class="c1"&gt;//   table=app_fd_cpa_comp_question&amp;amp;&lt;/span&gt;
&lt;span class="c1"&gt;//   field=c_consumer_signature&amp;amp;&lt;/span&gt;
&lt;span class="c1"&gt;//   id=UUID&lt;/span&gt;
&lt;span class="c1"&gt;// ]#&lt;/span&gt;
&lt;span class="c1"&gt;// --------------------------------------------------&lt;/span&gt;


&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateJSON&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;callHashToInsertResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="s"&gt;"#form."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"["&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"]?url#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Evaluate hash&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processHashVariable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callHashToInsertResponse&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;



&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"generateSignature"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Params -&amp;gt; table="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", field="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", id="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Image URL : "&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;generateJSON&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;


&lt;span class="c1"&gt;//Here defines the signature image size&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;jsonstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;URLDecoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateJSON&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recordId&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;JSONArray&lt;/span&gt; &lt;span class="n"&gt;jarr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonstr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;BufferedImage&lt;/span&gt; &lt;span class="n"&gt;offscreenImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BufferedImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BufferedImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TYPE_INT_RGB&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Graphics2D&lt;/span&gt; &lt;span class="n"&gt;g2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;offscreenImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createGraphics&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setColor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fillRect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPaint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;black&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStroke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BasicStroke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRenderingHint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KEY_ANTIALIASING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RenderingHints&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VALUE_ANTIALIAS_ON&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;jarr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JSONObject&lt;/span&gt; &lt;span class="n"&gt;jobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jarr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;g2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drawLine&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lx"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ly"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mx"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;ByteArrayOutputStream&lt;/span&gt; &lt;span class="n"&gt;baos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ByteArrayOutputStream&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;ImageIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUseCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;ImageIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="n"&gt;offscreenImage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"png"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baos&lt;/span&gt; &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;baos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;imageInByte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toByteArray&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;baos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;base64bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEncoder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;encodeToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageInByte&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data:image/png;base64,"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64bytes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Quick Validation Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Signature exists in selected record.&lt;/li&gt;
&lt;li&gt;Script returns value starting with &lt;code&gt;data:image/png;base64,&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Image renders correctly in form/userview/report.&lt;/li&gt;
&lt;li&gt;Empty/invalid signature JSON is handled safely in your UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Security and Publishing Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replace real table names, field IDs, and record IDs with placeholders in public posts.&lt;/li&gt;
&lt;li&gt;Do not publish internal schema naming conventions if they reveal business context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Final Note
&lt;/h2&gt;

&lt;p&gt;This is a practical conversion pattern when your workflow needs image output from Joget signature strokes without adding external services.&lt;/p&gt;

&lt;p&gt;Follow-up topic: Auto-attach generated signature images in Joget email notifications.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>java</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Trigger Previous Activity in Joget (Controlled Rollback Script)</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 26 Apr 2026 11:25:07 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/trigger-previous-activity-in-joget-controlled-rollback-script-43bn</link>
      <guid>https://dev.to/exploringmylifeworks/trigger-previous-activity-in-joget-controlled-rollback-script-43bn</guid>
      <description>&lt;h2&gt;
  
  
  1. Why This Script Is Useful
&lt;/h2&gt;

&lt;p&gt;Sometimes a running process reaches a wrong step because of data issues, user mistakes, or production fixes.&lt;/p&gt;

&lt;p&gt;This script lets you rollback selected process instances by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;matching only specific running instances,&lt;/li&gt;
&lt;li&gt;aborting from a known current activity,&lt;/li&gt;
&lt;li&gt;resuming at a target activity,&lt;/li&gt;
&lt;li&gt;restoring workflow variables when possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Where to Use in Joget
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Workflow Builder context: execute as an admin/maintenance script.&lt;/li&gt;
&lt;li&gt;Best use case: production correction for specific process instances.&lt;/li&gt;
&lt;li&gt;Not a form validator: this operates on workflow engine state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Critical Safety Rules Before Running
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run only with a strict target instance list.&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;appId&lt;/code&gt;, &lt;code&gt;processDefId&lt;/code&gt;, and &lt;code&gt;processVersion&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;Keep logs enabled and review summary counts after execution.&lt;/li&gt;
&lt;li&gt;Take backup/snapshot before mass correction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Configuration You Must Edit
&lt;/h2&gt;

&lt;p&gt;Update only these values in the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;appId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processDefId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processVersion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;activityDefIdToAbort&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;activityDefIdToResume&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;targetProcessInstanceIds&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Full Working Code (Unchanged)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.service.WorkflowManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.WorkflowActivity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.WorkflowProcess&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.WorkflowVariable&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Collection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 PURPOSE
=====================================================================
 This script performs a controlled workflow rollback / jump in Joget.

 - Finds running workflow instances of ONE app + ONE process + ONE version
 - Checks if the instance is currently at a specific activity
 - Force-aborts that activity
 - Starts ANY activity you define (earlier, later, or never-run)
 - Restores workflow variables if the target activity ran before
 - Re-evaluates assignments after rollback

 IMPORTANT:
 - This affects ONLY workflow state, NOT form table data
 - Designed for admin / production-fix usage
=====================================================================
*/&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 GET WORKFLOW MANAGER
=====================================================================
*/&lt;/span&gt;
&lt;span class="nc"&gt;WorkflowManager&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowManager&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;AppUtil&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workflowManager"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 HELPER METHOD:
 Get the latest (current) activity of a process instance
=====================================================================
*/&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="nf"&gt;getLatestActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;activityList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"dateCreated"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityList&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;activityList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;activityList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 HELPER METHOD:
 Get the most recent execution of ANY specific activity definition
 (Used to restore workflow variables if activity ran before)
=====================================================================
*/&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="nf"&gt;getLastRecentSpecificActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;activityDefId&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;activityList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"dateCreated"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityList&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;activityList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activityList&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityDefId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityDefId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Return the latest matching execution&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 HELPER METHOD:
 Extract all workflow variables from an activity instance
=====================================================================
*/&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt; &lt;span class="nf"&gt;extractWorkflowVariables&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;activityInstanceId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;varList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityVariableList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;varList&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;WorkflowVariable&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowVariable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVal&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVal&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 CONFIGURATION SECTION (MODIFY ONLY THIS PART)
=====================================================================
*/&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;appId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_app_id"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processDefId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_process_def_id"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_process_version"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Activity where process MUST currently be */&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToAbort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_current_activity_def_id"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Activity where process will be resumed (ANY activity you want) */&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_target_activity_def_id"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Restrict rollback to specific process instances */&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;targetProcessInstanceIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"your_process_instance_id"&lt;/span&gt;
&lt;span class="o"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 SAFETY METHOD:
 Check if current instance is in target list
=====================================================================
*/&lt;/span&gt;
&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;isTargetInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 MAIN EXECUTION LOGIC
=====================================================================
*/&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalMatched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;successCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;failureCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Fetch all running workflow instances */&lt;/span&gt;
&lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;runningProcesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRunningProcessList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;appId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processDefId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processVersion&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;runningProcesses&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;WorkflowProcess&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowProcess&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstanceId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/* Get current activity */&lt;/span&gt;
    &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="n"&gt;currentActivity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getLatestActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/* Skip if not at expected activity */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentActivity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;activityDefIdToAbort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityDefId&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;totalMatched&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
    &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Matched instance --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/* Safety check: only allowed instances */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isTargetInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetProcessInstanceIds&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Skipped (not in target list) --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/*
    ===============================================================
     Attempt to restore workflow variables from previous execution
     of the resume activity (if it exists)
    ===============================================================
    */&lt;/span&gt;
    &lt;span class="nc"&gt;Map&lt;/span&gt; &lt;span class="n"&gt;wfVars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="n"&gt;lastRunOfResumeActivity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;getLastRecentSpecificActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastRunOfResumeActivity&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wfVars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractWorkflowVariables&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastRunOfResumeActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Workflow variables restored from previous execution of --&amp;gt; "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"No previous execution found for --&amp;gt; "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", starting fresh"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/*
    ===============================================================
     Force start the chosen activity
    ===============================================================
    */&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activityStart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;WorkflowActivity&lt;/span&gt; &lt;span class="n"&gt;resumedActivity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;getLatestActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="cm"&gt;/* Restore variables if available */&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;wfVars&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activityVariables&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resumedActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;wfVars&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="cm"&gt;/* Re-evaluate assignments */&lt;/span&gt;
        &lt;span class="n"&gt;wm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reevaluateAssignmentsForActivity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resumedActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;successCount&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"SUCCESS rollback --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" resumed at --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;activityDefIdToResume&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failureCount&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"FAILED rollback --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;processInstanceId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
=====================================================================
 FINAL SUMMARY LOGS
=====================================================================
*/&lt;/span&gt;
&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TOTAL MATCHED --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;totalMatched&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SUCCESS COUNT --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;successCount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Auto-Rollback"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"FAILURE COUNT --&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;failureCount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Quick Test Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Test on one non-critical instance first.&lt;/li&gt;
&lt;li&gt;Verify target instance is currently at &lt;code&gt;activityDefIdToAbort&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm resumed activity is &lt;code&gt;activityDefIdToResume&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check assignment list after &lt;code&gt;reevaluateAssignmentsForActivity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review logs for matched/success/failure counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Security and Publishing Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replace real app IDs, process IDs, version numbers, and instance IDs with generic placeholders before publishing.&lt;/li&gt;
&lt;li&gt;Do not expose internal process naming conventions in public posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Final Note
&lt;/h2&gt;

&lt;p&gt;Use this script as a controlled recovery tool, not as a routine workflow path. With strict targeting and logging, it becomes a reliable production-fix pattern.&lt;/p&gt;

&lt;p&gt;Follow-up topic: Bulk rollback strategy with dry-run mode and audit report output.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>backend</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🧩 Ensure Approver Roles Stay Unique in Joget (BeanShell Validator)</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 26 Apr 2026 11:15:46 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/ensure-all-approvers-are-unique-in-joget-using-beanshell-validation-3dal</link>
      <guid>https://dev.to/exploringmylifeworks/ensure-all-approvers-are-unique-in-joget-using-beanshell-validation-3dal</guid>
      <description>&lt;h2&gt;
  
  
  1. Why This Matters
&lt;/h2&gt;

&lt;p&gt;If the same user is selected for multiple approval roles, approvals lose segregation and audits become weak.&lt;/p&gt;

&lt;p&gt;This validator blocks duplicate user selections at form submission time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where to Use in Joget
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Form Builder: add this as a Custom Validator on each approver-related field.&lt;/li&gt;
&lt;li&gt;Suitable fields: dropdown, select box, or user picker fields storing usernames.&lt;/li&gt;
&lt;li&gt;Not for Workflow Builder or API Builder: this is pre-submission form validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Configuration You Must Set
&lt;/h2&gt;

&lt;p&gt;Update these field IDs to match your form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;reviewer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authorizer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;approver&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attach the same validator script to each of these fields so each selected value is checked against the other roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Production-Ready Validator Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.model.Element&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.model.Form&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.model.FormData&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.service.FormUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Arrays&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;reviewerFieldName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"reviewer"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;authorizerFieldName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Authorizer"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;approverFieldName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Approver"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findRootForm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;reviewerElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviewerFieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;authorizerElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizerFieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;approverElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approverFieldName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;reviewerElement&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;authorizerElement&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;approverElement&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;reviewerValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getElementPropertyValues&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviewerElement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;authorizerValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getElementPropertyValues&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizerElement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;approverValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getElementPropertyValues&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approverElement&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;elementId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FormUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getElementParameterName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;selectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedValue&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selectedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFormError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elementId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Approver cannot be blank"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviewerValues&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;
                    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizerValues&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedValue&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;selectedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;approverValues&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFormError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elementId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Approver must be unique"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Quick Validation Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Select the same user in two roles: form must fail with duplicate error.&lt;/li&gt;
&lt;li&gt;Select different users across roles: form must submit.&lt;/li&gt;
&lt;li&gt;Leave a required role empty: form must show empty-field error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Security and Publishing Note
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep sample field IDs generic when sharing publicly.&lt;/li&gt;
&lt;li&gt;Do not publish real usernames, emails, internal group names, or environment-specific IDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Final Note
&lt;/h2&gt;

&lt;p&gt;This validator is small but high impact: it enforces role separation before workflow execution and prevents approval design mistakes early.&lt;/p&gt;

&lt;p&gt;Follow-up topic: Dynamic multi-level approver uniqueness (including grid or multi-select fields).&lt;/p&gt;

</description>
      <category>java</category>
      <category>nocode</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🧭 Get Line Manager Until CEO in Joget Using BeanShell</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 26 Apr 2026 11:05:15 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/get-line-manager-until-ceo-in-joget-using-beanshell-mfa</link>
      <guid>https://dev.to/exploringmylifeworks/get-line-manager-until-ceo-in-joget-using-beanshell-mfa</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;✅ This Joget snippet helps you decide who the next approver should be based on whether a request is escalated or not.&lt;/p&gt;

&lt;p&gt;✅ If the item is not escalated, the script takes the requester’s higher-level manager and stores it as the next line manager.&lt;/p&gt;

&lt;p&gt;✅ If the item is already escalated, the script keeps moving up the hierarchy by using the previously stored manager value.&lt;/p&gt;

&lt;p&gt;✅ In real workflows, this is useful when approval paths depend on organizational levels instead of a fixed list of users.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How It Works
&lt;/h2&gt;

&lt;p&gt;⚙️ The script reads a form field that tells Joget whether the record is escalated.&lt;/p&gt;

&lt;p&gt;⚙️ If the value is not &lt;code&gt;Yes&lt;/code&gt;, it fetches the manager information from the requestor and saves two workflow variables:&lt;/p&gt;

&lt;p&gt;⚙️ &lt;code&gt;LineManager&lt;/code&gt; for the next approver email.&lt;/p&gt;

&lt;p&gt;⚙️ &lt;code&gt;CurrentLineManager&lt;/code&gt; for the username of the current hierarchy level.&lt;/p&gt;

&lt;p&gt;⚙️ If the value is &lt;code&gt;Yes&lt;/code&gt;, the script reuses the previously stored &lt;code&gt;CurrentLineManager&lt;/code&gt; and moves one step higher.&lt;/p&gt;

&lt;p&gt;⚙️ The idea is simple: keep pushing the workflow to the right person without hardcoding the approval chain in every step.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Full Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.workflow.model.service.WorkflowManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;getLineManager&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;WorkflowManager&lt;/span&gt; &lt;span class="n"&gt;workflowManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowManager&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pluginManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workflowManager"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace these placeholders with your own form and user hash variables.&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;escalatedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#form.your_form.escalated#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Line Manager Blog"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Escalated value: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;escalatedValue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="s"&gt;"Yes"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;escalatedValue&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#user.{variable.requestor}.manager.email#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;managerUsername&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#user.{variable.requestor}.manager.username#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Line Manager Blog"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Initial manager email: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;workflowManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activityVariable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflowAssignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"LineManager"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;managerEmail&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;workflowManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activityVariable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflowAssignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"CurrentLineManager"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;managerUsername&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;currentManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#variable.CurrentLineManager#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nextManagerEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#user.{variable.CurrentLineManager}.email#"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Line Manager Blog"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Current manager: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;currentManager&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;workflowManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activityVariable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflowAssignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getActivityId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"LineManager"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextManagerEmail&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;getLineManager&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Example Use Cases
&lt;/h2&gt;

&lt;p&gt;✅ Multi-level approval chains where each escalation step goes to the next manager.&lt;/p&gt;

&lt;p&gt;✅ HR or compliance flows where the approver depends on who submitted the form.&lt;/p&gt;

&lt;p&gt;✅ Internal request routing where the next reviewer changes after each escalation.&lt;/p&gt;

&lt;p&gt;✅ Cases where you want a simple and traceable approval path without building a separate routing table.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Customization Tips
&lt;/h2&gt;

&lt;p&gt;💡 Replace &lt;code&gt;#form.your_form.escalated#&lt;/code&gt; with your real Joget form field.&lt;/p&gt;

&lt;p&gt;💡 If your hierarchy uses department heads, team leads, or role-based routing, adjust the hash variables to match your directory structure.&lt;/p&gt;

&lt;p&gt;💡 If you want to go up more than one level, you can chain the logic and keep updating &lt;code&gt;CurrentLineManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 Keep the workflow variable names short and consistent so they are easy to reuse in later scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Key Benefits
&lt;/h2&gt;

&lt;p&gt;✅ Less hardcoding in the workflow design.&lt;/p&gt;

&lt;p&gt;✅ Easier escalation handling for approval chains.&lt;/p&gt;

&lt;p&gt;✅ Cleaner maintenance when the organization structure changes.&lt;/p&gt;

&lt;p&gt;✅ Better reuse across similar Joget processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Security Note
&lt;/h2&gt;

&lt;p&gt;⚠️ Do not publish real usernames, email domains, or internal hierarchy field names if they reveal your organization structure.&lt;/p&gt;

&lt;p&gt;⚠️ If your project uses sensitive directory mappings, replace them with generic placeholders before sharing the article.&lt;/p&gt;

&lt;p&gt;⚠️ Keep the logic public, but keep the real deployment values private.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;✅ This pattern is small, but it solves a common workflow problem very cleanly.&lt;/p&gt;

&lt;p&gt;✅ Once you have a reliable way to move from requestor to manager to higher manager, many approval flows become much easier to maintain.&lt;/p&gt;

&lt;p&gt;✅ The main thing to remember is to keep the hierarchy logic generic enough to reuse, but specific enough to remain readable.&lt;/p&gt;

&lt;p&gt;💡 Follow-up topic suggestion: &lt;strong&gt;Dynamic Manager Escalation in Joget With Conditional Workflow Variables&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🎨 Dynamic Dashboard Colors: Mapping Pie Chart Slices to Workflow Status in Joget</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 16 Nov 2025 11:24:01 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/dynamic-dashboard-colors-mapping-pie-chart-slices-to-workflow-status-2ihg</link>
      <guid>https://dev.to/exploringmylifeworks/dynamic-dashboard-colors-mapping-pie-chart-slices-to-workflow-status-2ihg</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;Dashboards are only effective when they communicate information clearly. When using Joget's Report Builder (which uses ECharts) to create a &lt;strong&gt;Status Pie Chart&lt;/strong&gt;, the default colors are meaningless. We need &lt;strong&gt;Green&lt;/strong&gt; for "Authorized," &lt;strong&gt;Red&lt;/strong&gt; for "Rejected," and &lt;strong&gt;Yellow&lt;/strong&gt; for "Pending."&lt;/p&gt;

&lt;p&gt;This client-side JavaScript solution, placed in a &lt;strong&gt;Custom HTML&lt;/strong&gt; element on your Userview page, allows you to hijack the chart after it loads and programmatically apply meaningful colors based on the data label (the workflow status).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How It Works
&lt;/h2&gt;

&lt;p&gt;This script performs two main actions: &lt;strong&gt;Color Customization&lt;/strong&gt; and &lt;strong&gt;Interactive Filtering&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Action 1: Applying Status Colors 🌈
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Chart Initialization:&lt;/strong&gt; The script waits for the page to load, then targets the specific ECharts instance using its unique HTML ID (e.g., &lt;code&gt;echarts_XYZ&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data Grab:&lt;/strong&gt; It pulls the raw data (&lt;code&gt;series[0].data&lt;/code&gt;) from the chart.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Color Loop:&lt;/strong&gt; It loops through every slice. For each slice, it checks the slice's name (the status, like "Authorized") and manually injects a custom &lt;code&gt;itemStyle&lt;/code&gt; property with a specific Hex color.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Redraw:&lt;/strong&gt; Finally, it uses &lt;code&gt;aChart.setOption(option)&lt;/code&gt; to force ECharts to redraw the chart with the new color mappings.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Action 2: Interactive Filtering 🖱️
&lt;/h3&gt;

&lt;p&gt;The second part of the script uses the ECharts built-in &lt;code&gt;on('click', ...)&lt;/code&gt; event listener to turn each pie slice into a functional link, routing users to specific, pre-filtered Userview dashboards.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Full Code
&lt;/h2&gt;

&lt;p&gt;Paste this code into a &lt;strong&gt;Custom HTML&lt;/strong&gt; element (placed after the Report Grid element) in your Joget Userview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Note:&lt;/strong&gt; The unique ECharts ID (&lt;code&gt;echarts_CHART_ID_HERE&lt;/code&gt;) and the Userview paths have been made generic. &lt;strong&gt;You must replace these placeholders&lt;/strong&gt; with your actual values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// IMPORTANT: Replace 'echarts_CHART_ID_HERE' with the actual ID of your chart element.&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;aChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;echarts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;echarts_CHART_ID_HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOption&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pieData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOption&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// --- DYNAMIC COLOR MAPPING LOGIC ---&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pieData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pieData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Set color based on status (Use Hex codes for consistency)&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Approval Pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#FFDD7D&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Yellow/Amber&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#9AC4A1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Green&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#EEA94E&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Orange&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escalated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Red Flag&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Request Rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#DC143C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Crimson (Strong Red)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Default color for any unexpected or unmapped values&lt;/span&gt;
                &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;itemStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gray&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Apply the new colors to the chart&lt;/span&gt;
        &lt;span class="nx"&gt;aChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// --- INTERACTIVE CLICK LOGIC ---&lt;/span&gt;
        &lt;span class="nx"&gt;aChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="c1"&gt;// IMPORTANT: Replace the generic Userview links with your actual paths.&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Approval Pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jw/web/userview/app_name/v/_/pending_dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jw/web/userview/app_name/v/_/authorized_dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jw/web/userview/app_name/v/_/rejected_dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escalated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jw/web/userview/app_name/v/_/escalated_dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// Generic fall-through: For all other statuses, open the main list filtered by the clicked status name&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// The hash variable used here (d-6985709-fn_Status) is a Report Grid filter hash.&lt;/span&gt;
                &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/jw/web/userview/app_name/v/_/all_document_list?d-GRID_HASH-fn_Status=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Example Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Compliance Dashboards:&lt;/strong&gt; Instantly highlight records that are "Expired" (Red) or "Compliant" (Green).&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Service Desk:&lt;/strong&gt; Show tickets based on priority: "High" (Red), "Medium" (Orange), "Low" (Blue).&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Financial Approvals:&lt;/strong&gt; Use strong, clear colors to represent the stage of the approval process: "Submitted" (Yellow), "Finance Approved" (Light Green), "CEO Approved" (Dark Green).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Customization Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💡 &lt;strong&gt;Using Lookup Objects:&lt;/strong&gt; For cleaner code, consider replacing the long &lt;code&gt;if/else if&lt;/code&gt; chain with a simple JavaScript lookup object (or map). This is easier to update when new statuses are added.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Userview Pathing:&lt;/strong&gt; The click logic uses &lt;code&gt;window.open(...)&lt;/code&gt; to direct users. Ensure you replace &lt;code&gt;app_name&lt;/code&gt; and the specific Userview categories (&lt;code&gt;_&lt;/code&gt;) with your application's actual URLs.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;ECharts ID Safety:&lt;/strong&gt; If you modify and save your Report Grid element, Joget sometimes changes the unique ECharts ID. If your colors disappear, this is the first place to check!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Key Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Enhanced Clarity:&lt;/strong&gt; Makes dashboards instantly readable by associating colors with conventional meanings (red=bad, green=good).&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Improved UX:&lt;/strong&gt; Turns a static visualization into an interactive filter, drastically reducing the clicks needed for users to drill down into data.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Maintainability:&lt;/strong&gt; All coloring rules are contained in one simple, client-side script, separate from the core reporting configuration.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Security Note
&lt;/h2&gt;

&lt;p&gt;🔒 &lt;strong&gt;Userview Security:&lt;/strong&gt; The click logic exposes Userview URLs. Ensure that the target dashboard pages (&lt;code&gt;/pending_dashboard&lt;/code&gt;, &lt;code&gt;/rejected_dashboard&lt;/code&gt;, etc.) are protected with appropriate Userview &lt;strong&gt;Permission&lt;/strong&gt; settings so unauthorized users cannot access the underlying data lists directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;By using client-side scripting to customize the ECharts object, you transform a generic Joget dashboard into a highly intuitive and interactive data visualization tool. This simple technique is fundamental for professional-grade reporting in Joget.&lt;/p&gt;

</description>
      <category>joget</category>
      <category>charts</category>
      <category>dashboard</category>
      <category>javascript</category>
    </item>
    <item>
      <title>💾 Custom Approval History in Joget: Direct SQL Insertion via BeanShell Tool</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 16 Nov 2025 11:19:31 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/custom-approval-history-in-joget-direct-sql-insertion-via-beanshell-tool-1kcp</link>
      <guid>https://dev.to/exploringmylifeworks/custom-approval-history-in-joget-direct-sql-insertion-via-beanshell-tool-1kcp</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;While Joget's Form Binders and built-in audit trails handle standard data storage, complex enterprise applications often require specialized auditing and history tracking. This is particularly true when you need a dedicated, sequentially numbered &lt;strong&gt;Approval History&lt;/strong&gt; table that links to multiple main processes for easy reporting.&lt;/p&gt;

&lt;p&gt;This post details an advanced, expert technique using a &lt;strong&gt;Java/BeanShell Process Tool&lt;/strong&gt; to perform a direct SQL &lt;code&gt;INSERT&lt;/code&gt; into a custom database table (&lt;code&gt;app_fd_approval_history&lt;/code&gt;). This method grants granular control over the primary key (&lt;code&gt;id&lt;/code&gt;) sequence and allows you to log specific metadata (user, status, date) mid-workflow, decoupled from the main form's binder.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How It Works
&lt;/h2&gt;

&lt;p&gt;The script operates as a robust, transactional database logger configured as a workflow tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Database Connection:&lt;/strong&gt; It securely retrieves the Joget application's main database connection via the &lt;strong&gt;&lt;code&gt;setupDataSource&lt;/code&gt;&lt;/strong&gt; Spring bean using &lt;code&gt;AppUtil.getApplicationContext().getBean("setupDataSource")&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Manual ID Generation:&lt;/strong&gt; It executes &lt;code&gt;SELECT MAX(CAST(id AS SIGNED))&lt;/code&gt; to find the highest existing sequential ID. Crucially, it &lt;strong&gt;casts the &lt;code&gt;id&lt;/code&gt;&lt;/strong&gt; (often stored as a string in Joget forms) to a signed integer to ensure correct numerical ordering, and then calculates the &lt;code&gt;nextId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Dynamic Data Capture:&lt;/strong&gt; It utilizes Joget &lt;strong&gt;Hash Variables&lt;/strong&gt; (e.g., &lt;code&gt;#form.new_request.createdByName#&lt;/code&gt;) to dynamically capture real-time form data, user information, and timestamps directly from the workflow instance before preparing the data for insertion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Execution &amp;amp; Cleanup:&lt;/strong&gt; It uses a &lt;code&gt;PreparedStatement&lt;/code&gt; to safely execute the &lt;code&gt;INSERT&lt;/code&gt; query. The &lt;strong&gt;&lt;code&gt;finally&lt;/code&gt;&lt;/strong&gt; block is critical, ensuring the database connection is closed (&lt;code&gt;conn.close()&lt;/code&gt;) even if an exception occurs, preventing connection leaks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Full Code
&lt;/h2&gt;

&lt;p&gt;This code is configured as a &lt;strong&gt;BeanShell&lt;/strong&gt; within a Joget workflow, typically placed immediately after a submission or approval step where the status transition occurs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.joget.apps.app.service.AppUtil;
import org.joget.commons.util.LogUtil;

public void insertNewData() {
    Connection conn = null;
    try {
        LogUtil.info("ApprovalHistoryTool", "Starting Approval History Creation Tool");

        // 1. Get the primary application data source
        DataSource ds = (DataSource) AppUtil.getApplicationContext().getBean("setupDataSource");
        conn = ds.getConnection();

        // 2. Determine the next sequential ID manually
        String maxIdQuery = "SELECT MAX(CAST(id AS SIGNED)) FROM app_fd_approval_history";
        PreparedStatement maxIdStmt = conn.prepareStatement(maxIdQuery);
        ResultSet resultSet = maxIdStmt.executeQuery();

        int nextId = 1; 
        if (resultSet.next()) {
            nextId = resultSet.getInt(1) + 1;
            LogUtil.info("ApprovalHistoryTool", "Next ID to be inserted: " + nextId);
        }
        maxIdStmt.close();

        // 3. Prepare the INSERT statement
        String insertQuery = "INSERT INTO app_fd_approval_history" +
                "(id, c_name, c_status, c_date, c_remark, c_request_for, c_parent_id)" +
                "VALUES (?, ?, ?, ?, ?, ?, ?)";

        // 4. Capture dynamic data using Joget Hash Variables
        // IMPORTANT: Replace the placeholder values (#form.new_request...) with your actual form IDs.
        String name = "";
        String status = "";
        String date = "";

        // Conditional logic based on a form field value (Request Type)
        if ("New".equals("#form.new_request.request_type#")) {
            name = "#form.new_request.createdByName#";
            status = "Request Submitted";
            date = "#form.new_request.dateCreated#";
        } else if ("Update".equals("#form.new_request.request_type#")) {
            name = "#form.new_request.modifiedByName#";
            status = "Update Request";
            date = "#form.new_request.dateModified#";
        }

        String remark = "#form.new_request.reason#";
        String requestFor = ""; 
        String foreignKey = "#form.new_request.id#"; // Links to the main form record

        PreparedStatement insertStmt = conn.prepareStatement(insertQuery);

        // Set values using the PreparedStatement (prevents SQL injection)
        insertStmt.setInt(1, nextId);
        insertStmt.setString(2, name);
        insertStmt.setString(3, status);
        insertStmt.setString(4, date);
        insertStmt.setString(5, remark);
        insertStmt.setString(6, requestFor);
        insertStmt.setString(7, foreignKey);
        LogUtil.info("ApprovalHistoryTool", "Linking history to parent ID: " + foreignKey);

        // 5. Execute and close
        insertStmt.executeUpdate();
        insertStmt.close();

        LogUtil.info("ApprovalHistoryTool", "Data inserted successfully in Approval History Table.");
    } catch (Exception e) {
        LogUtil.error("ApprovalHistoryTool", "Error inserting data: " + e.getMessage(), e);
    } finally {
        // 6. Ensure connection is always closed
        try {
            if (conn != null &amp;amp;&amp;amp; !conn.isClosed()) {
                conn.close();
            }
        } catch (SQLException e) {
            LogUtil.error("ApprovalHistoryTool", "Error closing connection: " + e.getMessage(), e);
        }
    }
}

insertNewData();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Example Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Complete Audit Trail:&lt;/strong&gt; Creating a persistent, uneditable log of every approval, rejection, and modification action for compliance or security auditing.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Custom Reporting:&lt;/strong&gt; Facilitating streamlined reporting on approval cycle times, user actions, and status changes using a normalized, dedicated history table structure.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Sequential Primary Keys:&lt;/strong&gt; Implementing simple, strictly sequential, numeric &lt;code&gt;id&lt;/code&gt;s for history records, which are far easier for end-users and reports to reference than default UUIDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Customization Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💡 &lt;strong&gt;Hash Variable Validation:&lt;/strong&gt; Always verify that your Joget Hash Variables are correctly returning the required values (e.g., using &lt;code&gt;LogUtil.info()&lt;/code&gt;) before trusting them in a database insertion.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Error Logging:&lt;/strong&gt; Expand the &lt;code&gt;catch&lt;/code&gt; block to include logic that notifies an administrator (e.g., sends an email) if the database insertion fails, rather than just logging the error.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Time Zone Control:&lt;/strong&gt; Dates retrieved via Hash Variables may require explicit formatting. Use Java's &lt;code&gt;SimpleDateFormat&lt;/code&gt; inside the script to format the date string before insertion, ensuring consistency across different geographic locations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Key Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Granular Control:&lt;/strong&gt; Provides developers with complete control over SQL execution, allowing for custom data structures and transactional integrity.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Decoupled Logging:&lt;/strong&gt; Separates the audit trail from the main form data, making both the main table and the history table cleaner and more performant.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Security &amp;amp; Consistency:&lt;/strong&gt; Use of &lt;strong&gt;&lt;code&gt;PreparedStatement&lt;/code&gt;&lt;/strong&gt; protects against basic SQL injection and guarantees the field values are treated as data, not code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Security Note
&lt;/h2&gt;

&lt;p&gt;🔒 &lt;strong&gt;SQL Injection Prevention:&lt;/strong&gt; The use of &lt;strong&gt;&lt;code&gt;PreparedStatement&lt;/code&gt;&lt;/strong&gt; with placeholder &lt;code&gt;?&lt;/code&gt; marks is the most critical security feature in this script. It prevents external data (even malicious data from a form field) from altering the structure of the SQL command.&lt;br&gt;
🔒 &lt;strong&gt;Resource Leaks:&lt;/strong&gt; The meticulous implementation of the &lt;strong&gt;&lt;code&gt;finally&lt;/code&gt;&lt;/strong&gt; block to close the &lt;code&gt;Connection&lt;/code&gt; is non-negotiable. Failing to close connections will lead to resource exhaustion and potential database failure under load.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Mastering direct database interaction via BeanShell Tools gives you expert-level control over your Joget application's data layer. This technique is indispensable for building robust, compliance-ready solutions that require custom, sequential auditing and complex data relationships.&lt;/p&gt;

</description>
      <category>joget</category>
      <category>beanshell</category>
      <category>workflow</category>
      <category>automation</category>
    </item>
    <item>
      <title>🚨 Resuming Stuck Joget Workflow Tools with API</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 16 Nov 2025 10:45:20 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/resuming-stuck-joget-workflow-tools-with-api-kg1</link>
      <guid>https://dev.to/exploringmylifeworks/resuming-stuck-joget-workflow-tools-with-api-kg1</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;A critical issue in production environments is a workflow process halting when the Joget server unexpectedly goes down. While user activities are often easy to "Complete" manually via the Process Monitor, processes stuck specifically on a &lt;strong&gt;Tool Plugin&lt;/strong&gt; (like an Email Tool or integration script) often resist normal completion attempts. The "Complete" button in the Process Monitor UI has no effect because a Tool is a server-side action, not a user-driven workflow task.&lt;/p&gt;

&lt;p&gt;This post reveals the expert method, recommended by the Joget support team, to reliably &lt;strong&gt;force-resume or restart&lt;/strong&gt; a stuck tool using the built-in &lt;strong&gt;JSON Monitoring API&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How It Works
&lt;/h2&gt;

&lt;p&gt;When a tool element is stuck, the workflow engine typically believes the tool is still running or failed to complete its transaction. The solution is to issue a direct command to the workflow engine using the API endpoint specifically designed to manage activity states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Endpoint:&lt;/strong&gt; The &lt;code&gt;/monitoring/activity/start&lt;/code&gt; endpoint is used to push the activity back into the "ready" or "running" state, effectively forcing a re-execution of the tool's logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; The API requires administrative credentials (Basic Authentication or URL parameters) to ensure only authorized users can manipulate the workflow state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters:&lt;/strong&gt; You must provide the specific &lt;strong&gt;Process Instance ID&lt;/strong&gt; and the &lt;strong&gt;Activity Definition ID&lt;/strong&gt; (the unique ID of the tool element in the workflow designer) to target the exact stuck step.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Full API Command
&lt;/h2&gt;

&lt;p&gt;The API call uses the &lt;code&gt;POST&lt;/code&gt; method and the &lt;code&gt;/monitoring/activity/start/&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /jw/web/json/monitoring/activity/start/(*:processId)/(*:activityDefId)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example Request (Using URL Authentication)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/jw/web/json/monitoring/activity/start/24002_sampleApp_requestForm_approver_process/email_on_received?j_username=admin&amp;amp;j_password=admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must be used to change state.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;processId&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The specific instance ID of the stalled workflow.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;24002_sampleApp_requestForm_approver_process&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;activityDefId&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The unique ID of the stuck Tool Plugin.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;email_on_received&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;j_username&lt;/code&gt;/&lt;code&gt;j_password&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Admin credentials for authorization.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  4. Example Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Integration Failure Recovery:&lt;/strong&gt; A process is stuck on an &lt;strong&gt;API Tool Plugin&lt;/strong&gt; because the external service was temporarily unavailable during a server interruption. Restarting the tool allows the integration to run again.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Notification Retries:&lt;/strong&gt; An &lt;strong&gt;Email Tool&lt;/strong&gt; failed to send due to a mail server timeout. The API call forces the email tool to re-execute and notify users correctly.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Data Sync Issues:&lt;/strong&gt; A &lt;strong&gt;Script Tool&lt;/strong&gt; responsible for data synchronization failed mid-execution and left the process in an ambiguous state. The API forces the engine to resolve the tool's status and advance the flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Customization Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💡 &lt;strong&gt;Postman Environment:&lt;/strong&gt; Create an environment variable in Postman for your base Joget URL (e.g., &lt;code&gt;http://localhost:8080/jw&lt;/code&gt;) and your admin credentials to quickly execute these recovery calls without re-typing.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Basic Auth:&lt;/strong&gt; For better security, use the &lt;strong&gt;Basic Auth&lt;/strong&gt; tab in Postman or similar tools instead of passing credentials in the URL parameters, especially in non-local environments.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Logging:&lt;/strong&gt; Before using this method, always consult the Joget server logs (&lt;code&gt;wflow.log&lt;/code&gt;, &lt;code&gt;catalina.out&lt;/code&gt;, etc.) to understand &lt;em&gt;why&lt;/em&gt; the tool failed initially. Addressing the root cause is crucial to prevent recurrence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Key Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Immediate Process Continuity:&lt;/strong&gt; Quickly recovers stalled business processes without requiring manual data intervention or lengthy server restarts.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;High Data Integrity:&lt;/strong&gt; By utilizing the official Monitoring API, you maintain the integrity of the workflow state within the Joget engine.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Targeted Fix:&lt;/strong&gt; Allows administrators to precisely fix specific process instances without affecting other running processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Security Note
&lt;/h2&gt;

&lt;p&gt;🔒 &lt;strong&gt;API Exposure:&lt;/strong&gt; This API grants the power to manipulate live workflow states. &lt;strong&gt;Never&lt;/strong&gt; expose this endpoint publicly. Ensure that any system making calls to this API uses strong administrative credentials and operates exclusively within a secure network environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Understanding how to directly interface with the Joget workflow engine via the Monitoring JSON API is a critical skill for any advanced Joget administrator. It provides the necessary surgical precision to handle edge cases like stuck Tool Plugins, ensuring your production workflows remain resilient and functional even after unexpected interruptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyy0sl7nzcstifqzm56k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feyy0sl7nzcstifqzm56k.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>joget</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>🛠️ Dynamic Joget Form Generation Using BeanShell Scripting</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 16 Nov 2025 10:02:36 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/dynamic-joget-form-generation-using-beanshell-scripting-aln</link>
      <guid>https://dev.to/exploringmylifeworks/dynamic-joget-form-generation-using-beanshell-scripting-aln</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;While Joget's Form Builder is excellent for visual design, there are advanced scenarios where you need to generate form definitions programmatically. This is typically done when a form's structure is &lt;strong&gt;dynamic&lt;/strong&gt; or based on an external metadata source (e.g., a database table, a configuration list, or another Joget list).&lt;/p&gt;

&lt;p&gt;This blog post analyzes a powerful &lt;strong&gt;BeanShell/Groovy script&lt;/strong&gt; designed to run within Joget's server-side tools. It reads form field definitions from a &lt;code&gt;FormRowSet&lt;/code&gt; (or similar data source) and constructs a complete Joget Form Definition JSON, finally saving it to the App Definition.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How It Works
&lt;/h2&gt;

&lt;p&gt;The script operates by constructing the complete JSON object that defines a Joget Form, adhering strictly to the internal schema used by the platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Data Source:&lt;/strong&gt; The script assumes field metadata (ID, Label, Class Name) is available in the &lt;code&gt;rows&lt;/code&gt; variable (a &lt;code&gt;FormRowSet&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Field Construction:&lt;/strong&gt; It iterates over the &lt;code&gt;rows&lt;/code&gt;. For each entry, it creates a string representation of a JSON object for a single form element, ensuring the &lt;code&gt;className&lt;/code&gt; (field type) and &lt;code&gt;properties&lt;/code&gt; (ID and Label) are correctly inserted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ JSON Aggregation:&lt;/strong&gt; All individual field JSONs are placed into a &lt;code&gt;JSONArray&lt;/code&gt; (&lt;code&gt;fields&lt;/code&gt;). This array is then inserted as the content of the Form's main &lt;code&gt;Column&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚙️ Form Saving:&lt;/strong&gt; Finally, the fully assembled JSON is wrapped in a &lt;code&gt;FormDefinition&lt;/code&gt; object. The script uses the &lt;code&gt;AppService&lt;/code&gt; bean to call &lt;code&gt;createFormDefinition&lt;/code&gt;, making the new form instantly available in the App Designer.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Full Code
&lt;/h2&gt;

&lt;p&gt;This code is typically executed within a &lt;strong&gt;Process Tool&lt;/strong&gt; using the BeanShell/Groovy language or in a &lt;strong&gt;Form/Datalist Binder&lt;/strong&gt; script where the &lt;code&gt;rows&lt;/code&gt; variable is populated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Collection&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.model.AppDefinition&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.model.FormDefinition&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.app.service.AppUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.model.FormRow&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.apps.form.model.FormRowSet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.joget.commons.util.LogUtil&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONArray&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.json.JSONObject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Get form metadata from request or script tool properties&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;formDefId&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"form_def_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;formName&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"form_name"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;formTableName&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"form_table_name"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Assume 'rows' is a FormRowSet containing field configurations&lt;/span&gt;
&lt;span class="c1"&gt;// FormRowSet rows = ... &lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;JSONArray&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONArray&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Loop through the source data (e.g., field definitions)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FormRow&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fieldId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"field_id"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fieldLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"field_label"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Must be a valid Joget Form Element class name (e.g., org.joget.apps.form.lib.TextField)&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"field_type"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;JSONObject&lt;/span&gt; &lt;span class="n"&gt;formElement&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Build the JSON structure for a single form element&lt;/span&gt;
        &lt;span class="n"&gt;formElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"    \"className\" : \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"    \"properties\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"controlField\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"id\": \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fieldId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"label\": \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fieldLabel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"permissionHidden\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"readonly\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"readonlyLabel\": \"\",\n  "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"size\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"validator\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"            \"className\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"            \"properties\": {}\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        },\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"value\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"        \"workflowVariable\": \"\"\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"    }\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                        &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formElement&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Embed all generated fields into the main Form structure&lt;/span&gt;
    &lt;span class="nc"&gt;JSONObject&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"    \"className\": \"org.joget.apps.form.model.Form\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"    \"elements\": [{\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"className\": \"org.joget.apps.form.model.Section\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"elements\": [{\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"className\": \"org.joget.apps.form.model.Column\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"elements\":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"properties\": {\"width\": \"100%\"}\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        }],\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"properties\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"id\": \"section1\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"label\": \"Dynamic Fields Section\"\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        }\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"    }],\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"    \"properties\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"description\": \"Dynamically generated form.\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"id\": \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;formDefId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"loadBinder\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"className\": \"org.joget.apps.form.lib.WorkflowFormBinder\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"properties\": {}\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        },\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"name\": \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;formName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"noPermissionMessage\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"permission\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"className\": \"\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"properties\": {}\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        },\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"postProcessor\": null,\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"postProcessorRunOn\": \"create\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"storeBinder\": {\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"className\": \"org.joget.apps.form.lib.WorkflowFormBinder\",\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"            \"properties\": {}\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        },\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"        \"tableName\": \""&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;formTableName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\"\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"    }\n"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Save the new Form Definition to the current App&lt;/span&gt;
    &lt;span class="nc"&gt;FormDefinition&lt;/span&gt; &lt;span class="n"&gt;formDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormDefinition&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;formDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;formDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formDefId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;formDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;formDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTableName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formTableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;AppService&lt;/span&gt; &lt;span class="n"&gt;appService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;AppUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"appService"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;AppDefinition&lt;/span&gt; &lt;span class="n"&gt;appDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrentAppDefinition&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;appService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createFormDefinition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appDef&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formDefinition&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Optional: Log errors if form creation failed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FormCreator"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Errors encountered while creating form: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;LogUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FormCreator"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Cannot Create Form"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Example Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Dynamic Survey Generation:&lt;/strong&gt; Create different forms instantly based on survey questions stored in a master configuration list.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Automated Integrations:&lt;/strong&gt; Automatically generate forms that mirror the structure of an external API or third-party database table, minimizing manual development work.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;User-Defined Forms:&lt;/strong&gt; Allow specific power-users to define the fields they need in a secondary Joget list, then use this script to generate the final functional form.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Customization Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;💡 &lt;strong&gt;Field Type Mapping:&lt;/strong&gt; Use a map structure in your BeanShell script to translate simple data types (e.g., &lt;code&gt;"TEXT"&lt;/code&gt;) from your source &lt;code&gt;rows&lt;/code&gt; into the necessary full Java Class Names (e.g., &lt;code&gt;"org.joget.apps.form.lib.TextField"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Adding Complex Properties:&lt;/strong&gt; For field types like Select Boxes, dynamically add the &lt;code&gt;options&lt;/code&gt; or &lt;code&gt;optionsBinder&lt;/code&gt; properties to the &lt;code&gt;formElement&lt;/code&gt; JSON to ensure the new field is fully functional upon creation.&lt;/li&gt;
&lt;li&gt;💡 &lt;strong&gt;Metadata Refresh:&lt;/strong&gt; If you use this script to &lt;em&gt;update&lt;/em&gt; a form, use &lt;code&gt;appService.updateFormDefinition&lt;/code&gt; instead of &lt;code&gt;createFormDefinition&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Key Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Metadata-Driven Design:&lt;/strong&gt; Decouples form structure from application code, making forms easier to manage and update.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Consistency:&lt;/strong&gt; Ensures all generated forms follow a consistent structure (e.g., using the same default binders and field properties).&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Extensibility:&lt;/strong&gt; Enables the creation of complex applications where form fields are determined by business rules or configuration, not just static design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Security Note
&lt;/h2&gt;

&lt;p&gt;🔒 &lt;strong&gt;Access Control:&lt;/strong&gt; Since this script can alter the core application definition, it must be carefully permissioned. It is best used in a &lt;strong&gt;workflow process tool&lt;/strong&gt; that runs under the secure system user account or is triggered only by application administrators. Avoid exposing this logic directly in front-end form validation or client-side scripts.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Leveraging BeanShell to dynamically manipulate Joget's JSON configurations is the pinnacle of advanced Joget development. It unlocks the ability to build flexible, configuration-driven solutions that are resilient to changes in business requirements.&lt;/p&gt;

</description>
      <category>joget</category>
      <category>lowcode</category>
      <category>automation</category>
      <category>beanshell</category>
    </item>
    <item>
      <title>Mastering BeanShell Hash Variables in Joget: The Most Basic &amp; Practical Guide</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Sun, 16 Nov 2025 07:49:01 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/mastering-beanshell-hash-variables-in-joget-the-most-basic-practical-guide-5fnc</link>
      <guid>https://dev.to/exploringmylifeworks/mastering-beanshell-hash-variables-in-joget-the-most-basic-practical-guide-5fnc</guid>
      <description>&lt;p&gt;1.Create an App Variable &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r2m5g68erwpl07kl945.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r2m5g68erwpl07kl945.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.call it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wqltu3yb2hqq32vapwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wqltu3yb2hqq32vapwc.png" alt=" " width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Points to Note Down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It only Return String.&lt;/li&gt;
&lt;li&gt;You can pass Dynamic Values to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How To Pass dynamic values to Beanshell hash.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4jnufuecn3sxnfled60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4jnufuecn3sxnfled60.png" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Call it by passing the dynamic value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs8xzgmngr50ipju34ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs8xzgmngr50ipju34ul.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>joget</category>
      <category>beanshell</category>
      <category>lowcode</category>
      <category>automation</category>
    </item>
    <item>
      <title>🌍 Auto-Load Multilingual Dropdown Labels in Joget Using Platform Locale</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Tue, 11 Nov 2025 06:17:11 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/auto-load-multilingual-dropdown-labels-in-joget-using-platform-locale-37md</link>
      <guid>https://dev.to/exploringmylifeworks/auto-load-multilingual-dropdown-labels-in-joget-using-platform-locale-37md</guid>
      <description>&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;When building multilingual Joget apps, it’s important for dropdowns (Select Boxes) to automatically display values in the user’s active language — whether that’s English, Arabic, or any other supported locale.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll learn how to configure a &lt;strong&gt;Joget Select Box&lt;/strong&gt; to automatically load the &lt;strong&gt;localized label&lt;/strong&gt; based on the platform’s current language setting.&lt;/p&gt;

&lt;p&gt;This approach ensures that users see the same data, but with labels translated according to their locale — all without extra scripting.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ How It Works
&lt;/h2&gt;

&lt;p&gt;💡 The solution uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;SQL query&lt;/strong&gt; as the Select Box’s options source.&lt;/li&gt;
&lt;li&gt;Joget’s variable &lt;code&gt;#platform.currentLanguage#&lt;/code&gt; to detect the logged-in user’s interface language.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CASE statement&lt;/strong&gt; in SQL to conditionally choose between Arabic and English column names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever the user changes the platform locale (via User Profile or &lt;code&gt;/web/userview&lt;/code&gt; language toggle), the dropdown automatically displays the correct labels.&lt;/p&gt;




&lt;h2&gt;
  
  
  💻 Full Code
&lt;/h2&gt;

&lt;p&gt;Use the following SQL query in your &lt;strong&gt;Select Box’s “Options Binder → JDBC Binder → SQL Query”&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 
    c_code AS value, 
    CASE '#platform.currentLanguage#'
        WHEN 'ar' THEN c_name_ar
        ELSE c_name_en
    END AS label
FROM app_fd_governate_mst
ORDER BY label
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c_code&lt;/code&gt; → The stored value in Joget (e.g., &lt;code&gt;001&lt;/code&gt;, &lt;code&gt;002&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_name_en&lt;/code&gt; → English name of the location&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_name_ar&lt;/code&gt; → Arabic name of the location&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#platform.currentLanguage#&lt;/code&gt; → Joget’s built-in variable that returns &lt;code&gt;"ar"&lt;/code&gt;, &lt;code&gt;"en"&lt;/code&gt;, or another locale code depending on the user’s active language&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Example Use Cases
&lt;/h2&gt;

&lt;p&gt;🌍 &lt;strong&gt;Government or Region Dropdowns&lt;/strong&gt; — show English or Arabic names dynamically.&lt;br&gt;
🏢 &lt;strong&gt;Branch Selector&lt;/strong&gt; — display localized branch names based on user locale.&lt;br&gt;
🏫 &lt;strong&gt;School / University Lists&lt;/strong&gt; — handle multilingual labels seamlessly.&lt;br&gt;
📦 &lt;strong&gt;Product Category Selector&lt;/strong&gt; — automatically adjust category names to the interface language.&lt;/p&gt;


&lt;h2&gt;
  
  
  🛠️ Customization Tips
&lt;/h2&gt;

&lt;p&gt;💡 &lt;strong&gt;Add more languages:&lt;/strong&gt;&lt;br&gt;
You can extend the CASE statement to support multiple locales:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CASE '#platform.currentLanguage#'
    WHEN 'ar' THEN c_name_ar
    WHEN 'fr' THEN c_name_fr
    ELSE c_name_en
END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚙️ &lt;strong&gt;Default language fallback:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ELSE c_name_en&lt;/code&gt; ensures that English labels appear when no match is found.&lt;/p&gt;

&lt;p&gt;🗂️ &lt;strong&gt;Table naming convention:&lt;/strong&gt;&lt;br&gt;
Keep your table name generic — e.g., &lt;code&gt;app_fd_location_mst&lt;/code&gt; — to avoid exposing project-specific data.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 Key Benefits
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Fully automatic localization&lt;/strong&gt; — no extra scripting or filters required.&lt;br&gt;
⚙️ &lt;strong&gt;Simple SQL-only setup&lt;/strong&gt; — works directly in Select Box configuration.&lt;br&gt;
💬 &lt;strong&gt;Consistent user experience&lt;/strong&gt; — respects platform locale settings.&lt;br&gt;
📱 &lt;strong&gt;Multilingual scalability&lt;/strong&gt; — add new columns and locales easily.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 Security Note
&lt;/h2&gt;

&lt;p&gt;⚠️ Always ensure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SQL query only reads from safe, authorized tables.&lt;/li&gt;
&lt;li&gt;No sensitive information is exposed via the &lt;code&gt;label&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;parameterized queries&lt;/strong&gt; if dynamic filtering is added later.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>automation</category>
      <category>sql</category>
      <category>joget</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>🗑️ Add Delete Button and Add Row Feature in Joget Spreadsheet using JS</title>
      <dc:creator>Explorer</dc:creator>
      <pubDate>Tue, 11 Nov 2025 05:44:52 +0000</pubDate>
      <link>https://dev.to/exploringmylifeworks/add-dynamic-delete-button-and-add-row-feature-in-joget-spreadsheet-3jio</link>
      <guid>https://dev.to/exploringmylifeworks/add-dynamic-delete-button-and-add-row-feature-in-joget-spreadsheet-3jio</guid>
      <description>&lt;h2&gt;
  
  
  🧩 Overview
&lt;/h2&gt;

&lt;p&gt;In Joget, the &lt;strong&gt;Spreadsheet form element&lt;/strong&gt; (powered by Handsontable) is perfect for managing tabular data inside forms.&lt;br&gt;
However, it doesn’t natively provide built-in &lt;strong&gt;row-level delete buttons&lt;/strong&gt; or a convenient &lt;strong&gt;add row&lt;/strong&gt; feature.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll learn how to:&lt;br&gt;
✅ Add a delete button to each row in a Joget Spreadsheet.&lt;br&gt;
✅ Create a separate “Add Row” button that dynamically inserts new rows.&lt;br&gt;
✅ Keep the entire implementation simple, secure, and fully functional.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ How It Works
&lt;/h2&gt;

&lt;p&gt;💡 The approach uses a &lt;strong&gt;custom column renderer&lt;/strong&gt; and a &lt;strong&gt;jQuery button event&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The custom renderer injects a &lt;strong&gt;Delete&lt;/strong&gt; button in a column cell.&lt;/li&gt;
&lt;li&gt;The button calls &lt;code&gt;hot.alter("remove_row", row)&lt;/code&gt; to delete that row.&lt;/li&gt;
&lt;li&gt;A separate &lt;strong&gt;Add Row&lt;/strong&gt; button triggers &lt;code&gt;hot.alter("insert_row_below")&lt;/code&gt; to insert a new row at the bottom.&lt;/li&gt;
&lt;li&gt;Both actions are performed dynamically without reloading the form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🧠 &lt;strong&gt;Important:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the spreadsheet &lt;strong&gt;column format type&lt;/strong&gt; to &lt;code&gt;Custom&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Paste the renderer script directly in the custom field configuration.&lt;/li&gt;
&lt;li&gt;Avoid adding any extra comments inside the &lt;code&gt;renderer&lt;/code&gt; function (to prevent Joget parser issues).&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  💻 Full Code
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 🔹 Place this inside your Spreadsheet column (Format Type: Custom) --&amp;gt;&lt;/span&gt;
{ { 
  renderer: function (instance, td, row, col, prop, value, cellProperties) { 
    td.innerHTML = "&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;'button'&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'eaction'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#i18n.ocs_delete_btn#&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;"; 
    td.style.textAlign = "center"; 
    $(document).ready(function () { 
      let hot = FormUtil.getField("appointments_spreadSheet").data("hot"); 
      let btn = td.querySelector(".eaction"); 
      btn.onclick = function (e) { 
        e.preventDefault(); 
        e.stopPropagation(); 
        hot.alter("remove_row", row); 
      }; 
    }); 
  } 
} }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🔹 Add this script in a Custom HTML element below your Spreadsheet&lt;/span&gt;

&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#addRowBtn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FormUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appointments_spreadSheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;hot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hot&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handsontable not initialized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countRows&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;hot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;insert_row_below&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastRow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to add row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- 🔹 Add Row Button --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"addRowBtn"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ➕ Add Row
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 Example Use Cases
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Employee Shift Planner&lt;/strong&gt; – Quickly add or remove shift rows.&lt;br&gt;
✅ &lt;strong&gt;Project Task Tracker&lt;/strong&gt; – Manage task entries dynamically.&lt;br&gt;
✅ &lt;strong&gt;Material Request Form&lt;/strong&gt; – Add/remove item rows on demand.&lt;br&gt;
✅ &lt;strong&gt;Appointment List&lt;/strong&gt; – Delete canceled appointments instantly.&lt;/p&gt;


&lt;h2&gt;
  
  
  🛠️ Customization Tips
&lt;/h2&gt;

&lt;p&gt;💡 &lt;strong&gt;To change the delete button label:&lt;/strong&gt;&lt;br&gt;
Replace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;#i18n.ocs_delete_btn#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with plain text (e.g., &lt;code&gt;"Delete"&lt;/code&gt; or &lt;code&gt;"Remove"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;⚙️ &lt;strong&gt;To use a custom Spreadsheet ID:&lt;/strong&gt;&lt;br&gt;
Update this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FormUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appointments_spreadSheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to match your spreadsheet’s field ID.&lt;/p&gt;

&lt;p&gt;🎨 &lt;strong&gt;To style buttons:&lt;/strong&gt;&lt;br&gt;
Apply CSS in a Custom HTML element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.eaction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e74c3c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;#addRowBtn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🌟 Key Benefits
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;User-friendly:&lt;/strong&gt; Adds clear visual controls to manage rows.&lt;br&gt;
⚙️ &lt;strong&gt;Lightweight:&lt;/strong&gt; Uses only jQuery and built-in Joget APIs.&lt;br&gt;
🚀 &lt;strong&gt;Instant updates:&lt;/strong&gt; No form reload needed.&lt;br&gt;
🧩 &lt;strong&gt;Reusable:&lt;/strong&gt; Works in any spreadsheet-based form.&lt;br&gt;
🔧 &lt;strong&gt;Customizable:&lt;/strong&gt; Easily adjust button text, color, or logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 Security Note
&lt;/h2&gt;

&lt;p&gt;⚠️ Ensure that delete actions are &lt;strong&gt;contextually safe&lt;/strong&gt; —&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only use this feature for &lt;strong&gt;client-side data entry&lt;/strong&gt; (not for record deletion in the database).&lt;/li&gt;
&lt;li&gt;If connected to backend records, validate all deletion logic on the server side before applying changes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Final Thoughts
&lt;/h2&gt;

&lt;h2&gt;
  
  
  By adding these small enhancements, your Joget Spreadsheet becomes much more interactive and user-friendly.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap7hlbau5vfjyoe23unu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap7hlbau5vfjyoe23unu.png" alt=" " width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frezjso2yngz45of120or.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frezjso2yngz45of120or.png" alt=" " width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>joget</category>
      <category>lowcode</category>
      <category>handsontable</category>
    </item>
  </channel>
</rss>
