Project

General

Profile

Embedded Mode Web Client

Introduction

Architecture

Example Use Cases

In-Process vs External Web Application

Runtime Configuration

The required runtime configuration for the embedded mode is mostly related security items, to explicitly allow (or disallow) access to the application from the 3rd party embedded app. Even if the embedded web app is running from within the FWD Server, it is considered a 3rd party app, and needs to be configured explicitly.

A. Configuring the embedded web app and the spawned clients

The embedded app can be run in the same Jetty server as the FWD's Virtual Web Client, or in a standalone server. But regardless, to be able to spawn a FWD Client, a session with the FWD Server must be established - and this session requires a SSL certificate. This is specified in the embeddedWebApp node, under the server/default or server/[name]:

        <node class="container" name="embeddedWebApp">
          <node class="boolean" name="enabled">
            <node-attribute name="value" value="TRUE"/>
          </node>
          <node class="string" name="authAlias">
            <node-attribute name="value" value="ehotel"/>
          </node>
          <node class="string" name="authKeyStore">
            <node-attribute name="value" value="path/to/private-key.store"/>
          </node>
          <node class="string" name="authKeyStorePassword">
            <node-attribute name="value" value="[private-key-store-password]"/>
          </node>
        </node>

The SSL connection to the FWD Server will be established using the certificate specified in the authKeyStore file, which can be opened via the authKeyStorePassword. The FWD session will be established under the authAlias FWD username. Also, this certificate must be installed in the FWD Server's certificate section, and a FWD process account must be configured:

          <node class="process" name="ehotel">
            <node-attribute name="enabled" value="TRUE"/>
            <node-attribute name="description" value="E-Hotel Server"/>
            <node-attribute name="master" value="FALSE"/>
            <node-attribute name="server" value="FALSE"/>
            <node-attribute name="alias" value="ehotel"/>
          </node>

The user under which the embedded app has authenticated with the FWD Server must be allowed to spawn clients, via some specific configurations in the FWD directory:
  • specify the mode under which the embedded app is connected as headless and that it runs the clients in embedded mode:
             <node class="container" name="ehotel">
                <node class="boolean" name="headless">
                  <node-attribute name="value" value="TRUE"/>
                </node>
                <node class="container" name="webClient">
                  <node class="boolean" name="embedded">
                    <node-attribute name="value" value="TRUE"/>
                  </node>
                </node>
              </node>
    
  • specify the default OS account under which the spawning will be performed:
            <node class="container" name="webClient">
              <node class="boolean" name="override">
                <node-attribute name="value" value="TRUE"/>
              </node>
              <node class="string" name="defaultAccount">
                <node-attribute name="value" value="[OS-account]"/>
              </node>
           </node>
    
  • allow this OS account to spawn clients:
            <node class="container" name="trustedspawner">
              <node class="container" name="000100">
                <node class="resource" name="resource-instance">
                  <node-attribute name="reference" value="[OS account]"/>
                  <node-attribute name="reftype" value="TRUE"/>
                </node>
                <node class="trustedSpawnerRights" name="rights">
                  <node-attribute name="allow" value="true"/>
                </node>
                <node class="strings" name="subjects">
                  <node-attribute name="values" value="ehotel"/>
                </node>
              </node>
            </node>
    

B. Allow the embedded application to specify launch options for the spawned client.

In the FWD directory, the remotelaunchoption ACLs are used to specify which bootstrap configuration parameters can be set as a command-line argument for the spawned client. This includes:
  • the client:cmd-line-option:startup-procedure, to specify the FWD entry point.
  • the client:cmd-line-option:param, to specify any -param arguments for the FWD client.
  • the client:web:embedded, to mark the client as embedded.
This is specified via ACLs, using this template:
        <node class="container" name="remotelaunchoption">
          <node class="container" name="000100">
            <node class="resource" name="resource-instance">
              <node-attribute name="reference" value="[boostrap-key]"/>
              <node-attribute name="reftype" value="TRUE"/>
            </node>
            <node class="remoteLaunchOptionRights" name="rights">
              <node-attribute name="allow" value="true"/>
            </node>
            <node class="strings" name="subjects">
              <node-attribute name="values" value="ehotel"/>
            </node>
          </node>
        </node>

where:
  • [bootstrap-key] is one of the bootstrap keys which can be set by the FWD client.
  • ehotel is the FWD user account under which the embedded application has connected.

