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) "EntityId")">
...
</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=""PSN"" 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