NexJ Logo

Behavior: UI events, UI actions, and data actions


Learning objectives

In this lesson, we will learn:

  • How UIEvents and UIActions handle actions taken by the user.
  • About DataActions, which are events raised from changes in the model.

Key concepts

UIEvent - fires when a user performs an action such as pressing a button, selecting an item in a menu or list, or selecting a row in a grid. There are system UIEvents and custom UIEvents. Custom UIEvents are created in the Presentation layer. You can handle UIEvents by configuring a UIAction

There are two main ways to fire UI events:

  • Interaction with a control (view) on which a UIEvent is set.

  • Running a script that uses the fireUIEvent API method.

A UIEvent can be enabled or disabled dynamically by binding it to a boolean class attribute or the ClientModel.

UIAction - a user interface handler that responds to a UIEvent in portlets, dialogs, and layouts. A UIAction launches (and contains) one of the following when triggered by an event:

  • Pop-up action - opens a dialog.
  • Navigate action - navigates to another portlet in the application.
  • Script action - runs a custom script.

DataAction - a handler for a change that occurs in an underlying model.

Based on your work from the previous lesson, your files should appear as follows. If not, copy the code below and paste into your files before continuing.

training:Contact.portal
<Portal caption="idsa.training.portal.Contact.caption">
   <Tool name="toolNotifications" caption="ids.notifications" icon="icon:notifications" event="setSidebar" parameter="NOTIFICATIONS"/>   
   <WorkspaceRef name="refSandbox" workspace="training:Sandbox"/>
   <WorkspaceRef name="refHome" workspace="mda:Home"/>
   <WorkspaceRef name="refContacts" workspace="mda:Contacts"/>
   <Drawer event="setSidebar" name="refNotifications" portlet="mda:NotificationDrawer"/>
</Portal>
training:Sandbox.workspace
<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money">
   <Page name="page">
      <Tab layout="cols:3 fluid:true" name="tabDetail" type="grid">
         <PortletRef name="training_EntityList_portlet" portlet="training:EntityList"/>
      </Tab>
   </Page>
</Workspace>
training:EntityList.portlet
<Portlet>
   <Composite name="composite">
      <LayoutRef layout="training:EntityList" name="training_EntityList_layout"/>
   </Composite>
</Portlet>
training:EntityList.layout
<Layout class="Entity">
   <UIActions>
      <UIAction event="sysProperties">
         <Popup association="tblEntityList" edit="true" screen="training:mda:EntityProperties"/>
      </UIAction>
   </UIActions>
   <Composite name="composite">
      <Table caption="idsa.training.layout.EntityList.caption" name="tblEntityList" rows="(@)">
         <Menu name="mnuRightClick">
            <Item caption="ids.edit2" event="sysProperties" icon="edit" name="mitProperties"/>
         </Menu>
         <Column name="colType" caption="idsa.training.layout.EntityList.type" icons="type iconDefault"/>
         <Column name="colIcon" caption="idsa.training.layout.EntityList.icon" icons="(image . image)(data . mimeData)(type . mimeType)" texts="initial"/>
         <Column name="colName" caption="idsa.training.layout.EntityList.name" length="100" values="fullName"/>
      </Table>
   </Composite>
</Layout>
training:mda:EntityProperties.dialog
<Dialog caption="ids.caption.editPopup">
   <Composite cols="2" name="composite">
      <LayoutRef layout="mda:EntityPropertyDashboard" head="true" name="mda_EntityPropertyDashboard_layout"/>
      <Switch caption="ids.detail" head="true" name="grpDetailLayout" span="2" value="mda_EntityPropertyDashboard_layout">
         <Composite case="UserPerson" name="compUser">
            <LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:UserPersonEditDetail" head="true" name="mda_UserPersonEditDetail_layout"/>
         </Composite>
         <Composite case="Person" name="compPerson">
            <LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:PersonEditDetail" head="true" name="mda_PersonEditDetail_layout"/>
         </Composite>
         <Composite case="Company" name="compOrg">
            <LayoutRef association="mda_EntityPropertyDashboard_layout" layout="mda:CompanyEditDetail" head="true" name="mda_CompanyEditDetail_layout"/>
         </Composite>
      </Switch>
      <LayoutRef association="mda_EntityPropertyDashboard_layout" head="true" layout="mda:EntityAuditFields" name="mda_EntityAuditFields_layout" span="2"/>
   </Composite>
   <Actions layout="mda:PopupButtons" name="mda_PopupButtons_layout"/>
