- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 09-03-2021 04:16 AM
As you may know, in Agent Workspace, when one wants to create a Request based on an Interaction, a portal page is opened, one gets to select the Catalog Item, fill in the form and when the Catalog Item is submitted, the created Request is linked to the source Interaction record through field Parent of the former. This is a feature that would be nice to have in Platform UI, if for no other reason, then to eliminate the need to make Catalog Items compatible with both Platform UI and Portal. And of course thus it's also a nice solution to the age old problem of how to turn Incidents into Requested Items with the press of a button.
So in the following article I will describe how to adapt this Agent Workspace based solution to Platform UI.
The original solution is based around a special portal having prefix swp
, named "Service Workspace Portal". That portal is displayed in an <iframe/>
and has a special functionality that it communicates back the final result to the parent page - originally Agent Workspace. The portal communicates various messages but of particular interest are the ones dispatched when one of buttons "Close" or "View Details" is pressed after submitting a request.
Knowing all this, it is almost self evident that the adaptation to Platform UI would rely on a UI Page that will host the <iframe/>
and a script that enables the communication. To make the solution easier to code and maintain I'll use a UI Script to host the code.
Of course the process needs a trigger and that could be a UI Action. That UI Action can be created on the Task root table and it could be given a condition so that the UI Action will only be displayed if an entry exists in table request_parent_mapping
for the current form. The primary role of that table is that it indicates to the swp
portal which field on the trigger record contains the Requested for information when creating the Request, but it can naturally be used as indicator of when and where displaying the UI Action makes sense.
A last optional piece of the puzzle could be a Business Rule on table Request that when field Parent is updated, links the parent record to the generated Request.
In short the process flows like this:
- a UI Action redirects the user to a UI Page that displays the
swp
portal in an<iframe/>
- the user selects the appropriate Catalog Item, and submits it, after filling in the form
- as a result the system creates a Request and Requested Item while setting (reference field) Parent of the generated Request to the trigger/source Task
- an optional Business Rule on Request sets a reference on the trigger/source Task to the generated Request.
Now let's see some code.
This is how the UI Script moderating data flow between the UI Page and the <iframe/>
might look like:
- API Name:
u_convertIntoRequest
- this will be used in the UI Page to load this script - UI Type: Desktop
- Script:
As you can see everything is tucked away into a closure so that potential conflicts with global code is eliminated. I have used the DNS domain example.com to come up with namespaces, you should use a real domain name to come up with something unique to you, but of course it can be anything unique enough. Needless to say you need to update the namespaces everywhere to be the same.(function (com) { (function (example) { (function (u_convertIntoRequest) { setWindowOriginIfMissing(window); // Configures an event listener for the iframe hosting the portal // This is how the UI Page will be informed what just happened in the iframe u_convertIntoRequest.onLoad = u_convertIntoRequest_onLoad; // React to the appropriate messages arriving from the swp portal in the iframe u_convertIntoRequest.onMessage = u_convertIntoRequest_onMessage; function addMessageEventListener (iframe, handler) { return function (frame) { return iframe.id == frame.frameElement.id && frame.addEventListener('message', u_convertIntoRequest.onMessage); }; } function getLocationPortComponent (port) { return port ? ':' + port : ''; } function messageDataHasMessageID (message) { return typeof message.data != 'undefined' && typeof message.data.msg != 'undefined'; } function messageIsFromWindowOriginator (message) { return message.origin && message.origin.toLowerCase() == window.origin.toLowerCase(); } function navigateToTargetRecord (message) { var originGu = new GlideURL(message.data.target_table + '.do'); return g_navigation.open(originGu.getURL({ 'sys_id': message.data.target_sys_id, 'sysparm_view': u_convertIntoRequest.sysparm_view })); } function navigateToTriggerRecord () { var originGu = new GlideURL(u_convertIntoRequest.sysparm_parent_table + '.do'); return g_navigation.open(originGu.getURL({ 'sys_id': u_convertIntoRequest.sysparm_parent_sys_id, 'sysparm_view': u_convertIntoRequest.sysparm_view })); } function setWindowOriginIfMissing (window) { if (!window.origin) window.origin = window.location.protocol + "//" + window.location.hostname + getLocationPortComponent(window.location.port); } function uponSubmitCloseSelected (message) { return message.data.msg == 'CATALOG_ITEM_CLOSED' && navigateToTriggerRecord(); } function uponSubmitTargetRecordSelected (message) { return message.data.msg == 'TARGET_RECORD_SELECTED' && navigateToTargetRecord(message); } function u_convertIntoRequest_onLoad (iframe) { return Array.prototype.slice.call(window.frames).map(addMessageEventListener(iframe, u_convertIntoRequest.onMessage)); } function u_convertIntoRequest_onMessage (message) { return messageIsFromWindowOriginator(message) && messageDataHasMessageID(message) && (uponSubmitCloseSelected(message) || uponSubmitTargetRecordSelected(message)); } })(example.u_convertIntoRequest = example.u_convertIntoRequest || {}); })(com.example = com.example || {}); })(window.com = window.com || {});
Moving on, this is how the UI Page using the UI Script might be defined:
- name:
u_convertIntoRequest
- HTML:
<?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null"> <j2:set var="jvar_DOM_id" value="iframe_$[HTML: gs.generateGUID(); ]" /> <j:set var="jvar_cache_control" value="${HTML: GlideProperties.get('glide.builddate.last', ''); }" /> <script src="/u_convertIntoRequest.jsdbx?v=${ jvar_cache_control; }" type="text/javascript"></script> <p style="text-align: center; margin: 1em;">${HTML: gs.getMessage('Return to'); }$[AMP]nbsp;<a href="$[HTML: sysparm_parent_anchor; ]"> $[HTML: sysparm_parent_number; ]</a></p> <iframe id="$[ jvar_DOM_id; ]" onload="window.com.example.u_convertIntoRequest.onLoad(this);" parent-sys-id="$[ sysparm_parent_sys_id; ]" parent-table="$[ sysparm_parent_table; ]" sandbox="allow-same-origin allow-forms allow-scripts allow-modals" src="/swp?sysparm_parent_table=$[ sysparm_parent_table; ]&sysparm_parent_sys_id=$[ sysparm_parent_sys_id; ]" style="border: none; height: 100%; width: 100%"></iframe> </j:jelly>
The reason for passing in the parent table name and the parent sys_id is to allow potential Catalog Client Scripts to determine the Requested for info where non-standard Requested for variables are used. If the OOB Requested for variable is used, that will automatically be filled based on settings in table
request_parent_mapping
mentioned above. - Client script:
(function (com) { (function (example) { (function (u_convertIntoRequest) { u_convertIntoRequest.id = '$[ jvar_DOM_id; ]'; u_convertIntoRequest.origin = '$[HTML: sysparm_parent_anchor; ]'; u_convertIntoRequest.sysparm_parent_sys_id = '$[HTML: sysparm_parent_sys_id; ]'; u_convertIntoRequest.sysparm_parent_table = '$[HTML: sysparm_parent_table; ]'; u_convertIntoRequest.sysparm_view = '$[HTML: sysparm_view; ]'; })(example.u_convertIntoRequest = example.u_convertIntoRequest || {}); })(com.example = com.example || {}); })(window.com = window.com || {});
Finally the client UI Action configuration:
- Name: Create Request
- Table: Task [task]
- Action name:
u_convert_into_request
- Form button: checked
- Client: checked
- Isolate script: not-checked
- Onclick:
u_convertIntoRequest_onClick()
- Condition:
new TaskFormUI().isCreateRequestAvailable(current);
- Script:
function u_convertIntoRequest_onClick () { return warnIfFormIsUnsaved() && openConvertIntoRequest(); function addInfoMessage (message) { g_form.addInfoMessage(message); } function openConvertIntoRequest () { var convertIntoRequestGu = new GlideURL('u_convertIntoRequest.do'), formGu = new GlideURL(); formGu.setFromCurrent(); g_navigation.open(convertIntoRequestGu.getURL({ 'sysparm_parent_anchor': formGu.getURL(), 'sysparm_parent_table': g_form.getTableName(), 'sysparm_parent_sys_id': g_form.getUniqueValue(), 'sysparm_parent_number': g_form.getValue('number'), })); } function warnIfFormIsUnsaved () { return !g_form.modified || getMessage('You have unsaved data; save the record first', addInfoMessage); } }
I will leave the exercise of creating the Script Include referenced in field Condition and the Business Rule that created additional references from the trigger/source record to the generated Request to the reader.
Also don't forget that UI Scripts are cached forever so you should add additional parameters to the URL in the src attribute of the <script> tag in the UI Page sourcing in the UI Script so that whenever you update the UI Script by changing that parameter you would be forcing all browsers to re-cache the new version of the UI Script. Something like:
<script src="/u_convertIntoRequest.jsdbx?v=${ jvar_cache_control; }&r=1.0" type="text/javascript"></script>
Whenever you would update the UI Script, version 1.0
would be modified. Of course it does not have to be a version like number, but it may be the easiest way to insure that just upping the version number you would be creating unique in time script URLs.
And finally, this is how the end result would look like - one would be taken to a page that looked like the one below when pressing the Create Request button on a task form:
After pressing the "Order now" button:
After pressing the "View Details" button:
- 1,016 Views