Tutorial 9

Creating your own eXtensions

Getting Started

eXtensions are JavaScript objects with properties, methods and events. They are defined in a JavaScript file.

 

The name of the JavaScript file that defines the extension must start with

Extension_ followed by your chosen extension name.

 

In this tutorial we will create a simple push button extension:

 

Which will look like this:

 

Clicking the button will send the Enter key by default.

 

To begin creating this extension, start Notepad and save the file with the name Extension_SimplePushButton.js

 

Remarks:

 

Do not use any combination of the prefix ax (Ax, AX, etc) they are reserved for aXes only developed extensions.

 

When adding eXtensions to 5250 screens for subssequent use in an aXes-Cloud environment ensure you are executing them in the same way that your end users will. See If You Use aXes-eXtensions with aXes-Cloud in the Best Practices tutorial for more details.

 

A basic eXtension skeleton

Depending on your JavaScript skills, when writing the first extension you may choose an existing extension as the starting point. Otherwise, you can use this basic eXtension skeleton:

 

function DEFINE_EXTENSION_XYZ()

{

       var definition =

       {

 

              properties:

              {

 

              },

 

              init: function(element, elementContainer)

              {

                  this._element = element ;

                  this._elementContainer = elementContainer ;

 

 

   

 

   if (AXES.isTS2Engine) element.addListener("AddedToDOM", this._handleAddedToDOM, this);

              },

 

              render: function(element, elementContainer)

              {

 

 

 

 

              },

     

              destroy: function(element, elementContainer)

              {

                  element.removeListener("AddedToDOM", this._handleAddedToDOM);

                 delete this._element;

                 delete this._elementContainer;

              },

 

                            

              _handleAddedToDOM: function(event)

              {

 

               },

 

      <other event handler(s)>

 

       };

 

       AXES.Extensions.add(definition);

}

 

DEFINE_EXTENSION_XYZ()

 

Because we know the name of the extension to create we can replace XYZ with our chosen name of SimplePushButton.

 

Copy this skeleton into your Extension_SimplePushButton.js file:

 

function DEFINE_EXTENSION_SimplePushButton()

{

       var definition =

       {

               name: "SimplePushButton", 

 

              properties:

              {

 

              },

 

              init: function(element, elementContainer)

              {

                  this._element = element ;

                  this._elementContainer = elementContainer ;

 

 

 

 

 

   if (AXES.isTS2Engine) element.addListener("AddedToDOM", this._handleAddedToDOM, this);

              },

 

              render: function(element, elementContainer)

              {

 

 

              },

     

              destroy: function(element, elementContainer)

              {

   element.removeListener("AddedToDOM", this._handleAddedToDOM);

                 delete this._element;

                 delete this._elementContainer;

 

              },

 

              _handleAddedToDOM: function(event)

              {

 

               },

 

      <other event handler(s)>

 

 

       };

 

       AXES.Extensions.add(definition);

}

 

DEFINE_EXTENSION_SimplePushButton ()

 

The aXes defined properties section

 

The beginning of the eXtension object definition specifies the properties that identify it in aXes:

 

function DEFINE_EXTENSION_SimplePushButton()

