Customizing with Advanced Rules¶
Rules are JavaScript modules that can be used to add application functionality to the Mobile Development Kit client API.
You have the ability to customize the rules you use in your app so that you can change the behavior of the app itself.
Client API Objects¶
IClientAPI
is the base interface used by developers to pass objects to rule functions. The exported rule function will be given a ClientAPI
object as an argument to provide access to a context. Through this object, the rule function can query application state and data. The ClientAPI
object implements the IClientAPI
interface or one of its sub-interface types depending on the context of the rule. An example where the rule is used in the context of a page will receive an IPageProxy
instance as its ClientAPI
object.
Note
IPageProxy
is an interface that will be passed as an argument to a rule when that rule is bound to a page. IControlProxy
is an interface that will be passed as an argument to a rule when that rule is bound to a control.
The table below identifies the ClientAPI
objects used in rule functions in the Client API.
Term | Description |
---|---|
IClientAPI |
The base interface for all objects passed to rule functions |
IControlProxy |
The interface of the object passed to a rule function when the rule is run in the context of a control |
IControlContainerProxy |
The interface of the object passed to a rule function when the rule is run in the context of a control container |
IFormCellProxy |
The interface of the object passed to a rule function when the rule is run in the context of a form cell It also provides access to the IControlProxy interface. |
IFormCellTargetProxy |
The interface of the object returned to a rule function when the rule executes the IFormCellProxy::getTargetSpecifier method |
IListPickerFormCellTargetProxy |
The interface for support object cell used in a form cell list-picker |
IPageProxy |
The interface of the object passed to a rule function when the rule is run in the context of a page |
ISectionedTableProxy |
The interface of the object passed to a rule function when the rule is run in the context of a sectioned table |
ISectionProxy |
The interface of the object passed to a rule function when the rule is run in the context of a section |
ILinkSpecifierProxy |
The interface of the object returned to a rule function when the rule executes the IClientAPI::createLinkSpecifierProxy method |
IToolbarProxy |
The interface of the object passed to a rule function when the rule is run in the context of a toolbar |
IActionResult |
The interface of the object passed to a rule function when the rule is run in the context of an action |
LoggerManager |
The interface of the object returned to a rule function when the rule executes the IClientAPI::getLogger method |
DataQueryBuilder |
The API for building OData Query options |
FilterBuilder |
The API for building OData Filter options |
Rule Examples¶
The following are examples of the IClientAPI
objects being used in a rule:
-
Using the
IControlProxy.getValue
interface in aControl.Type.FormCell.SegmentedControl
control// Control definition { "Caption": "Priorities", "OnValueChange": "/AssetWorkManager/Rules/SegmentControlChange.js", "Segments": [ "Low", "Medium", "High" ], "Value": "Low", "_Name": "SegmentedFormCell", "_Type": "Control.Type.FormCell.SegmentedControl" }
// Whenever a new selection is made // "/AssetWorkManager/Rules/FormCell/SegmentControlChange.js" is called: // SegmentControlChange.js export default function FormCellSegmentedChangeHandler(clientAPI) { let selections = ""; // getValue returns an array of Objects in the form: // [{SelectedIndex:value, ReturnValue:value}, ...] clientAPI.getValue.forEach((selectedItem) => { selections += selectedItem.ReturnValue + '\n'; }) console.log(selections); // Log all selections to the control alert(clientAPI.getValue[0].ReturnValue); // Alert with the first selection }
-
Changing
PickerItems
using the IFormCellProxy.setTargetSpecifier interface// Control definition { "Caption": "high via rule", "Segments": [ "Low", "Medium", "High" ], "Value": "Low", "OnValueChange": "/AssetWorkManager/Rules/FormCell/UpdateListPickerItems.js", "_Name": "SegmentedFormCell", "_Type": "Control.Type.FormCell.SegmentedControl" }
js // Whenever the a new segmented button is selected // "/AssetWorkManager/Rules/FormCell/UpdateListPickerItems.js" is called export default function UpdateListPickerItems(clientAPI) { // selection is the selected SegmentedControl value let selection = clientAPI.getPageProxy().getControl('SectionedTable').getSection('FormCellSection0').getControl('SegmentedFormCell').getValue[0].ReturnValue; let containerProxy = clientAPI.getPageProxy().getControl('SectionedTable').getSection('FormCellSection0'); // get a proxy to the Control.Type.FormCell.ListPicker instance let listPickerProxy = containerProxy.getControl('ListPickerFormCellSingle'); let specifier = listPickerProxy.getTargetSpecifier; specifier.setDisplayValue("{OrderDescription}"); specifier.setEntitySet("MyWorkOrderHeaders"); specifier.setReturnValue("{OrderId}"); specifier.setService("/AssetWorkManager/Services/Amw.service"); // set the query options for the list picker based on SegmentedControl Value switch (selection) { case 'Low': specifier.setQueryOptions("$top=5"); break; case 'Medium': specifier.setQueryOptions("$top=10"); break; case 'High': specifier.setQueryOptions("$top=15"); break; } listPickerProxy.setTargetSpecifier(specifier); }
-
Using logging interfaces in a rule
// Initialize logger by rule // Ideally used when the application starts export default function InitializeLoggerRule(clientAPI) { const logFileName = 'LogFile.txt'; const maxFileSizeInMegaBytes = 8; // If the logger has already been initialized, it has no effect. // FileName and fileSize are optional values, if not specified, default values will be used. clientAPI.initializeLogger(logFileName, maxFileSizeInMegaBytes); // You can even initialize logger state and level let logger = clientAPI.getLogger; logger.on; logger.setLevel('Info'); } // Or you can even use this // Initialize logger by rule which calls an action export default function InitializeLoggerWithCallingAnAction(clientAPI) { return clientAPI.executeAction('/AssetWorkManager/Actions/Logger/SetState.action'); } // Use the logger by rule (it should have been already initialized) // Available functionality: // Log a message, Edit log level, Edit state (turn on/off), Get current logger state, // Get current root log level, Upload log file export default function UseLoggerRule(clientAPI) { // Get logger instance let logger = clientAPI.getLogger; // Returns a boolean according to current logger state (on=true, off=false) logger.isTurnedOn; // Turns off the logger logger.off; // Turns on the logger logger.on; // Toggles the logger state (on/off) logger.toggle; // Returns the current logger root level. Possible values: Debug, Info, Warn, Error, Off logger.getLevel; // Sets the logger root log level. Possible values: Debug, Info, Warn, Error, Off logger.setLevel('Info'); // Logs a message with the specified log level. // If log level is not specified, the root log level will be used. const optionalLogMessageLogLevel = 'Debug'; logger.log('This is a test message', optionalLogMessageLogLevel); //Log uploading works only after successful authentication // Upload the log file according to client settings clientAPI.executeAction('/AssetWorkManager/Actions/Logger/Upload.action'); // Upload log file according to own settings logger.uploadLogFile(backendURL, applicationID); }
-
Setting links in a rule using the clientAPI.createLinkSpecifierProxy interface
// Definition { "_Type": "Action.Type.ODataService.UpdateEntity", "Target": { "EntitySet": "MyWorkOrderHeaders", "Service": "/AssetWorkManager/Services/Amw.service", "ReadLink": "{@odata.readLink}" }, "Properties": { "OrderDescription": "#Control:OrderDescription/#Value", }, "UpdateLinks": "/AssetWorkManager/Rules/OData/UpdateLinkRule.js" }
export default function UpdateLinkRule(clientAPI) { let containerProxy = clientAPI.getControl('PageOneFormCell'); let listPickerProxy = containerProxy.getControl('EquipmentList'); let queryOption = ''; // odata action expects link array let links = []; if (listPickerProxy.getValue.length > 0) { let selectedValue = listPickerProxy.getValue[0].ReturnValue; queryOption = `$filter=EquipId eq '${selectedValue}'` let link = clientAPI.createLinkSpecifierProxy('EquipmentHeader', 'MyEquipments', queryOption, ''); links.push(link.getSpecifier); } return links; }
-
Setting Toolbar item property value using a rule
// Definition { "Caption": "Dynamic toolbar item value based on rule", "Controls": [], "ToolBar": { "Controls": [ { "_Name": "DynamicItem", "_Type": "Control.Type.ToolbarItem", "Caption": "/AssetWorkManager/Images/off.png", "Image": "/AssetWorkManager/Images/off.png", "OnPress": "/AssetWorkManager/Rules/Toolbar/SetToolbarItemValue.js" }, ...
// Rule export default function SetToolbarItemValue(clientAPI) { var toolbarItems = clientAPI.getToolbarControls; var imageOn = '/AssetWorkManager/Images/on.png'; var imageOff = '/AssetWorkManager/Images/off.png'; var imageOnText = 'AddedAsFavorite'; var imageOffText = 'RemovedFromFavorite'; var newValue, newText; toolbarItems.forEach(function (element) { if (element.name == clientAPI.getName) { if (element.name == 'DynamicItem') { newText = imageOnText; newValue = imageOn; if (element.caption == imageOnText) { newText = imageOffText; newValue = imageOff; } element.setCaption(newText); element.setImage(newValue); /* The properties that rule writer is allowed to set and get are: //SET element.setCaption(newValue); // text element.setStyle(newValue); // style element.setImage(newValue); // image element.setSystemItem(newValue); // 0 - 22 (more details on Apple Developer website - https://developer.apple.com/documentation/uikit/uibarbuttonsystemitem) element.setWidth(newValue); // number element.setEnabled(newValue); // boolean element.setVisibility(newValue); // visible/hidden/collapse //GET element.name; element.clickable; element.caption; element.enabled; element.visibility; element.width; element.systemItem; */ return clientAPI.executeAction('/AssetWorkManager/Actions/Toast/' + newText + '.action'); } } }); }
-
Using feedback indicators in
ObjectTable
Sections// Definition { "Sections": [ { "ObjectCell": { "ProgressIndicator": "/AssetWorkManager/Rules/ProgressIndicator/SetIndicatorState.js", "DetailImage": "/AssetWorkManager/Images/compressor.png", "Description": "{Description}", "Footnote": "{MimeType}", "StatusText": "{FileName}", "SubstatusText": "/AssetWorkManager/Rules/OData/IsMediaLocal.js", "Subhead": "{DocumentID}", "Title": "{ObjectKey}", "OnPress": "/AssetWorkManager/Rules/ProgressIndicator/DownloadOrOpenDocument.js" }, "Target": { "EntitySet": "BDSDocuments", "Service": "/AssetWorkManager/Services/Amw.service" }, "_Type": "Section.Type.ObjectTable" } ], "_Type": "Control.Type.SectionedTable", "_Name": "SectionedTable" }
// Rules export default function DownloadOrOpenDocument(clientAPI) { const pageProxy = clientAPI.getPageProxy; let bindingObject = pageProxy.getActionBinding; let readLink = bindingObject["@odata.readLink"]; let serviceName = '/AssetWorkManager/Services/Amw.service'; let entitySet = 'BDSDocuments'; // first we need to decide if the media exists locally or needs to be downloaded return clientAPI.isMediaLocal(serviceName, entitySet, readLink).then((result) => { if (result) { // the media has been downloaded, we can open it -> the path needs to be provided in the action definition // or it should come from binding // persist the media data to local sandbox, so we can open it with the document viewer let fs = require('file-system'); const actionBinding = pageProxy.getActionBinding; if (typeof actionBinding === 'undefined') { return pageProxy.executeAction('/AssetWorkManager/Actions/OData/ODataDownloadFailure.action'); } let key = actionBinding['@odata.readLink']; let filename = actionBinding.DocFile; if (typeof filename === 'undefined' || filename === '') { filename = actionBinding.FileName; } if (typeof filename === 'undefined' || filename === '') { filename = actionBinding.SaveDocFile; } if (typeof filename === 'undefined') { // Document filename not found. proceeding with a test filename. filename = "res://TestOpenDocument.pdf"; } const filepaths = filename.split('/'); const filelastSegment = filepaths[filepaths.length - 1]; filename = filelastSegment; if (key) { var tempDir = fs.knownFolders.temp; var tempPath = fs.path.join(tempDir.path, filename); var tempFile = fs.File.fromPath(tempPath); tempFile.writeSync(pageProxy.getClientData[key], err => { return pageProxy.executeAction('/AssetWorkManager/Actions/OData/ODataDownloadFailure.action'); }); pageProxy.setActionBinding({ 'FileName': tempPath, }); return pageProxy.executeAction('/AssetWorkManager/Actions/Document/OpenRelatedDocument.action'); } } else { // The media is on the server, we can download it const pressedItem = pageProxy.getPressedItem; // get the object table section proxy, which contains the feedback indicators and performs download/open in order to update the indicator state const objectTableSection = clientAPI.getSections[0]; // set the indicator's state to in progress and start the download objectTableSection.setIndicatorState("inProgress", pressedItem); clientAPI.executeAction("/AssetWorkManager/Actions/OData/DownloadDocumentStreams.action").then((result) => { // success case // the document was successfully downloaded, we can set the indicator's state to open objectTableSection.setIndicatorState("open", pressedItem); }, (error) => { // error case objectTableSection.setIndicatorState("toDownload", pressedItem); clientAPI.executeAction("/AssetWorkManager/Actions/OData/ODataDownloadFailure.action"); }); } }); } export default function SetIndicatorState(clientAPI) { let bindingObject = clientAPI.getBindingObject; let readLink = bindingObject["@odata.readLink"]; let serviceName = '/AssetWorkManager/Services/Amw.service'; let entitySet = 'BDSDocuments'; // first we need to decide if the media exists locally or needs to be downloaded return clientAPI.isMediaLocal(serviceName, entitySet, readLink).then((result) => { if (result) { // The media is saved locally, we can open it return 'open'; } else { // The media is on the server, we can download it return 'toDownload'; } }); }
-
Localization and Formatting in a rule
// Languages related API export default function ManageLanguages(clientAPI) { // get supported languages based on resource files under i18n directory, returns in key value pair var supportedLanguages = clientAPI.getSupportedLanguages; // set language in appSettings by language key clientAPI.setLanguage('en'); // get current language in appSettings var language = clientAPI.getLanguage; } // Regions related API export default function ManageRegions(clientAPI) { // get regions based on system, returns in key value pair var regions = clientAPI.getRegions; // set region in appSettings by language key clientAPI.setRegion('SG'); // get current region in appSettings var region = clientAPI.getRegion; } // Localize text examples export default function Localize(clientAPI) { // work_orders="Work Orders" var localizedText = clientAPI.localizeText('work_orders'); // result: Work Orders // dynamic_localizable_value="{1} and {2} are required for {0} development." var dynamicParams = ['MDK', 'NativeScript 3.x', 'XCode 9.x']; var localizedTextWithDynamicParams = clientAPI.localizeText('dynamic_localizable_value', dynamicParams); // result: NativeScript 3.x and XCode 9.x are required for MDK development. // order id binding is 4013 // dynamic_binding_localizable_value_order_id="The order id is: {0}." var dynamicBindingParams = ['{OrderId}']; var localizedTextWithDynamicBindingParam1 = clientAPI.localizeText('dynamic_binding_localizable_value_order_id', dynamicBindingParams); // result: The order id is: 4013 var orderId = clientAPI.binding.OrderId; var localizedTextWithDynamicBindingParam2 = clientAPI.localizeText('dynamic_binding_localizable_value_order_id', [orderId]); // result: The order id is: 4013 } // Number formatting examples export default function NumberFormattingExamples(clientAPI) { var valueToBeConverted = 123456.789; // Format as Number var customLocale = 'en-US'; var formattedAsNumber = clientAPI.formatNumber(valueToBeConverted, customLocale); // result: 123,456.79 // Format as Currency var currencyCode = 'USD'; customLocale = 'en-FR'; var formattedAsCurrency = clientAPI.formatCurrency(valueToBeConverted, currencyCode, customLocale); // result: 123 456,79 $US // Format as Percentage valueToBeConverted = 0.7865; customLocale = 'en-US'; var formattedAsPercentage = clientAPI.formatPercentage(valueToBeConverted, customLocale); // result: 78.65% // Format as Scientific Notation var customLocale = 'en-US'; var formattedAsNumber = clientAPI.formatScientific(valueToBeConverted, customLocale); // result: 1.23456789E5 // Formatting with options. // Refer to `https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat` let options = { maximumFractionDigits: 1, useGrouping: false, } // Format as Number var customLocale = 'en-US'; var formattedAsNumber = clientAPI.formatNumber(valueToBeConverted, customLocale, options); // result: 123456.8 // Format as Currency var currencyCode = 'USD'; customLocale = 'en-FR'; var formattedAsCurrency = clientAPI.formatCurrency(valueToBeConverted, currencyCode, customLocale, options); // result: 123456,8 $US // Format as Percentage valueToBeConverted = 0.7865; customLocale = 'en-US'; var formattedAsPercentage = clientAPI.formatPercentage(valueToBeConverted, customLocale, options); // result: 78.6% // Format as Scientific Notation var customLocale = 'en-US'; var formattedAsNumber = clientAPI.formatScientific(valueToBeConverted, customLocale, options); // result: 1E5 } // DateTime, Date, Time formatting examples export default function DateTimeFormattingExamples(clientAPI) { var dateTimeValue = clientAPI.formatDatetime(new Date, "en-GB", "UTC"); // result: 28 Nov 2017 at 06:16:02 var dateValue = clientAPI.formatDate("2017-11-28T11:40:00Z", "de-DE"); // result: 28.11.2017 var timeValue = clientAPI.formatTime("2017-11-28T11:40:00Z", "zh-CN", "Asia/Shanghai"); // result: 下午7:40:00 }
-
Using
ActionResult
in a target path and in a rule// Message Action definition of action run after CreateWorkOrder action result is stored { "_Type": "Action.Type.Message", "Message": "/AssetWorkManager/Rules/OData/CreateEntityMessage.js", "Title": "#ActionResults:CreateWorkOrder/#Property:data/#Property:OrderDescription", "OKCaption": "Ok", "OnSuccess": "/AssetWorkManager/Actions/Navigation/NextChangeSetAction.action" }
// CreateEntityMessage.js export default function GetCreateEntityMessage(clientAPI) { let actionResult = clientAPI.getActionResult('CreateWorkOrder'); if (actionResult) { let entity = actionResult.success; return 'Entity created with description \"' + entity.OrderDescription + '\"'; } return 'Entity successfully created'; }
-
Call a Function Import in a rule
// TaskDetail page { "Caption": "Function Import Example", "OnLoaded": "/MDKOnlineFioriTrial/Rules/FunctionImport/GetDecisionOptions.js", "Controls": [ { "_Type": "Control.Type.SectionedTable", "Sections": [ { "Controls": [ { "OnPress": "/MDKOnlineFioriTrial/Rules/FunctionImport/ApproveRequest1.js", "_Type": "Control.Type.FormCell.Button", "Title": "Approve", "_Name": "ApproveButton", "IsVisible": false, "ButtonType": "Text", "Semantic": "Tint" }, { "OnPress": "/MDKOnlineFioriTrial/Rules/FunctionImport/RejectRequest1.js", "_Type": "Control.Type.FormCell.Button", "Title": "Reject", "_Name": "RejectButton", "IsVisible": false, "ButtonType": "Text", "Semantic": "Tint" } ], "_Type": "Section.Type.FormCell", "_Name": "FormCellSection0" } ], "_Name": "SectionedTable" } ], "_Name": "TaskDetail", "_Type": "Page", "PrefersLargeCaption": true }
// GetDecisionOptions.js export default function GetDecisionOptions(clientAPI) { var functionName = 'DecisionOptions'; var parameters = { 'InstanceID': '001' }; var oFunction = { Name: functionName, Parameters: parameters }; var serviceName = '/MDKOnlineFioriTrial/Services/FioriTrial.service'; var approveBtn = clientAPI.getPageProxy().getControl('SectionedTable').getSection('FormCellSection0').getControl('ApproveButton'); var RejectButton = clientAPI.getPageProxy().getControl('SectionedTable').getSection('FormCellSection0').getControl('RejectButton'); clientAPI.callFunction(serviceName, oFunction).then(function (result) { if (result && result.length > 0) { result.forEach(function (item) { if (item.DecisionKey === '0001') { approveBtn.setVisible(true); } if (item.DecisionKey === '0002') { RejectButton.setVisible(true); } }); } else { console.log('No result set'); } }).catch(function (error) { return console.log('------ERROR--------' + error); }); }