NexJ Logo

UX Scripting


MDA Controller API and Library Functions

These are some of the new scripting APIs accessible from the root Controller. They are all invoked using the following syntax:

(@ <name> : <arg1> <arg2> ... <argN>)

Many of the controller methods available in the classic client framework have been deprecated and replaced by new methods native to the AFL MDA controller. While some of the older methods are still supported, they mostly delegate to the new methods. The following is a list of methods commonly used in application developement, their signatures and what they replace in the classic metadata.

The list has mainly been compiled from "afl/mod/nexj/ui.mda.mvc.js"

Controller API

add

Creates and adds items to the model of a given view.
NOTE: Useful for adding instances to a collection model rather than updating a where clause and doing query.
@arg {scm.Pair|scm.Symbol|string} path Model view path.
@arg {scm.Pair|scm.Symbol|string[]|undefined} factory [class, event]|event.
@arg {scm.Pair|object[]|object} values Draft object values.
@retcc {object[]|object} Created item(s).
@throws ReferenceError if the view is not found or the model is read-only.

complete

Schedules a continuation for the rest of the script body after (@ complete)
@example
    (@ complete)
    (logger'info "postMessageBody" (@ model item))
REPLACES:
    (this'postMessage)
<!-- **postUIEvent** * DEPRECATED * REPLACES: ** (this'postMessage "" (lambda () (this'fireUIEvent))) * COMMENT: Basically replaces fireUIEvent wrapped in a postMessage * Constructs and posts a UI event. * @see mda.Controller#postMessage * @see mda.UIEvent */ -->

confirm