{

       var definition =

       {

 

In this section you specify these predefined properties:

  •  name: the name of this extension
  • type: whether the extension applies to an element or to a screen.  
  • display: the description that is visible when you select the extension
  • group: determines whether when selecting this extension all other extensions in the same group for the same element are deselected.
  • subType: determines whether the extension is used only for New elements or for both new and existing fields on the screen.
  • category: determines which category this extension belongs when displayed in Extensions Toolbox list. Do not add this property if you will not categorize the extension.

To add a new category, the name must be “cat<categoryName>”. e.g. catMobile

Then, define the text display in Texts_Dev_<lang>.txt text file as follows:

           "catMobile.display"                            : "Mobile",

 

In your extension file, copy and paste these properties and values:

 

       var definition =

       {

name: "SimplePushButton", 

group: "vis",

type: "element",

display: "Simple Push Button",

subType: "new",

 

 

The User-defined properties section

In the next section you specify the properties and events that are under your control, the ones that are visible to the developer in aXes Designer:

 

    

 

We are going to add four properties as in the above picture to the button extensions:

 

  • caption: the button caption
  • style:  to allow the user to apply a style to the button
  • onClick: the onClick event
  • sizeToField: to allow the user to resize the button to its containing field or not

 

Properties have a type. They can also have a default value and a description.

 

   properties:

   {

     caption: { type: "String", defaultStatic:"OK", description: "Text to appear on the button." },

     style:   { type: "Style", description: " Style to apply to the extension." },

     onClick: { type: "Event", defaultDynamic: "SENDKEY(ENV.defaultKey);", description: " The button's onclick event." },

     sizeToField { type: "Boolean", defaultStatic: true, description: "Indicates whether the button should size to its containing field or its content." };

   },

 

In your extension copy and paste the above four properties. Your extension should now look like this:

 

function DEFINE_EXTENSION_SimplePushButton()

{

    var definition =

    {

       name: "SimplePushButton", 

       group: "vis",

       type: "element",

       display: "Simple Push Button",

       subType: "new",

 

       properties:

       {

        caption: { type: "String", defaultStatic:"OK", description: "Text to appear   on the button." },

        style:   { type: "Style", description: "Style to apply to the extension." },

        onClick: { type: "Event", defaultDynamic: "SENDKEY(ENV.defaultKey);", description: " The button's onclick event." },

sizeToField { type: "Boolean", defaultStatic: true, description: "Indicates whether the button should size to its containing field or its content." };

       },

 

 

The program section

 

You write the executable code in the methods in the next section.

 

An extension has at least three methods, init, render and destroy and event handler _handleAddedToDOM and any other event handlers that have been defined.

 

Initialization

 

The init method executes at design time and runtime:

 

init: function(element, elementContainer)

{

    this._element = element ;

    this._elementContainer = elementContainer ;

 

 

 

    if (AXES.isTS2Engine) element.addListener("AddedToDOM", this._handleAddedToDOM, this);

},

 

The init method is where you usually create the HTML element that your extension will visualize itself as.

 

It receives two parameters:

 

element – a reference to the aXes element object

elementContainer – usually the element's parent DIV element

 

In this example you use the standard createElement dhtml method to create a button:

 

var btn = document.createElement("button");

 

Get the value of the caption property:

 

    var btnCaption = this.getPropertyValue("caption");

 

Set the dhtml innerHTML button property to the value of the caption:

 

    btn.innerHTML = btnCaption;

 

Add a user-defined property to the button reference. Here we set the default function key we want to send.

 

    btn.sendKey   = "Enter";

 

Add the onclick event to the html button. Because the init logic is executed at design time but we don't want the onclick to fire when we are editing the screen, we make sure to attach the event only when not in design mode:

 

    if (!this.isDesignMode())

    {

      var self = this;

      btn.onclick = function() { self._onClick(btn); };

    }         

 

Make the button part of the tabbing order:

 

    this.applyTabIndex(btn);

 

Append the html button as a child of the container:

 

    elementContainer.appendChild(btn);

 

Save a reference to the html button for easier access in other routines:

 

    this.buttonElem = btn;

 

The init method should look like this:

                                                                      

init: function(element, elementContainer)

{

   this._element = element ;

   this._elementContainer = elementContainer ;

   

   var btnCaption = this.getPropertyValue("caption");

 

   var btn = document.createElement("button");

   btn.innerHTML = btnCaption;

   btn.sendKey   = "Enter";

 

   if (!this.isDesignMode())

   {

      var self = this;

      btn.onclick = function() { self._onClick(btn); };

   }         

 

   this.applyTabIndex(btn);

        

   elementContainer.appendChild(btn);

            

   this.buttonElem = btn;

 

 

   if (AXES.isTS2Engine) element.addListener("AddedToDOM", this._handleAddedToDOM,     this);

},

 

 

Rendering

 

The render method executes at design time and runtime, after the init method. In this example we are going to use it to size the button.

 

When customizing a screen, you size the button on the screen by stretching it. When the screen is saved the size needs to be recorded as a property of the element:

 

    var size = element.getSize();

    button.style.width = (size.width).toString() + "px";

    button.style.height = (size.height).toString() + "px";

 

 

When sizing controls, both TS1 and TS2 modes have to be handled. TS1 sizing is done within the render method. TS2 sizing is done in the _handleAddedToDOM event handler. 

 

TS1

 

if (!AXES.isTS2Engine)

{

   /* TS1 sizing */

   var size = element.getSize();

   button.style.width = (size.width).toString() + "px";

   button.style.height = (size.height).toString() + "px";

}

 

 

 

 

 

TS2

 

_handleAddedToDOM: function(event)

{

/* TS2 sizing */

/* To improve rendering performance, The TS2 engine doesn't add a field to the */

/* DOM until all the extensions have been rendered. Elements do not have */

/* dimensions until they are added to the DOM so we use this event to do */

/* anything that requires knowledge of the size of things. */

/* For best rendering performance, avoid using this unless absolutely necessary. */

 

/* we have to refer to the element and the button via "this" */

 

   var size = this._element.getSize();

   this.buttonElem.style.width = (size.width).toString() + "px";

   this.buttonElem.style.height = (size.height).toString() + "px";

 

}

 

 

We are also going to apply the styles the developer may have specified in the style property:

 

   var style = this.getPropertyValue("style");

   this.applyStyle(style, btn);

 

Then, check the value of sizeToField property. If the value is true, set the button’s both width and height to 100% so that the size corresponds to its containing field.

   if (this.getPropertyValue("sizeToField")) {

      this.buttonElem.style.width = "100%";

      this.buttonElem.style.height = "100%";

   }

 

The render method should look like this:

 

   render: function(element, elementContainer)

   {

      var button = this.buttonElem;

      if (!AXES.isTS2Engine)

      {

         /* TS1 sizing */

         var size = element.getSize();

         button.style.width = (size.width).toString() + "px";

         button.style.height = (size.height).toString() + "px";

      }

      var style = this.getPropertyValue("style");

      this.applyStyle(style, button);

      if (this.getPropertyValue("sizeToField")) {

          this.buttonElem.style.width = "100%";

          this.buttonElem.style.height = "100%";

      }

   },

 

The _handleAddedToDOM event handler should look like this:

 

  

_handleAddedToDOM: function(event)

{

/* TS2 sizing */

/* To improve rendering performance, The TS2 engine doesn't add a field to the */

/* DOM until all the extensions have been rendered. Elements do not have */

/* dimensions until they are added to the DOM so we use this event to do */

/* anything that requires knowledge of the size of things. */

/* For best rendering performance, avoid using this unless absolutely necessary. */

 

/* we have to refer to the element and the button via "this" */

 

   var size = this._element.getSize();

   this.buttonElem.style.width = (size.width).toString() + "px";

   this.buttonElem.style.height = (size.height).toString() + "px";

},

 

 

 

 

 

Destroying

 

The destroy method is crucial for your extension to work properly. In this method you should remove any object references, objects, events and anything else that is not locally defined in one of the methods.

 

If you created a variable of any type in the render method like this:

 

var x = "aaa";

 

then you don't need to set it to null or do anything to it in the destroy method.

 

However, you may have added it as a reference to some other object, typically the this pointer or some html element. For example, you may have done something like this:

 

this.Xvar = x;

 

In this extension button we made a reference to the buttonElem:

 

   this.buttonElem = btn;

 

All references to it should be destroyed to avoid memory leaking. Add this code to the destroy method:

 

   destroy: function(element, elementContainer)

   {

      this.buttonElem.onclick = null;

      this.buttonElem.sendKey = null;             

      this.buttonElem = null;

      delete this.buttonElem;

  

      element.removeListener("AddedToDOM", this._handleAddedToDOM);

      delete this._element;

      delete this._elementContainer;

   },

 

 

Events

 

For this extension we will define an onClick event.

 

When we attached the event to the button, we will pass the button as a parameter:

 

     self._onClick(btn)

 

All we need to do in the extension is raise the event. The developer writes the event code at design time. When we raise the event, the event code is executed. In this button extension it will execute this code by default:

 

 

 

When we set up the extension in the init method, we specified Enter as the default key, so this button will now automatically send the Enter key.

 

Replace this text:

 

<event handler(s)>

 

With this event routine:

 

_onClick: function(button)

{

   var env = { defaultKey : button.sendKey };

   this.raiseEvent("onClick", env);

 

}

 

Save the changes to your extension file.

Solution

The entire extension code should look like this:

function DEFINE_EXTENSION_SimplePushButton()

{

var definition =

       {

 

       name: "SimplePushButton", 

       group: "vis",

       type: "element",

       display: "Simple Push Button",

       subType: "new",

 

       properties:

       {

        caption: { type: "String", defaultStatic:"OK", description: "Text to appear   on the button." },

        style:   { type: "Style", description: "CSS to apply to the extension." },

        onClick: { type: "Event", defaultDynamic: "SENDKEY(ENV.defaultKey);", description: " The button's onclick event." },

        sizeToField: { type: "Boolean", defaultStatic: true, description: "Indicates whether the button should size to its containing field or its content." }

       },

 

       init: function(element, elementContainer)

       {

          this._element = element ;

          this._elementContainer = elementContainer ;

          

          var btnCaption = this.getPropertyValue("caption");

 

          var btn = document.createElement("button");

          btn.innerHTML = btnCaption;

          btn.sendKey   = "Enter";

 

          if (!this.isDesignMode())

          {

             var self = this;

             btn.onclick = function() { self._onClick(btn); };

          }         

 

          this.applyTabIndex(btn);

               

          elementContainer.appendChild(btn);

            

          this.buttonElem = btn;

 

 

          if (AXES.isTS2Engine) element.addListener("AddedToDOM", this._handleAddedToDOM, this);

       },

 

       render: function(element, elementContainer)

       {

          var button = this.buttonElem;

          if (!AXES.isTS2Engine)

          {

             /* TS1 sizing */

             var size = element.getSize();

             button.style.width = (size.width).toString() + "px";

             button.style.height = (size.height).toString() + "px";

          }

          var style = this.getPropertyValue("style");

          this.applyStyle(style, button);

 

          if (this.getPropertyValue("sizeToField")) {

             this.buttonElem.style.width = "100%";

             this.buttonElem.style.height = "100%";

         }

       },

     

       destroy: function(element, elementContainer)

       {

          this.buttonElem.onclick = null;

          this.buttonElem.sendKey = null;                

          this.buttonElem = null;

          delete this.buttonElem;

      

          element.removeListener("AddedToDOM", this._handleAddedToDOM);

          delete this._element;

          delete this._elementContainer;

       },

 

      _handleAddedToDOM: function(event)

      {

      /* TS2 sizing */

      /* To improve rendering performance, The TS2 engine doesn't add a field to the */

      /* DOM until all the extensions have been rendered. Elements do not have */

      /* dimensions until they are added to the DOM so we use this event to do */

      /* anything that requires knowledge of the size of things. */

      /* For best rendering performance, avoid using this unless absolutely necessary. */

 

      /* we have to refer to the element and the button via "this" */

 

         var size = this._element.getSize();

         this.buttonElem.style.width = (size.width).toString() + "px";

         this.buttonElem.style.height = (size.height).toString() + "px" ;

 

      },

 

 

 

     _onClick: function(button)

     {

       var env = { defaultKey : button.sendKey };

       this.raiseEvent("onClick", env);

 

     }

 

  };

 

  AXES.Extensions.add(definition);

}

 

DEFINE_EXTENSION_SimplePushButton ()

 


 

Testing the Extension

To test your new extension copy Extension_SimplePushButton.js and add it to your project folder (\ts\screens\<<your folder>>) or if you do not have one, to the \ts\screens folder. See The program development lifecycle.

 

Use the WRKLNK command in your IBM i to ensure that the file only has *PUBLIC *R authorities.

 

Start aXes as a developer.

 

Start customizing a screen and add a new element.

 

Verify that the Simple Push Button extension is present in aXes Designer.

 

Select the Simple Push Button extension.

 

Size and position the button, then save your customized screen.

 

Test that clicking the button sends the Enter key.

 

 

The program development lifecycle

 

Develop / Test

 

During the extension's develop / test phase, you should put your new extension in your project folder. This is found on the server, under

 

\\<<your axes installation name (typically axes)>>\ts\screens\<<your folder>>

 

If you don't yet have a project folder, you can sign on as a developer and create one from the projects home page:

 

 

 

By keeping your work in this folder, any mistakes you make will not impact on other developers or users.

 

Tested

 

When your extension is tested, if you want to make it available to users and other developers, you can promote your extension file to the screens folder:

 

\\<<your axes installation name (typically axes)>>\ts\screens

 

and delete it from your project folder.

 

(if you want the extension to be available only for your project you can leave it in the project directory and not promote it to the screens folder)

 

Deploy

 

To deploy your extension to another site, you just need to ensure that the Extension_XXXX.js file is moved into the screens folder in the target axes installation.

 

See Extension_Tutorial 9 for detailed instructions of how to create a complete deployment package which will include your new extension.

 

 

 

 


 

The extension checklist

 

Check/Consideration

Okay

The main wrapper function is named DEFINE_EXTENSION_XXEEEEEEEEEEE

(where XX is NOT ax AX aX or Ax)


 

The EEEEEEEEEEE portion of the name uses upper and lowercase


 

The properties section identifies the name, group, type, display and subType properties of the extension


 

There are standard published methods init, render and destroy


 

All unpublished (private) functions are prefixed with _


 

There is a call to DEFINE_EXTENSION_XXEEEEEEEEEEE();


 


 


 

Properties


 

  • All published properties have a description property that refers to an appropriate long description (used as a tooltip) for the property

 

  • All published properties are named in camel case

 

  • All unpublished (private) properties are prefixed _

 


 


 

The destroy method


 

  • Nullifies all references kept in private properties, e.g. this._myProperty

 

  • Destroys all arrays

 

  • Nullifies all references kept in HTML elements (very important to prevent leakage)

 

  • Unattaches all events previously attached

 


 


 

The render method


 

  • All UI visualization logic is placed in render method so it correctly responds to property changes made at design time

 

  • Design mode awareness exists in logic, especially UI event handling

 

  • Visualization logic can handle the change of associated field content coming from places other than UI (eg: another script changes field content associated with a checkbox, checkbox should change)

 


 


 

Tracing


 

  • Tracing has been added to complex or error prone areas via AXES.Trace.Output() functions

 

  • Tracing has not been added for basic execution of init, render or destroy routines - this is done automatically by the environment

 


 


 

Tab positioning logic has been added in Init


 


 


 

Tested in these situations:


 

v  Normal field – alpha input


 

v  Normal field – alpha output


 

v  Normal field – numeric input


 

v  Normal field – numeric output


 

v  Subfile field – alpha input


 

v  Subfile field – alpha output


 

v  Subfile field – numeric input


 

v  Subfile field – numeric output


 

v  New screen element 


 

v  Multiple usage on same 5250 screen


 

v  Multiple usage on multiple 5250 screens


 


 


 

Promoted to Screens folder


 


 


 

Standard documentation sheet produced


 

 

 

Questions?

If you have any questions about creating an extension, please contact your product vendor or aXes Support.