If a bootstrap key appears in the spawned client's arguments, and is not allowed via an explicit ACL, the spawning will fail. This is used to not allow the spawned client to i.e. target a different FWD server, than the one used to spawn it.

The default options are client:cmd-line-option:startup-procedure=emain.p client:web:embedded=true, and these can be overridden via the embeddedWebApp/cfgOverrides directory configuration.

C. Allow the embedded app to invoke 4GL code from the FWD Server

Implicitly, remote calls from the embedded app into the FWD Application Server are not allowed. Each program exposed to this 3rd party app must be explicitly allowed.

For the FWD user account under which the spawned FWD Client is running, access to these programs is configured via remoteentrypoint ACLs; there are at least two programs which need to be accessed:
  • fwd-embedded-driver.p, which contains FWD's code to manage the FWD Client in embedded mode
  • emain.p, the main program started by the spawned FWD Client.

These programs and any other programs which can be ran from the embedded app, via the Javascript Appserver APIs, must be configured like this:

        <node class="container" name="remoteentrypoint">
          <node class="container" name="000300">
            <node class="resource" name="resource-instance">
              <node-attribute name="reference" value="path/to/program.p"/>
              <node-attribute name="reftype" value="TRUE"/>
            </node>
            <node class="remoteEntryPointRights" name="rights">
              <node-attribute name="allow" value="true"/>
            </node>
            <node class="strings" name="subjects">
              <node-attribute name="values" value="[FWD account]"/>
            </node>
          </node>
        </node>

The program must be specified usings its legacy location in the 4GL source code, a full path. This must be resolvable from the PROPATH, and must match the target used by the Javascript AppServer API call (e.g. the target of a RUN statement from 4GL code).

D. Paths requests by the embedded application

The embedded application uses these URIs to perform requests:
  • /embedded/launch, to spawn a new FWD client
  • /embedded/adm_windows.json, to retrieve a JSON with the ADM windows determined by the conversion rules. This file is not mandatory to be used by the embedded application.
  • /embedded/index.html, with the login page
  • /embedded/custom, holding custom Javascript or other resources. When the embedded app runs from within the FWD Server, this will serve resources from the [pkgroot]/custom package.
  • /embedded/fwd, to retrieve FWD builtin Javascript modules. When the embedded app runs from within the FWD Server, this will serve resources from the com/goldencode/p2j/ui/client/gui/driver/web/res/ package.

Implementation Guide

A. Summary

The embedded application is built around an iframe which is the communication point with the converted application. This is performed by sending requests to execute 4GL code, which can either drive the iframe's ADM window or interpret the results to i.e. build custom HTML widgets, and more.

When implementing the embedded application, once you have the 4GL APIs implemented, for development purposes is easier to run it in standalone mode, and not via the FWD server. This allows you to debug and work on the UI without having to restart the FWD server.

On the embedded application side, there are both Javascript and 4GL files which require custom implementation. The embedded application project is usually a sub-folder of the main converted application, named embedded/. This folder has the following structure:
  • src/resources/custom will contain custom-built Javascript code, images, other JS libraries, not included in FWD.
  • src/resources/fwd will have copies of all the FWD Javascript libraries, from src/com/goldencode/p2j/ui/client/gui/driver/web/res/ folder in the FWD project. The files which can be used are:
    • p2j.embedded.js, main library which allows communication with the iframe
    • p2j.emain.js, a library containing some APIs used for managing the ADM windows in embedded mode
    • p2j.module.js and p2j.modules.css, defines a tree-like JS widget which contains the modules available from the embedded application. These usually are all the ADM windows available to the user, from a main application page, in the legacy application.
    • p2j.menu.js and p2j.menu.css, defines a JS widget which maps the legacy menubar for an ADM window.
    • p2j.toolbar.js, maps an ADM window's toolbar to a JS widget.
    • p2j.chips.js and p2j.chips.css, a widget for managing the opened windows in embedded mode.
When moving an existing application to embedded mode, you can either use the existing framework to manage the ADM windows, or write one from scratch - all you need is to define 4GL APIs, which will be invoked remotely, to manage the ADM windows and other widgets. Using the standard configuration, both custom Javascript and 4GL code needs to be implemented:
  • on 4GL side, standard configuration relies on a emain.p program, which defines the login/logout business logic, application initialization logic, other helper 4GL procedures which are used to extract i.e. the toolbar, menubar, etc from an ADM window
  • the embedded application must write at a minimum an index.html and an emain.js file, which are used to configure the application and define the HTML structure. Beside these, each ADM window can have a custom Javascript file, which can be used to augment the window with other widgets, specific to it. The example configuration in Hotel GUI can be used as a starting point to see how these can be defined.

