DEMO2
In this post we are going to delve into the JavaScript code that Pega uses to execute action sets in a control.
Before we start
For this post, let's create a very simple section with the following configuration:
-
Create a
.YesNoproperty with "Yes" and "No" as accepted values that are defined in a local list.
-
Add a dropdown with the
.YesNoproperty as its source.
-
Add an Action set to the dropdown on "Change" event: A Post value action, and a Run Script action that will show an alert dialog box when the
.YesNoproperty has the "Yes" value.
As a result, a message will appear when the "Yes" option is selected. Also, the selected value will be posted to the server.
Now, let's start our study 😀.
Descriptors
Before we go through the action processing, let's define two concepts: The Event descriptor and the Control descriptor.
The Event descriptor
The following is the generated HTML source code of the .YesNo dropdown field.
<select data-template="" data-change='[[\"postValue\",[\":event\"]],[\"runScript\", [\"alert(\\\"Yes option selected\\\")\"],[\"&\",[\"AP\",[\"$PpyWorkPage$pYesNo\",\"=\",\"Yes\",\"false\"]]]]]' data-test-id="202208112144400330804" data-ctl='[\"Dropdown\"]' aria-invalid="false" id="83838a67" class="standard" style="width:auto" name="$PpyWorkPage$pYesNo" aria-describedby="$PpyWorkPage$pYesNoError " value="" pn=".YesNo">
<option value="" title="Select" selected="selected">Select</option>
<option value="Yes" title="">Yes</option>
<option value="No" title="">No</option>
</select>
Let's focus on the data-change attribute. This is called the Event descriptor of the control. in our case, this descriptor corresponds to the Change event.
data-change='[[\"postValue\",[\":event\"]],[\"runScript\", [\"alert(\\\"Yes option selected\\\")\"],[\"&\",[\"AP\",[\"$PpyWorkPage$pYesNo\",\"=\",\"Yes\",\"false\"]]]]]'
The complete list of Event descriptors is defined in the descriptors object.
namespace pega.c.ControlBehaviorExecutor {
...
var descriptors: Object = {};
descriptors["click"] = "data-click";
descriptors["dblclick"] = "data-dblclick";
descriptors["focusin"] = "data-focus";
descriptors["focus"] = "data-focus";
descriptors["focusout"] = "data-blur";
descriptors["blur"] = "data-blur";
descriptors["keypress"] = "data-keypress";
descriptors["keyup"] = "data-keyup";
descriptors["mouseover"] = "data-hover";
descriptors["keydown"] = "data-keydown";
descriptors["mousedown"] = "data-msdwn";
descriptors["paste"] = "data-paste";
descriptors["cut"] = "data-cut";
descriptors["change"] = "data-change";
descriptors["contextmenu"] = "data-rightclick"; /*contextmenu event mapped to data-rtclick attribute - delta touch*/
descriptors["playing"] = "data-playing"; // video element events
descriptors["pause"] = "data-pause";
descriptors["ended"] = "data-ended";
...
}
The value of the Event descriptor is a JSON-string encoded array of actions configured in the control.
We can decode the Event descriptor using the JSON.parse function.
The Control descriptor
Now, let's focus on the data-ctl attribute. This is called the Control descriptor, in our example, this descriptor corresponds to the Dropdown control.
data-ctl='[\"Dropdown\"]'
This descriptor corresponds to the pega.c.[Control] object. For example, pega.c.Dropdown for dropdowns and pega.c.TextInput for text inputs. It contains the general behaviors (also called Implicit behaviors) for each control. By default, the pega.c.UIElement generic control descriptor will be used during the action processing.
Now that we know the Event Descriptor and Control Descriptor, let's see how the actions are performed 😎.
Events, listeners and handlers
The event listeners are being attached directly to the document object as shown in the following code.
(function() {
var $d = document,
$b,
$ec = pega.c.eventController,
$h = $ec.handler,
$e = pega.util.Event;
/* Moved click , contextmenu and dblclick event addListener from here to pzpega_ui_events_infra.js to make it conditional - Delta Touch */
// focusin needs to be attached to the body. Using onDomReady since firefox executes this before parsing HTML
$(window).on("load", function() {
$b = document.body
$b.addEventListener("focus", $ec.focusHandler, true);
$b.addEventListener("blur", $ec.blurHandler, true);
$b.addEventListener("change", $ec.changeHandler);
$b.addEventListener("paste", $h);
$b.addEventListener("cut", $h);
$b.addEventListener("playing", $h, true); // Video element event
$b.addEventListener("pause", $h, true); // Video element event
$b.addEventListener("ended", $h, true); // Video element event
});
$d.addEventListener("keypress", $h);
$d.addEventListener("keyup", $h);
$d.addEventListener("keydown", $ec.keyDownHandler);
$d.addEventListener("mousedown", $h);
$d.addEventListener("click", $ec.clickHandler);
$d.addEventListener("dblclick", $ec.handler);
$d.addEventListener("contextmenu", $ec.rightClickHandler);
$d.addEventListener("input", $ec.inputHandler);
} ());
The event handling logic is implemented in the pega.c.eventController namespace.
changeHandler
For our example, the pega.c.eventController.changeHandler function will handle the "change" event. In general, an event handler (changeHandler, clickHandler, etc.) is composed of a pre-processing code followed by a call to the pega.c.eventController.handler function.
export var changeHandler = function(e: PegaEvent): void {
/* PRE-PROCESSING */
var target: PegaHTMLElement = e.target;
if (target.getAttribute("data-formatting") == "yes" && target.getAttribute("data-change")) {
if(!(target.getAttribute("data-custom-timezone") && target.getAttribute("data-value") && target.type=="hidden")){
target.setAttribute("data-value", (target).value);
}
target.setAttribute('data-changed', 'true');
}
/* HANDLER */
pega.c.eventController.handler(e);
};
handler
The following is the source code of the pega.c.eventController.handler function. It will emit a custom ON_BEFORE_EVENT_PROCESSING event globally and then it will continue calling the notifyEventToAll function.
export var handler = function(e: any): void {
if (!pega.u.EventUtils.isElementDisabled(e.target, e)) {
pega.ui && pega.ui.EventsEmitter && pega.ui.EventsEmitter.publishSync(ON_BEFORE_EVENT_PROCESSING, e);
notifyEventToAll(e);
}
};
As shown in the source code of the pega.c.eventController.handler function, a custom ON_BEFORE_EVENT_PROCESSING event will be emitted. Correspondingly, a function can be registered to listen this event using the pega.ui.EventsEmitter.subscribe function.
For example, the following script will catch every UI event before it happens.
pega.ui.EventsEmitter.subscribe("onBeforeEventProcessing", function(e) {
console.log( "A " + e.type + " event on <" + e.target.tagName + "> is going to be processed." );
}, true, null, null, true);
notifyEventToAll
The following is the source code of the notifyEventToAll function. It will continue the processing of the actions by calling the fireImplicitControlBehavior function. After that, it will call every function registered in the eventSubscribers object.
function notifyEventToAll(e: PegaEvent): void {
var ev = pega.u.EventUtils.cloneEvent(e);
fireImplicitEventSubscribers(ev);
for (var name in eventSubscribers) {
if (eventSubscribers.hasOwnProperty(name) && typeof eventSubscribers[name] == 'function') {
eventSubscribers[name](ev);
}
}
}
As shown in the source code of the notifyEventToAll function, it will call every function registered in the eventSubscribers object. A function can be registered to this object using the pega.c.eventController.registerEventNotification function.
For example, the following script will catch every UI event after it happens (the name parameter can be any name).
pega.c.eventController.registerEventNotification( "name", function(e) {
console.log( "A " + e.type + " event on <" + e.target.tagName + "> has been processed." );
} );
fireImplicitEventSubscribers
The following is the source code of the fireImplicitEventSubscribers function.
var fireImplicitEventSubscribers = function(e: PegaEvent): void {
/* 1 */ switchOSCOThread(e);
/* 2 */ validationAttacher(e);
/* 3 */ pega.c.ClientConditionExecutor.processEvent(e);
/* 4 */ pega.c.ControlBehaviorExecutor.processBehavior(e, null);
};
This function does the following:
-
Call the
switchOSCOThreadfunction which is related to the offline mode. More knowledge needed 😔. -
Call the
validationAttacherfunction which attaches the client-side validations defined in the control (through thevalidationtypeattribute, but that's for another post 😉). Note that this step will only attach the client-side validations only without actually triggering them. -
Call the
pega.c.ClientConditionExecutor.processEventfunction which executes the Client conditions, like the Visibility on client, in the UI. -
Continue the action processing by invoking the
pega.c.ControlBehaviorExecutor.processBehaviorfunction.
Control behaviors
Now that the event has been handled with the functions defined in the pega.c.eventController namespace, the behaviors defined in the control will be processed using the functions defined in the pega.c.ControlBehaviorExecutor namespace.
processBehavior
The following is an extract of the source code of the processBehavior function. It will get the Event Descriptor using the getDescriptionForEvent function. With that, it will get the string-encoded array of actions. All these values will be passed to the processBehaviorsOnElem function to continue the action processing.
/* eventDescriptor ← null */
export var processBehavior = function(ev: PegaEvent, eventDescriptor: string): any {
var e: PegaEvent = ev;
...
var target: PegaHTMLElement = e.target;
...
if (!eventDescriptor) {
eventDescriptor = getDescriptionForEvent(e); // "data-change"
}
...
var jsonDesc: string = target.getAttribute(eventDescriptor); // '[[\"postValue\"...]]'
...
var eventType: string = e.type; // "change"
...
processBehaviorsOnElem(target, e, eventType, jsonDesc, true, null);
...
}
processBehaviorsOnElem
The following is an extract of the source code of the processBehaviorsOnElem function. It will call the fireImplicitControlBehavior function which executes the event functions defined in the corresponding control descriptor. Finally, it will invoke the performExecution function to continue the action processing.
/* isControlElem ← true, repeatLayoutInfo ← null */
function processBehaviorsOnElem(target: PegaHTMLElement, e: PegaEvent, eventType: string, jsonDesc: string, isControlElem: boolean, repeatLayoutInfo): void {
...
cancelBehavior = fireImplicitControlBehavior(target, e, eventType, isControlElem);
if (cancelBehavior != true && typeof jsonDesc == 'string') {
ctl = pega.c.UIElement;
...
performExecution(e, ctl, jsonDesc, repeatLayoutInfo);
...
}
}
As shown in the previous code, the event functions defined in the control descriptor will be executed automatically.
As an example, the following function will be executed every time a Dropdown is changed.
pega.c.Dropdown.change = function(e) {
console.log("A dropdown has changed its value to '" + e.target.value + "'");
}
performExecution
The jsonDesc variable will be parsed using the pega.c.eventParser.parseExecutables function and its result will be assigned to the executorArray variable which will be sent to the Action Sequencer through the pega.c.actionSequencer.schedule function.
/* controlDescriptor ← pega.c.UIElement, repeatLayoutInfo ← null */
var performExecution = function(e: PegaEvent, controlDescriptor, jsonDesc: string, repeatLayoutInfo): void {
var executorArray: Array<Array<any>> = pega.c.eventParser.parseExecutables(e, jsonDesc); // [ ["postValue", [":event"]], ["runScript", [...]] ]
...
// Send the actions to the Action Sequencer.
pega.c.actionSequencer.schedule(e, controlDescriptor, executorArray);
}
For our example, the executorArray is as follows:
executorArray =
[
[
"postValue",
[":event"]
],
[
"runScript",
["alert(\"Yes option selected\")"],
["&",["AP",["$PpyWorkPage$pYesNo","=","Yes","false"]]]
]
]
The Action sequencer
The Action Sequencer (pega.c.actionSequencer) is the namespace that contains all the functions in charge of processing the actions in a control. This namespace maintains an internal Queue of actions which are going to be executed.
schedule
The following is an extract of the source code of the schedule function. It will enqueue all the actions in the executorArray into the Queue using the appendToQueue function. Finally, if the action sequencer is not paused, it will invoke the enqueueExecuteOne function to continue the action processing.
/* controlDescriptor ← pega.c.UIElement, actionsArray ← executorArray */
export var schedule = function(e: PegaEvent, controlDescriptor, actionsArray: Array < Array < any >> ): boolean {
...
/* _pud ← pega.u.d */
appendToQueue(actionsArray, _pud.bModalDialogOpen, e, controlDescriptor);
if (!_paused) {
enqueueExecuteOne();
}
...
}
We can pause the Action sequencer using the pause function so that the actions won't be executed until the resume function is invoked.
/* Pause the Action sequencer */
pega.c.actionSequencer.pause()
/* Resume the Action sequencer */
pega.c.actionSequencer.resume()
As an example, after invoking the pause function, the dialog box won't be shown after selecting the "Yes" option, but it will be shown after invoking the resume function.
enqueueExecuteOne
The following is an extract of the source code of the enqueueExecuteOne function. If the Action sequencer is not paused, it will invoke the executeOne function to continue the action processing.
var enqueueExecuteOne = function(): void {
if (!_paused) {
...
executeOne();
}
}
executeOne
The following is an extract of the source code of the executeOne function. It will shift the first action in the Queue and invoke the executeAction function to continue the action processing. Finally, it will check if the Queue is not empty to invoke the enqueueExecuteOne function to process the next action.
var executeOne = function(): void {
var _currentQueue: Array < PegaQueueObject > = getCurrentQueue("execute");
...
var actionItem = _currentQueue.shift();
...
executeAction(actionItem.e, actionItem.cd, actionItem.action, actionItem.cP, actionItem.rP);
...
_currentQueue = getCurrentQueue("execute");
if (_currentQueue.length > 0 && !_paused) {
enqueueExecuteOne();
}
...
};
Calling the pega.c.actionSequencer.clearQueue function will clear the Queue and, as a result, the remaining actions won't be executed.
pega.c.actionSequencer.clearQueue();
This function can be used to stop the action processing completely if client-side validation fails.
executeAction
The following is an extract of the source code of the executeAction function. It will evaluate the condition in the action and stop the execution if it's false. In our example, the condition for the Run script action, the condition is .YesNo == "Yes". After that, it will call the action function. In our example, this action function is pega.c.UIElement.Actions.postValue for the first action, and pega.c.UIElement.Actions.runScript for the second action.
/* controlDescriptor ← pega.c.UIElement, executable ← ["postValue", [e]], cP ← "", rP ← "" */
var executeAction = function(e: PegaEvent, controlDescriptor: any, executable: Array, cP, rP): void {
var methodName : string,
action : Function,
scopeObj : Object = null,
...
/* Stop if the condition in the action evaluates to false. */
if (executable[2] && executable[2].length > 1 &&
!pega.c.conditionEngine.validateCondition({
op : executable[2][0],
conditions: executable[2].slice(1),
target : e.target
})
) {
return;
}
...
methodName = executable[0]; // "postValue"
...
action = controlDescriptor.Actions[methodName]; // pega.c.UIElement.Actions.postValue
...
action.apply(scopeObj, executable[1], e);
...
}
Finally, we've arrived to the actual action execution. Every action will have its own implementation.
The actions that can be configured in a control will eventually execute a function in the pega.c.UIElement.Actions object. For example:
pega.c.UIElement.Actions.postValuepega.c.UIElement.Actions.runScriptpega.c.UIElement.Actions.runDataTransformpega.c.UIElement.Actions.runActivity
The call stack
To finalize this post, this is the call stack produced during the execution of the Post Value action.
| Scope | Namespace | Function |
|---|---|---|
| public | pega.c.UIElement.Actions | postValue |
| private | pega.c.actionSequencer | executeAction |
| private | pega.c.actionSequencer | executeOne |
| private | pega.c.actionSequencer | enqueueExecuteOne |
| public | pega.c.actionSequencer | schedule |
| private | pega.c.ControlBehaviorExecutor | performExecution |
| private | pega.c.ControlBehaviorExecutor | processBehaviorsOnElem |
| public | pega.c.ControlBehaviorExecutor | processBehavior |
| private | pega.c.eventController | fireImplicitEventSubscribers |
| private | pega.c.eventController | notifyEventToAll |
| public | pega.c.eventController | handler |
| public | pega.c.eventController | changeHandler |

Comments
Post a Comment