Behavior: UI events, UI actions, and data actions
Learning objectives
In this lesson, we will learn:
- How
UIEvents
andUIActions
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.
In the
training:EntityList.layout
file, add the following item to themnuRightClick
menu.XML<Item event="sysRemove" name="deleteItem"/>
- Save and hotswap the UI.
When you run the application, you should see the Delete button at the end of the row. - Click the Delete button.
You should see a dialog similar to the following:
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
- Open
training:EntityList.layout
and go to the UI Actions tab. You should see the pop-up action we added in the previous lesson. - Press the Add button and select Script. Set the event to listen for the
sysRemove
event. Enter the following script:
SCHEME(@ 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:
SCHEMEconfirm <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.- Save and hotswap. When you press the Delete button, you should see a dialog similar to the following:
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
Modify the
UIAction
script to:XML(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.- 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
calledtrainingUpdateCaption
. - Add a button to raise the
UIEvent
. - Write the
UIAction
handler.
Learning activity
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
XML<UIEvents> <UIEvent caption="UpdateCaption Caption" icon="icon:fast_rewind" name="trainingUpdateCaption" tooltip="UpdateCaption Tooltip"/> </UIEvents>
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
XML... <Toolbar name="toolbar"> <Item event="trainingUpdateCaption" name="updateCaption2" value=""from toolbar""/> </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=""from row""/> </Menu> ...
Save your work and reload the UI. This will add buttons such as:
Notice the value entries for the menu items. The value property is the item value constant passed as aUIEvent
parameter. We set the toolbar button's value tofrom toolbar
and the row menu item's value tofrom row
. When aUIEvent
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.To handle these events and show their source and parameters in a message box, add a new
UIAction
script toEntityList.layout
that listens for thetrainingUpdateCaption
event. In the UIActions tab, click the Add button and select Script. AddtrainingUpdateCaption
as the event and enter the following script:CODE(@ confirm : (string-append "Parameter: " parameter) (string-append "Source: " (source'name)) '("ok") )
- Save and re-load the UI. Now, when you click on either button, you should get:
or Update the table's caption according to where the click came from. Replace the
UIAction
script with:CODE((@ findView : 'tblEntityList)'caption parameter)
- Reload your UI.
This updates the table's caption to "from row" or "from table", depending on which button you click. To see the
findView
controller method in action, update the script to the following:SCHEME((@ 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, thefindModel
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
A 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
Paste the following in the Source tab of
EntityList.layout
:EntityList.layout
CODE<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>
- 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: - 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.