The FWD conversion project for your application will need to include in the conversion (and in the generated Java code) the fwd-embedded-driver.p program, emain.p and any other programs written specifically for managing the embedded mode.

B. FWD conversion augmentation for ADM programs

The conversion process automatically augments the generated code with helper Java methods which expose information about the structure and widgets available in the ADM window. These APIs are emitted only if the conversion process determines that the ADM program for that window has the feature required to define the API:
  • fwdGetWindow, a 4GL function which will return the ADM window handle, in ADM terms, from the &SCOPED-DEFINE WINDOW-NAME 4GL preprocessor directive.
  • fwdGetMainFrame, a 4GL function which will return the main frame handle, in ADM terms, from the &SCOPED-DEFINE FRAME-NAME 4GL preprocessor directive.
  • fwdGetToolbarFrame, a 4GL function which will return the frame handle for a frame named options-frame.
  • fwdGetADMVersion, a 4GL function which will return the program's ADM version, from the &SCOPED-DEFINE ADM-VERSION 4GL preprocessor directive.
  • fwdGetTabHandle, a 4GL function which will return the handle for the ADM frame which acts as the tabulator. This is determined from the adm-create-objects ADM procedure, from a code like:
    WHEN 0 THEN DO:
       RUN init-object IN THIS-PROCEDURE (
          ...
          INPUT 'Folder-Labels = ' ...
          OUTPUT h-folder
    END.
    

    The h-folder handle will represent the handle for this 'tabbed frame'. This frame is required so that the 4GL driver code can disable changeFolderPage notification from within ADM, as the tabs (pages in embedded mode) will be managed explicitly by the 4GL driver.
  • fwdGetPages, a 4GL function which will return a comma-separated list of page names (what the folder-labels defined in the snippet above has.
  • fwdGetPageHandles, a 4GL function which will return a handle array, with the frame handles which will be shown for a certain page. This again is computed from the adm-create-objects procedure, from each code:
       RUN init-object IN THIS-PROCEDURE (
          ...
          INPUT FRAME f-main,
          OUTPUT h-frame
    
    

    which appears in a WHEN <pagenum> branch.
  • fwdGetHiddenEmbeddedWidgets, a 4GL function which returns the widget names which must not appear on this ADM window - these are provided via FWD preprocessor hints, like:
    <hints>
       <uast name="hidden-embedded-widgets" datatype="string[]">
          <array-val value="logout" />
          <array-val value="exit" />
       </uast>
    </hints>
    

    for each program defining an ADM window.
  • adm_windows.json, a JSON structure with a list of all ADM windows, having a structure like:
    [
       {
          "window-title" : "Hotel Demo FWD Application",
          "program-name" : "main-window.w" 
       }
    ]
    

    For each window, its title and ADM program is specified. This file is generated by the FWD conversion rules, and can be used to build the module list, but is optional.

C. Management of ADM windows

The ADM windows are driven in FWD by logic which exists as 4GL code in the fwd-embedded-driver.p plus various Javascript code to manage the embedded application, and with the help of custom 4GL and Javascript logic, dependent on the how the converted application was written. This section will describe how FWD handles applications which rely on the ADM paradigm.

FWD's embedded driver has complex logic which looks into the opened ADM window, 'cherry picks' certain components (like the toolbar, menubar, tabs) and presents in the embedded iframe only the frames which are associated with the currently selected page in the ADM window. The window will also be modified, so that:
  • the decorations will be removed (border, title); this is possible only in FWD, which will automatically discard the window's decoration, for any FWD Client running in embedded mode.
  • status and message area can be hidden, if configured as such; in 4GL, you can't hide a window's status or message area once it has been realized. FWD disables these checks for embedded mode, allowing toggling the status-area and message-area attributes, even if the window is visible.
  • no menubar will be presented - the window's menubar will be automatically set to unknown and, if a menubar was set, it will be saved to expose it to the embedded application.
  • toolbar and the window's tabs will be hidden; this is automatically performed by the FWD's embedded driver, with knowledge from the conversion generated 4GL functions.

When an ADM window is to be displayed, this is processed and only the frames determined to be the core part of the ADM window will be left visible. These frames (for the currently displayed page) will be relocated so that any empty space above and to the left will be occupied by them; in other words, the frames' location will be translated so that they will be located relative to the window's top-left corner. The window will also have its width set to match the total width of the visible frames, so that it can be centered in the iframe's boundaries.

Modal dialogs are supported; these are special in that they must prevent user action to both the embedded application components and the iframe; nested modal dialogs are supported, too. This is accomplished by using an overlay HTML div component, which is carefully placed so that it is below the topmost modal dialog and above everything else (by manipulating its z-order CSS property). When a modal dialog is discarded, this overlay will be moved behind the new topmost dialog, and so on, until no other modal dialog is displayed - at this time, the overlay will be hidden and the FWD iframe and embedded application are available to the user.

To make the FWD iframe 'blend' with the embedded application, the background color can be synced with that of the iframe's; this is automatically performed by providing a hex-format color (which can be retrieved from the iframe's background), and this will be automatically set to the 4GL window and parent frames for the ADM frames displayed in the current page. This is possible only via FWD's new BGCOLOR-RGB extension attribute.

ADM has some peculiarities which were addressed via named events; for example, the ADM's standard folder component (aka tabs, or pages) are subscribed to changeFolderPage event - but this will interfere when selecting a page from embedded mode, so FWD will unsubscribe the ADM folder component from this event. Additionally, the ADM code was changed so that a fwd-adm-page-changed event will be published if the business logic decides to override the user's (or initially) selected page and select another one - this will notify FWD's driver and will automatically initialize the newly selected page.

The broker-dispatch is used extensively in ADM to dispatch method calls to certain ADM components. In some cases, it was found that business logic can dispatch initialize, adm-hide or adm-destroy, which will interfere with the currently selected page; FWD needs to be aware of these cases, so ADM's broker-dispatch code was added the fwd-dispatch-pre and fwd-dispatch-post named events, published at the begining and the end of the broker-dispatch call. These events can be used to track if the business logic touches the currently displayed ADM window, so that it can process it, to be compatible with embedded mode.

The initial display of an ADM window, switching between ADM windows or selecting another page, is done automatically by the FWD driver:
  • each 4GL program for an ADM window must be ran persistent, to allow the FWD driver to manage it.
  • after the program is ran, the window is initialized by calling the initializeObject (for ADM2) or dispatching the initialize method (for ADM1).
  • selecting a page will require running selectPage (for ADM2) or select-page (for ADM1) procedure, using the ADM program's handle. This procedure has an argument the page which requires to be selected - but, if the business logic decides to select another page, the fwd-adm-page-changed event will be published and FWD will know that another page was selected by the business logic, and inform the embedded application of this (so that it can correctly highlight the visible page).

Not all ADM windows have pages - these cases are automatically identified by the FWD driver and the embedded app; just the window's main frame will remain visible, while everything else will be hidden.

More then one ADM window can be opened in the embedded application, but at a certain time only one can be visible. When switching between ADM windows in the embedded application, FWD's embedded driver will automatically modify the z-order for the selected window to be placed on top, and also hide all other windows, so that only this window remains visible. If the business logic decides to open another ADM window, this window will automatically be managed by FWD's embedded driver, and placed in the list of available windows; if the business logic focuses this window, FWD will show it as the current window automatically.

D. Embedded widgets

FWD provides a default set of widgets which can be used by the embedded application. These are mainly related to emulating the ADM window's 'decorations' (like toolbar, menubar, tabs) and presenting them as HTML widgets.

Each widget has a JS component (a javascript file used to build the widget in the embedded application), and 4GL code which provides the data to build this widget. The standard approach in FWD is for each widget (like a toolbar button, or a menu item) to have a counterpart 4GL widget, to which the action (i.e. button press) is delegated, via an APPLY event TO widgetHwnd statement. This allows the business logic for your application to have minimal changes, and the embedded app to reflect any change in the code or legacy 4GL UI immediately. This is not mandatory, other custom approaches can be used.

D.1. Building the module tree

The module tree is a JS component implemented in FWD by p2j.module.js. Its data can be built from adm_windows.json or retrieved via custom 4GL logic - this logic will reside in a custom fwdGetModules functions, which will return a JSON structure with the following data:

[
  {
     "label"    : <text>,
     "mnemonic" : <char>,
     "module"   : <module ID>,
     "items"    :
     [
        {
           "label"    : <text>,
           "mnemonic" : <char>,
           "module"   : <module ID>,
           "items"    :
           [
             ...
           ]
        },
        ...
     ]
  },
  ...
]

This is a tree-like structure which defines the module's title (label), an optional mnemonic, the module ID (a specific ID uniquely identifying the module in your application - a program name, other identifier) and a list of children modules. Only the leaf nodes will actually open a module.

D.2. Building the pages

The tabs (pages) of an ADM window are computed by default from the 'folder frame' in your ADM window, by invoking the fwdGetPages function in this window's program. Alternatively, your embedded application can define a custom initEmbeddedPageCustom 4GL procedure in your emain.p implementation, which will build the pages from some other component - this is especially useful if your application has the pages emulated via some custom widget, like a TABSET.

This is a replacement of the standard initEmbeddedPage procedure in FWD's fwd-embedded-driver.p - so you will need to manually ensure that only the required frames in this ADM window will be made visible. The definition of this procedure is:
procedure initEmbeddedPageCustom.
   def input param phandle as handle.
   def input param pnum as int.
   def input param hwin as handle.
   def input param hframe as handle.
   def input param bgColorRgb as int.
   def output param pages as char.
end.

where:
  • phandle is the handle of this ADM windows (e.g. module) external program.
  • pnum is the page being shown.
  • hwin is the handle for the 4GL window showing this ADM window.
  • hframe is the handle for the main frame for this ADM window
  • bgColorRgb is the background color used by the iframe (you can use this to synchronize the background color for the 4GL frame/window to match the iframe, via FWD's BGCOLOR-RGB extension attribute).
  • pages is an output parameter which must be returned as a comma-separated list of page names.
The business logic in this procedure should perform:
  • determine the list of pages in this ADM window
  • leave visible only the ADM frames specific to this page
  • center/position the main frame to top row
  • update the background color for main-frame's parent frame(s) and window, via the BGCOLOR-RGB attribute.

D.3. Building the toolbar

Usually, each ADM window comes with one or more toolbars. By default, FWD will use the frame returned by fwdGetTabHandle to gather all visible buttons, and compute a JSON structure containing the details about these buttons. Alternatively, if your emain.p defines a custom fwdListToolbar function, you can override this default implementation and manage your own way to build the toolbar.

The definition of this function is:
function fwdListToolbar returns char (input hframe as handle, output btns as handle extent 100, output btnNum as int).

where:
  • hframe is the handle to the toolbar's frame.
  • btns will be an array holding handles to the toolbar's components, as in 4GL.
  • btnNum is the number of valid entries in btns.
  • the returned value will be a JSON structure describing the toolbar.

The JSON structure is:

[
   {
      "handle"    : <HANDLE attribute>,
      "name"      : <NAME attribute>,
      "label"     : <LABEL attribute>,
      "tooltip"   : <TOOLTIP attribute>,
      "image"     : <image path>,
      "sensitive" : <SENSITIVE attribute>,
      "width"     : <WIDTH-PIXELS attribute>,
      "height"    : <HEIGHT-PIXELS attribute>,
      "x"         : <X attribute>,
      "y"         : <Y attribute>
   },
   ...
]

The above attributes are usually from a BUTTON 4GL widget, but these can be replaced with data computed in other means. Note that the toolbar will order the components based on their X value.

The image must be a resource available via the embedded/custom/path/to/image path. This image can be overriden in the embedded application via custom function configured with the p2j.toolbar.js - this will just return a custom HTML for that toolbar's item, if possible - otherwise, FWD will use button with the image, as it appears in the legacy application.

When a toolbar element is clicked by the user, by default a APPLY "choose" to <handle> will be executed via the inMsg_fwdToolbarItemSelected procedure in fwd-embedded-driver.p. This behaviour can be overridden in emain.p, if a custom inMsg_fwdToolbarItemSelected is defined:

procedure inMsg_fwdToolbarItemSelected.
   def input param hitem as handle.
end.

Knowing the element's handle, the business logic can perform custom actions to run the code associated with that element.

D.4. Building the menu

An ADM window usually can have a menubar. This menubar's components will be exposed to the embedded application automatically, if the embedded application is configured as such. The menubar is driven by default via the p2j.menubar.js component. The structure of the JSON is:

{
   "sensitive" : <MENUBAR:SENSITIVE attribute>,
   "visible"   : <MENUBAR:VISIBLE attribute>,
   "menu-items" :
   [
      {
         "handle"      : <HANDLE attribute>,
         "type"        : <TYPE attribute>
         "subtype"     : <SUBTYPE attribute>,
         "label"       : <LABEL attribute>,
         "accelerator" : <ACCELERATOR attribute>,
         "mnemonic"    : <the label's mnemonic>,
         "sensitive"   : <SENSITIVE attribute>,
         "read-only"   : <READ-ONLY attribute>,
         "visible"     : <VISIBLE attribute>,
         "toggle-box"  : <TOGGLE-BOX attribute>,
         "checked"     : <CHECKED attribute>,
         "menu-items"  :
         [
           ...
         ],
      },
      ...
   ]

This is a tree-like structure describing the entire menubar. When a sub-menu or menu-item is selected from the embedded emulated menubar, an APPLY will be emulated in the business logic for that element, with:
  • a MENU-DROP event, for a SUB-MENU element
  • a CHOOSE event, for a MENU-ITEM element
  • a VALUE-CHANGED event, for a MENU-ITEM which has the TOGGLE-BOX attribute set.

The APPLY will target the handle for that menubar element. In fwd-embedded-driver.p, the inMsg_fwdMenuItemSelected procedure will be invoked and will take care of executing the APPLY.

E. Writing the custom driver code

E.1. Configuration

The standard configuration is done in Javascript, after the p2jInitialized API message is received from the 4GL business logic, via P2J-REMOTE-CALL (informing the embedded web app that the auth passed). The configuration requires to specify the following details, in a Javascript object:
{
   "toolbar" :
   {
      "container"   : "fwdToolbar",       // ID of the toolbar element
      "customImage" : customImageFunc,    // function(imagePath, tooltip, label)
      "imageFolder" : "embedded/custom/", // the root folder to add as a prefix
      "handler"     : p2j.toolbar         // instance of p2j.toolbar
   },
   "menubar" :
   {
      "container" : "fwdMenubar", // ID of the menubar element
      "event"     : openMenuItem, // function(handleId, callback)
      "handler"   : p2j.menubar   // instance of p2j.menubar
   },
   "module" :
   {
      "container" : "fwdModule",        // ID of the module element
      "event"     : showEmbeddedWindow, // function(moduleId, windowTitle)
      "handler"   : p2j.modules         // instance of p2j.modules
   },
   "chipset" :
   {
      "container" : "window-chip-set", // ID of chipset element
      "remove"    : removeWindow,      // function(chipId)
      "interact"  : showWindow,        // function(chipId)
      "handler"   : p2j.chips          // instance of p2j.chips
   },
   "initCallback"    : hideLoading,     // function(), executed after initialization
   "populateWindows" : undefined,       // function(JSON) - populate the available windows list, if not using a p2j.module
   "topWindowChanged": undefined,       // function(windowId), executed when the top window has switched.  windowId is a HWND value. 
   "populatePages"   : populatePages,   // function(windowId, pagesTitles[], currentPage)
   "windowDestroyed" : windowDestroyed, // function(windowId) 
   "defaultBgColor"  : defaultBGColor,  // colorRgb

   "embedded-status-area"    : true,
   "show-status-callback"    : showStatus, // function(txt)

   "embedded-message-area"   : true,
   "show-messages-callback"  : showMessages // function(txt[])
}

where:
  • the toolbar, menubar, module and chipset are optional. Each of them have a:
    • container, representing the ID of a parent DOM element where the widget will be attached
    • handler, a JS object which will be used to construct the widget
  • initCallback is a function executed after the configuration has been performed. This can automatically load a certain window or just do nothing.
  • populateWindows is an optional function which loads the module list from the adm_windows.json.
  • topWindowChanged is an optional function executed when the top window has changed, via the business logic or by the user.
  • populatePages is a function which allows you to build the pages switching widget
  • windowDestroyed is an optional callback exeuted when an open window has been destroyed (by the user or business logic).
  • defaultBgColor is optional and represents a hex-encoded color, to be set as the background for the 4GL window.
  • embedded-status-area and show-status-callback are optional and are used to receive notifications from the FWD client, when a new status message has been set for the current window. If the flag is set, the 4GL window will have its status-area removed.
  • embedded-message-area and show-messages-callback are optional and are used to receive notifications from the FWD client, when a MESSAGE statement is executed with the current window. If the flag is set, the 4GL window will have its message-area removed.
The additional configuration for which widget component describes:
  • for the toolbar, how to process the HTML element describing an certain item in the toolbar.
    • customImage is an optional function which, if it returns a valid value, this will represent the HTML code for this toolbar's item. It can be used to override a certain imagePath and build a more friendly element.
    • when the toolbar item uses the same image as the Virtual Desktop FWD client, imageFolder represents URI prefix to be added to the HTML image's path, so that it can be retrieved.
  • for the menubar, the event represents an optional function which will be executed before the 4GL action (representing the user's click on item) is performed. This can be
  • for the module, event is a mandatory function which will perform the logic to open a module (i.e. the ADM window), when the user selects it. The p2j.emain.showEmbeddedWindow is the default function which can be used for performing the 'module invocation.
  • the chipset is usually used to manage the opened ADM windows. The remove callback is executed when a chip is deleted, and interact when the chip is selected:
    • for remove, a function is required to specify how to close the ADM window. p2j.emain.removeWindow can be used.
    • for interact, a function is required to specify what to do when an ADM window is selected. p2j.emain.switchWindow can be used.

API

The Javascript AppServer defines APIs to access 4GL business logic, by invoking external programs, internal procedures or user-defined functions, and more. The APIs are split into:
  • emulating RUN statement or user-defined function calls
  • managing named events via PUBLISH, SUBSCRIBE or UNSUBSCRIBE
  • other misc APIs

A. running 4GL business logic via external programs, internal procedures and user-defined functions

This starts by obtaining a handle to an external program ran persistent, which is allowed access to, via the remoteentrypoint configuration; once this handle value is obtained, any public internal procedures or functions can be ran from within it. The available APIs are accessed via p2j.embedded container:
  • invokeExtenalProgram(callback, name, args), to invoke an external program.
  • invokeExternalProgramPersistent(callback, name, args), to invoke an external program ran persistent.
  • invokeFunction(callback, name, args, handle), to invoke a user-defined function in the specified external program's handle.
  • invokeProcedure(callback, name, args, handle), to invoke an internal procedure in the specified external program's handle.

A persistent program can be deleted via the deleteHandle(callback, handleId) API.

When an API has a callback argument, this is a function which will be called when the response is received from the FWD Application Server, for this call. The function will take a single argument, msg, which is a JSON with the following structure:
  • type set to p2j.remote.INVOKE
  • id set to the request message ID
  • mode set to p2j.remote.RESPONSE
  • payload, with the response data
The payload will include:
  • handle, with the handle ID for external programs ran persistent, or procedure or function calls. This is a hex-encoded 64 bit value, as string.
  • return, the return value from this call; this can be RETURN-VALUE for procedures or the function's return value, otherwise.
  • error, error-num, error-msg - set if there was an ERROR condition raised
  • quit, set if there was a QUIT condition raised
  • stop, set if there was a STOP condition raised
  • arguments, key-value pairs with any OUTPUT or INPUT-OUTPUT arguments. The keys are always lowercased, and case-sensitive. The order is indeterminate.

When there is a args argument, this will be key-value pairs with the argument values. The keys must always match argument names for the target, and they are case-insensitive. The order in which they appear is indeterminate, and no mode is required. TABLE, TABLE-HANDLE, EXTENT, BUFFER is not supported.

B. Managing named events

Named events work in a similar way as 4GL events, with some extensions in FWD:
  • an event can be published globally, to all FWD clients subscribed to it.
  • you can subscribe to an event in this FWD client or globally, and receive the publish notification.
  • you can unsubscribe from an event.

When publishing events, the publisher can be an external program ran persistent by the JS AppServer, or a special kind of resource, called EXTERNAL RESOURCE in FWD. This is a kind of resource which can be created by the embedded application, to manage published events. It will reside in the associated FWD Client's context, but it will need to be managed by the embedded application (i.e. deleted and created). This external resource is created via the createResource(callback) API, and can only be deleted via the deleteHandle(callback, handleId) API.

The availabled named event management APIs are:
  • subscribe(callback, global, subscriber, event, publisher, method), used to subscribe a specific resource to the specified event, where:
    • callback is a function which will receive the response
    • global will listen for events from any FWD clients
    • subscriber is the subscriber's ID, and is optional; if not set, an external resource will be created and received via the payload.subscriber-id, in the response.
    • event is the event name
    • publisher is the publisher, which can be a hex-encoded handle ID representing a persistent program or an external resource
    • method is the Javascript function when the event is raised; the arguments will be passed via an 0-indexed array, with each argument string-encoded.
  • unsubscribe(callback, global, subscriber, event, publisher), used to unsubscribe a specific resource from the specified event, where the arguments have the same meaning as in subscribe.
  • publish(event, publisher, args), to publish an event on behalf of the specified publisher, which can be the handle for an external program or an external resource.
The callback will have the following structure:
  • type set to p2j.remote.SUBSCRIBE, p2j.remote.UNSUBSCRIBE or p2j.remote.PUBLISH
  • id set to the request message ID
  • mode set to p2j.remote.RESPONSE
  • payload, with the response data, which will be:
    • subscriber-id - hex-encoded handle ID; only for subscribe and publish
    • error, error-num, error-msg - set if there was an ERROR condition raised
    • event, the published event, only for publish
    • parameters, a 0-indexed array with the parameters, only for publish

C. Other misc APIs

External resources are a special kind of resource available only in FWD. These can be created by non-4GL 3rd party applications, and currently can be used only for named event mananagement. They reside on the FWD Client's context, but are managed by the embedded application, created or destroyed:
  • createResource(callback) will create this external resource, with its handle passed to the callback function - this has a single argument a JSON msg, and payload.resource-id will be the hex-encoded 64 bit value, represneting the resource's ID, as string.

An external resource or an external program ran persistent can only be deleted by the embedded application which created it, via the deleteHandle(callback, handleId) API; also, this API can only delete these kind of resources - anything else is prohibited. The callback's msg.payload will have only error, error-num, error-msg set if there was an ERROR condition raised.

The embedded application can work with widgets via their HWND value - this is an internal ID uniquely identifying each widget, like buttons, menu-items, etc. They can be exposed via OUTPUT arguments from invoked 4GL business logic, and the HWND can be used to emulate an APPLY to them, for example to press a 4GL button. This can be performed via the applyEvent(event, widgetId) API.

NO-ERROR mode is not enabled by default, but this can be managed via the silentErrorEnable() and silentErrorDisable() APIs; once set, it is assumed that all calls will be performed under NO-ERROR mode, until this mode is unset.

D. Executing JS code in embedded app, from the 4GL business logic.

A new 4GL statement, P2J-REMOTE-CALL, was added in FWD - this is a special statement which executes a remote call into the embedded app, synchronously - it will block and wait a response from the embedded application.

The statement has a single argument a string value, a JSON structure; this JSON is mandatory to have an api key with the name of the remote API which will be invoked. The other keys can be used to transmit additional data.

To be able to execute a P2J-REMOTE-CALL on the remote, embedded app, side, you will be required to register a parser and a handler function for each api used by the P2J-REMOTE-CALL function. The p2j.embedded.registerIncomingRequest(api, respCallback, parseCallback) must be used to specify, for each api:
  • respCallback, a callback function having as single argument a JSON object.
  • parseCallback, a function used to interpret the JSON sent by the P2J-REMOTE-CALL, and build a new JSON structure which will be used as an argument for respCallbac.

It is important for the response and parse functions to finish execution and terminate cleanly, as the 4GL side will wait for a response message that it finished execution - this is a simple OK message implicitly sent by p2j.embedded.js.

If an api is no longer needed, this can be deregistered via p2j.embedded.deregisterIncomingRequest(api).

Other APIs which can be used to register callbacks which can receive notifications from the FWD Client are:
  • setPageLoadedCallback(callback) - callback executed when the iframe has loaded
  • setPageClosedCallback(callback) - callback executed when the iframe has closed
  • setSocketDisconnectedCallback(callback) - callback execued when the web socket has disconnected
  • setSocketReconnectedCallback(callback) - callback execued when the web socket has reconnected
  • setWaitForInputCallback(waitForInputCallback) - callback executed when the iframe is in a state waiting for input from the user; in such cases, the iframe must be visible and the user must be able to send input (keyboard, mouse).
In the standard embedded JS driver, there are some builtin api's used to manage the embedded application or windows:
  • P2J-REMOTE-CALL('~{ "api":"p2jInitialized" ~}'), executed after the business logic has performed the authentication, to notify the embedded app that it can initialize
  • P2J-REMOTE-CALL('~{ "api":"showError" , "error":"<msg>" ~}') - shows a specific error messages in the embedded application's login screen
  • P2J-REMOTE-CALL('~{ "api":"admPageRefresh", "windowId":"<windowid>", "page":"<pageNum>" ~}')' - special API executed when the page has been refreshed (hide, view, etc) by the ADM business logic
  • P2J-REMOTE-CALL('~{ "api":"admPageChanged", "windowId":"<windowid>", "page":"<pageNum>" ~}'). - special API executed when the page has been changed by the ADM business logic
  • P2J-REMOTE-CALL('~{ "api":"notifyDisconnect" ~}')., executed before emain.p program has terminated.

© 2016-2019 Golden Code Development Corporation. ALL RIGHTS RESERVED.