Displays an info/confirmation dialog.
@arg {string} message Dialog message.
@arg {string} [caption] Dialog caption.
@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" '("sysOk" "sysClose"))
REPLACES:
    (this'createMessageBox)
NOTE: This returns the ordinal number of the selected action. The default action is indicated by 0.
REFACTOR: We need to update PopupHelper.scm in finance-mda, to use this API in (open-information-box) & (open-message-box).

createDialog

Creates and initializes a dialog controller.
@arg {string} name MDA view name.
@arg {scm.Pair} [...] Controller property name-value pairs.
@ret {mda.DialogController} Initialized controller.
@example (@ createDialog : "DialogName" args)
REPLACES:
    (this'createPopup ...)

createPickerDialog

Creates and initializes a picker dialog controller.
@arg {string} name MDA picker name.
@arg {string} [mode] Picker mode.
@arg {object} [items] Underlying model providing an item for editing.
@arg {scm.Pair} [...] Controller property name-value pairs.
@see createDialog
REPLACES:
    createPopupPickerM1
    createPopupPickerMM

forEachNode

Iterates over ActiveNodes and invokes the given closure. This method triggers continuation behavior.
@arg {string|scm.Symbol|mda.Class|null} base Base class for restricting the iteration.
If no base class is given visit every active node.
@arg {ui.View|null} view Optional root view for restricting the iteration.
@arg {scm.PCodeFunction} fun Closure to invoke: (lambda (node) ...).
@example
	(this'forEachNode 'SysExternalReport  '()
		(lambda (node)
			(logger'info (node'activeModel))
		)
	)
NOTE: Any code after forEachNode will resume continuation after invoking the closure fun

findModel

Finds the model of a given view or controller.
@arg {ui.View|mda.Controller} view View or controller which model to find.
@ret {object|null} Found model.
@example
    (@ findModel : 'frmDetail)
    ((@ findModel : 'frmDetail)'item)
REPLACES:
    (((this'findView viewPath)'collectionModel)'currentInstance)
    (client-get-model-instance this '(view))
NOTE: This returns the collection model. The instance can be accessed using the 'item or 'selection API on the model.

findView

Finds a view corresponding to a given path.
@arg {scm.Pair|scm.Symbol|string} path View path; it can match an indirect child.
@arg {integer} [index] List (repeater) index.
@ret {ui.View} The found view, or null if not found.
@example (@ findView : 'frmDetail)
NOTE: This accepts a symbol or string as argument. Useful when accessing a direct child view.

fireUIEvent

@arg {string} name Unique event name.
@arg [optional] parameter Event-specific parameter.
@arg [optional] {boolean} [broadcast=false] True for broadcasting the event.
@example (@ fireUIEvent : "eventName" parameter #f)
REPLACES:
    ((this'fireUIEvent "sysOK" parameter)

fireUIEvent (View API)

@arg {string|mda.UIEvent} event Unique event name or UIEvent object.
@arg [optional] parameter Event-specific parameter.
@arg [optional] {ui.View} Overridden source view.
@example
    (@ view fireUIEvent : "eventName" () (@ findView : "grdView"))
    (@ view fireUIEvent : (UIEvent'new "eventName" '() (@ findView : "grdView"))
REPLACES:
    ((this'view)'fireUIEvent (UIEvent'new "eventName" '() (this'view))

getEventModel

Gets the model of a given event, lazy-creating one, if necessary.
@arg {string} name Event name.
@ret {object} Event model.
@example (@ getEventModel : "sysPick")
REPLACE: findMenuItem
COMMENT: This is simplified, require just the event name as the argument.
It is recommended to update event properties such as enabled/visible calculate binds instead.

invoke

Invokes a domain event.
@arg {object|string|scm.Symbol} draft Object instance or a class name.
@arg {string|scm.Symbol} event Event name.
@arg {object[]|scm.Pair} [args] Event arguments.
@arg {string[]|object[]|scm.Pair} [attrs] Tree of association paths to retrieve:
[a11, a12, [a13, a21, ..., a2N], ..., a1N].
@arg {mda.ActiveNode[]|rpc.Requestor[]|scm.Pair} [reqs] Requestors to reload.
@arg {boolean} [commit=true] True to commit the work.
@retcc Event return value.
@example (@ invoke : (@ model item) 'event '(arg1 arg2))
REPLACES:
    (client-invoke)
    (this'invokeMethod)
    (this':invoke)

load

Loads missing attributes of an object, collection or active node.
@arg {object|object[]|ui.Collection|mda.ActiveNode} draft Object to load.
@arg {scm.Pair} attrs Read attribute tree.
@example (@ load : (@ model item) '(attr1 attr2))
REPLACES:
    (this'readMissingAttributes)
NOTE: The attribute order is different from readMissingAttributes.

model

@ret {object} Model of this.view()
@example (@ model item) -> (client-get-model-instance this ())
REPLACES:
    (client-get-model-instance this ())
    (client-get-current-instance)

openDialog

Opens a dialog.
@arg {string} name MDA view name.
@arg {scm.Pair} [...] Controller property name-value pairs.
@retcc mda.DialogController#value.
@example (@ openDialog : "DialogName" args)
REPLACES:
    ((this'createPopup ...)'open)
NOTE: This also returns the result value. In general, one can pass all the
arguments and obtain the result in one invocation expression, without
invoking individual dialog controller methods.
<!-- **query** DEPRECATED REPLACES: (this'refresh (ActiveNode)) (add-to-batch-requests controller node 'query) Loads an active node. @arg {mda.ActiveNode} node The node to load. @example (@ query : (@ model )) -->

openPickerDialog

Opens a picker dialog.
@arg {string} name MDA picker name, or true for a generic multipicker,
false for a generic single-value picker.
@arg {string} [mode] Picker mode (MDA pickers only).
@arg {object} [items] Underlying model providing an item for editing.
@arg {scm.Pair} [...] MDA Controller or picker dialog property name-value pairs.
@retcc mda.DialogController#value.
@see openDialog
REPLACES:
    openPickerMM
    ((createPopupPickerM1 ...)'open)
    ((createPopupPickerMM ...)'open)

postMessage

Schedules a p-code closure for delayed execution,
after controller initialization and model loading.
@arg key Unique message type key for preventing duplicates; undefined means unique.
@arg {function(handler)|scm.PCodeFunction} fun Closure to call.
@example (@ postMessage : "key" (lambda () ... ))
REPLACES:
    (this'postMessage)
NOTE: Depricated usage - Use (@ complete) instead

view

Controller's view.
@ret {ui.View} Current controller's view.

viewPath

Computes a view path array: parent/view name chain up to this controller's view.
@arg {ui.View} view View which path to compute.
@ret {string[]} View path.

Collection API

Collection model API is available from:

(@ view collectionModel <name> : <arg1> ... <argN>)

The activeNode API has been deprecated and is now available from the collection model.

clearMultipleSelection

Clears selection collection
COMMENT: Also triggered from reload.

index / index [int]

Collection index getter and setter.

load

Reloads the collection.
@example (@ view collectionModel load)
REPLACES:
    (add-to-batch-requests controller node 'query)

reload

Resets the current paging state and reloads the collection.
@example
    (@ model reload)
    (@ view collectionModel reload)
REPLACES:
    (add-to-batch-requests controller node 'refresh)

selection

Current item
@example: (@ view collectionModel selection)
REPLACES:
    (client-get-selection '(viewName))
    ((((this'findView viewPath )'collectionModel)'activeNode)'selection)

Features and development patterns

These are some of the new AFL features that would help reduce boiler plate scripting.

Calculated binds

This allows us to dynamically toggle boolean interactive properties such as "visible" and "enabled" for form views with respect to the object attribute value. In the MDA forms we should be able to use this pattern to simplify most of our scripted setters currently done using data actions, UI actions and postMessages.

Example from ScheduleItemGrid.form:

<Item 
    caption="idsf.EntityActList.addCallRecord" 
    enabled="`(and ,updatable ,(@ act updatable) (not ,(@ act hasCallRecord)))" 
    event="addRelatedCallRecord" 
    name="addCallRecord"/>

Please see Norair's forum post: https://forum.nexj.com/viewtopic.php?f=70&t=5048&p=20181#p20173

Calculated collection where clauses

Collection views in AFL controllers support dynamic where clause expressions. Where clause specification is extended to include quasiquoted expression with calls to global functions. If quasiquoted, it is expanded on the client every time the model is (re-)loaded.

We can use this pattern to support where clause based on context parameters or implement processWheres based on data binding values.

Example from mda:PortfolioHolding.filter

<Picker ... where="`(generate-where-clause this '(@@ Entity portfolio accounts) &quot;EntityId&quot;)">
    ...
</Picker>

Please see Norair's forum post: https://forum.nexj.com/viewtopic.php?f=70&t=5039&p=20143#p20143

Parameterized UIEvents

Form elements such as buttons, and menu items supporting UIEvent association now support parameter passing. A constant can be specified as parameter for the UIEvent as property "value" for the menu item. The parameter is available as a variable "parameter" in the UIAction handler script.

We can use this pattern in core portlets and dialogs where many declarations for the same UI action are created to handle different sources. Instead of creating multiple declarations, we can have one to handle each case differently using the parameter they are passing.

Example (not checked in):

<Menu>
...
<Item action="true" caption="ids.newContact" event="addContact"  ... value="&quot;PSN&quot;" name="mitAddContact"/>
</Menu>

<UIAction event="addContact">
    <Script><![CDATA[
        (case parameter
            (("PSN") ... )
            (("COM") ... )
            (else ... )
        )
]]></Script>
</UIAction>

See: https://jira.nexj.com/jira/browse/AFL-1338

Calculated Portlet Visibility Setters

This supports the use case of setting visibility of portlets based on model attribute values such as Contact's status.

Example:

<Portlet context="EntityId" visible="`(not ,(ref isCovered))">
    ...
    <FormRef caption="idsa.EntityBio.bio" form="mda:ContactBio" head="true" name="ref"/>
    ...
</Portlet>

Please see Norair's forum post: https://forum.nexj.com/viewtopic.php?f=70&t=5048&p=20181#p20181

Library Functions

context-get

Gets the current value of a given context variable.
The signature must not change as the framework invokes the function.
@arg view The view corresponding to the portlet requesting the value (ie. the portlet view).
@arg var Name of the context variable.
@ret any The context variable value.

context-set

Sets context variable value.
The signature must not change as the framework invokes the function.
@arg view view The view corresponding to the portlet that is changing the context (ie. the screen view).
@arg newValues list The list of pairs containing <variable, value> for each context variable to be set.
@example
(context-set (this'view) (cons "Contact" (inst'contactId)) (cons "Account" (inst'accountId)))

in-privilege?

Determines if the user has a given primitive privilege.
@arg {string|scm.Symbol} name Primitive privilege name.
@ret {boolean} True if the user has the specified privilege.
@example (in-privilege? "BatchProcessManage")
REPLACES: Reading the privilege from ((user)'privilege) which is loaded in Contact Broker portlet.
NOTE: The privileges are pre-loaded in the client meta data. If the privilege name is misspelled, the function will return #f in the client code.
REFACTOR: We need to change all invocations of ((user)'privilege) to use
this controller property and remove the long list of 
(this'readMissingAttribute) from mda:ContactBroker