</Dialog>
idsa.training.en.strings
idsa.training.portal.Contact.caption=My Training Application
idsa.training.workspace.Sandbox.caption=Sandbox
idsa.training.layout.EntityList.caption=Entity List
idsa.training.layout.EntityList.type=Type
idsa.training.layout.EntityList.icon=Icon
idsa.training.layout.EntityList.name=Name

Using system events

We saw a system UI event in use in the previous lesson about portlets and dialogs, when we overrode the system's default handling of the sysProperties event by adding a UIAction handler that opened the training:mda:EntityProperties dialog.

UIAction handler we've already added for sysProperties in training:EntityList.layout
<UIAction event="sysProperties">
   <Popup association="tblEntityList" edit="true" screen="training:mda:EntityProperties"/>
</UIAction>

Learning activity

In this lesson, we'll raise another system event by adding the sysRemove event to table rows and letting the system handle it.

  1. In the training:EntityList.layout file, add the following item to the mnuRightClick menu.

    <Item event="sysRemove" name="deleteItem"/>
  2. Save and hotswap the UI.
    When you run the application, you should see the Delete button trash can icon at the end of the row.
  3. Click the Delete button.
    You should see a dialog similar to the following:

    Dialog seen when deleting an entity


Without having to declare very much, we have a delete pattern. The Delete button and Delete hover text is retrieved from the sysRemove UI event's declaration. The Confirm Delete dialog is the built-in handler for sysRemove and it gets the name and description of the type from the class caption metadata. This dialog is responsive, works with accessibility, is compatible with international languages and formats, is testable, and works with performance counters with very little configuration.


The platform enforces data security and privileges automatically. If you hover over a row for a user, you will see that the Delete button becomes disabled. This is because the logged-in user doesn't have permission to delete User records. We do have the ability to delete Person and Company records.

You may also find that the application does not let you delete a company or contact if there are associated opportunities, service requests, or securities. This is the system enforcing the referential integrity declared in the business model.

Overriding system and other events

Let's override the default sysRemove behavior for our layout. To do this, we need to add a UIAction handler to our layout. Instead of a pop-up, we will use a script UIAction.

Learning activity

  1. Open training:EntityList.layout and go to the UI Actions tab. You should see the pop-up action we added in the previous lesson.
  2. Press the Add button Add buttonand select Script. Set the event to listen for the sysRemove event.
  3. Enter the following script:

    (@ confirm : "Delete is not allowed" "Delete Override" '("Ok"))

    Autocomplete in NexJ Studio UI script editing has not been fully updated for 9.x APIs. As a result, you may occasionally see the "Could not resolve symbol". You can ignore this. NexJ Studio should soon be updated to eliminate this.

    The script displays a confirmation dialog. The confirm method works as follows:

    confirm <message> <caption> <actions>
    
    Displays an info/confirmation dialog. Returns the ordinal number of the selected action. The default action is indicated by 0.
    
    @arg {string} message Dialog message - this can be a string id for internationalization.
    @arg {string} [caption] Dialog caption - this can be a string id for internationalization.
    @arg {string[]|scm.Symbol[]|scm.Pair} [actions] Action captions, localized string ids or event names. The first action is the default action.
    @retcc {integer|undefined} 0-based action ordinal number.
    @example (@ confirm : "message" "caption" '("Ok" "Close"))
    
    Note: replaces (this'createMessageBox)


    The(@ in this script is the controller of the layout. The API of the controller can be reviewed in the UI Scripting document.

  4. Save and hotswap. When you press the Delete button, you should see a dialog similar to the following:

    Dialog for Delete override


If a UIAction returns #f (false), the event propagates onwards and can be handled by another UIAction or the default system event handler. If a UIAction returns #t, it stops as the action is considered complete. Our script returns whatever the call to (@ confirm returns, which is 0 if you press the OK button. Returning anything other than #f tells the system that the event has already been handled. If we had returned #f, control would have been passed up the chain of handlers.

Enhancing system and other events

Let's modify our UIAction so we can conditionally pass the event on to the platform for handling.

Learning activity

  1. Modify the UIAction script to:

    (if (= (@ confirm : "You can stop the proccessing here by pressing STOP." "Delete Override" '("Continue" "Stop")) 0)
       #f
       (begin
          (@ confirm : "You decided not to delete the item." "Delete Override" '("Ok"))
          (@ flash : "Aborted Delete")
       )
    )

    This script checks the return value of the first confirm dialog. If the user clicks Continue, an #f (false) is returned, the platform treats the event as unhandled, and the default processing takes place. If the user clicks Stop, another confirm dialog and a flash will display. 

  2. Save and hotswap the UI. 

Now we can abort the delete, or pass control to the system delete handler. Security and referential integrity rules are automatically enforced, so there may be some entries in the list that you are not able to delete.

Adding your own UIEvents

In this exercise, you'll add a button to your table that updates the table's caption. The following high-level steps are required to implement this feature:

  • Create a custom UIEvent called trainingUpdateCaption.
  • Add a button to raise the UIEvent.
  • Write the UIAction handler.

Learning activity

  1. In NexJ Studio, go to File > New > UI Events, and enter the name training:UIEvents. Go to the source tab and paste the following:

    training:UIEvents.uievents
    <UIEvents>
       <UIEvent caption="UpdateCaption Caption" icon="icon:fast_rewind" name="trainingUpdateCaption" tooltip="UpdateCaption Tooltip"/>
    </UIEvents>
  2. To fire the event, let's place more menu items on the rows and a new toolbar item on our table. The toolbar and menu elements should be as follows:

    training:EntityList.layout
    ...         
    		 <Toolbar name="toolbar">
                <Item event="trainingUpdateCaption" name="updateCaption2" value="&quot;from toolbar&quot;"/>
             </Toolbar>
             <Menu name="mnuRightClickMenu">
                <Item caption="ids.edit2" event="sysProperties" icon="edit" name="editItem"/>
                <Item event="sysRemove" name="deleteItem"/>
                <Item event="trainingUpdateCaption" name="updateCaption" value="&quot;from row&quot;"/>
             </Menu>
    ...
  3. Save your work and reload the UI.  This will add buttons such as:
    Entity list with new buttons
    Notice the value entries for the menu items.  The value property is the item value constant passed as a UIEvent parameter.  We set the toolbar button's value to from toolbar and the row menu item's value to from row.  When a UIEvent is raised, it carries with it the event type, the source (in this case the table), and any parameters sent along. This allows for conditions to be set up to handle specific events appropriately. 

  4. To handle these events and show their source and parameters in a message box, add a new UIAction script to EntityList.layout that listens for the trainingUpdateCaption event. In the UIActions tab, click the Add button and select Script. Add trainingUpdateCaption as the event and enter the following script:

    (@ confirm : 
       (string-append "Parameter: " parameter) 
       (string-append "Source: " (source'name)) 
       '("ok")
    )
  5. Save and re-load the UI. Now, when you click on either button, you should get:


    or


  6. Update the table's caption according to where the click came from.  Replace the UIAction script with:

    ((@ findView : 'tblEntityList)'caption parameter)
  7. Reload your UI.
    This updates the table's caption to "from row" or "from table", depending on which button you click.
  8. To see the findView controller method in action, update the script to the following:

    ((@ findView : 'tblEntityList)'caption (format "{0} - {1}" parameter ((((@ findView : 'tblEntityList)'items)'item)'fullName)))

    The findView controller method is useful for manipulating, enabling, or disabling UI controls. Similarly, the findModel controller method can be used to inspect the underlying model or instances with code such as:
    ((((@ findView : 'tblEntityList)'items)'item)'fullName)


    The result of this would be:

For additional information about UX scripting, see the UX Scripting API Reference.

Data actions

data action is a handler similar to a UI action, but instead of listening for UI events fired from views, it listens for changes in models.

While aren't covering data actions in depth here, here is a quick example. 

Learning activity

  1. Paste the following in the Source tab of EntityList.layout:

    EntityList.layout
    <Layout class="Entity">
       <DataActions>
          <DataAction condition="(= locator 'selection)">
             <Script><![CDATA[(logger'info "Selection changed to " (@ model item lastName))
    ((@ findView : 'tblEntityList)'caption (string-append "Selection changed to " (@ model item lastName)))]]></Script>
          </DataAction>
       </DataActions>
    ...
    <rest of existing file>
  2. Switch to the DataActions tab. You should see something like the following:


    This data action will listen for a selection change in the EntityList model. When a change is detected, the caption of the table will update accordingly:
  3. Reload and refresh your browser. When we click in the application, the console (F-12 console tab) will output something similar to the following:


    These are the messages logged to the browser's console when the clicks occurred.

Further examples of using data actions can be seen in the layouts within the finance model.