Welcome to Episode 3 of "Everything You Need to Know About Maximo Mobile". In this episode, we delve into the intricacies of Maximo Mobile customizations, focusing on the use of JavaScript and validations when altering the status of a work order.
Changing the status of a work order is a common task, and there are several customizations that can enhance this process. Here are some that might resonate with many client scenarios:
Conditionally Filtering the Status ListA previous blog post discussed the use of the onAfterLoadData
method to filter the status list that appears when initiating the change status dialogue. However, there are instances where clients may want to present technicians with only the subsequent available status, thereby streamlining their choices.
To address this, I've crafted a function named _filterWOStatusList
. This function is designed to conditionally display only the upcoming status, effectively filtering the dataset:
_filterWOStatusList(dialog) {
let parentView = dialog.parent;
// current WO status
let woStatus = parentView.state.woItem.status;
let statusList = [];
switch (woStatus) {
case 'APPR':
statusList = ['INPRG'];
break;
case 'INPRG':
statusList = ['ONHOLD', 'RESTORED'];
break;
case 'ONHOLD':
statusList = ['INPRG'];
break;
case 'RESTORED':
statusList = ['COMP']; // no more actions needed from Maximo Mobile
break;
default:
statusList = [];
break;
}
var statusDS = parentView.findDatasource('dsstatusDomainList');
if (statusDS && statusDS.dataAdapter.items.length > 0 && statusList.length > 0) {
let filteredStatus = statusDS.dataAdapter.items.filter((item) => {
return statusList.includes(item.value);
});
log.d(TAG + ' Filtered Status -->', JSON.stringify(filteredStatus));
statusDS.load({ src: filteredStatus, noCache: true });
}
}
While I've employed a switch/case structure for this function, an if/else approach would work just as well. To integrate this function into the dialogue's lifecycle, follow these steps:
Step 1: Invoke the function upon opening the dialogue.
dialogOpened({ dialog }) {
if (dialog.name == 'woStatusChangeDialog') {
this._filterWOStatusList(dialog);
}
}
Step 2: Initialise the function during the dialogue setup. It's worth noting that the dialogue won't utilise the dialogOpened
function during its inaugural load. To circumvent this, we introduce the filter during the dialogue's creation phase.
dialogInitialized(dialog) {
if (dialog.name == 'woStatusChangeDialog') {
// Filtering the data for the fist change status dialog loading
this._filterWOStatusList(dialog);
}
}
Validating the Work Order Prior to Status Change
While it's entirely acceptable to employ a series of automation scripts to validate a Work Order before modifying its status, the experience isn't consistent across platforms. On the Role Base Application, upon selecting and confirming a new status, users receive feedback identical to what they'd get on the Maximo UI. However, the mobile experience is a different story.
On mobile devices, all transactions (changes made to one or more records) are queued for background synchronisation. This is where I encountered challenges. When online, validation messages don't pop up as they do in the Role Base Application. Instead, they manifest as transaction errors in the error queue. This discrepancy led to complications for me, especially when I made other record changes before the status update. This left my record in an uncorrectable state, prompting me to shift validation to the mobile platform—a move that proved advantageous, especially in offline mode.
For guidance, I referred to this blog post and tailored it to my requirements. While the foundational approach remained unchanged, I needed work order details for validation. Here, I'll demonstrate how to obtain these details, assuming a mandatory status change for the Work Order.
It's essential to recognise that Maximo Mobile operates with an event handler. For a deeper dive into this, please refer to Episode 2: Maximo Mobile Event Emitter.
Building on the aforementioned blog's concept, I opted to override the changeStatus
function, incorporating validation before invoking the out-of-the-box function. I retained the status filter function for concurrent use with validation.
dialogInitialized(dialog) {
if (dialog.name == 'woStatusChangeDialog') {
// Filtering the data for the fist change status dialog loading
this._filterWOStatusList(dialog);
// start everriding the changeStatus function
let controller = dialog.controllers[0];
// Save the OOTB Change status function
let coreChangeStatus = controller.changeStatus.bind(controller);
controller.changeStatus = (evt) => {
// Custom validation on before changing status
coreChangeStatus();
};
}
}
Next, I signaled the graphite framework of an impending action, allowing another function to intercept it.
dialogInitialized(dialog) {
if (dialog.name == 'woStatusChangeDialog') {
// Filtering the data for the fist change status dialog loading
this._filterWOStatusList(dialog);
// start everriding the changeStatus function
let controller = dialog.controllers[0];
// Save the OOTB Change status function
let coreChangeStatus = controller.changeStatus.bind(controller);
controller.changeStatus = (evt) => {
// Custom validation on before changing status
log.t(TAG + '[%s] - before-change-status fired', dialog.name);
this.app.emit('before-change-status', {
newStatus: dialog.state.selectedStatus,
app: this.app,
page: this.page,
dialog
});
log.t(TAG + '[%s] - Can change status: %b', dialog.name,
dialog.state.canChangeStatus);
coreChangeStatus();
};
}
}
The this.app.emit
method accepts a string (in this case, "before-change-status") and an object. This object, passed as a parameter to my "handler" function, contains details like the selected status from the dialogue, application scope, page scope, and the dialogue itself.
My handler function, named onBeforeChangeStatus
, allows for object destructuring in its parameters, streamlining the code and enhancing readability. Thus, we avoid doing something like paramName.newStatus or let newStatus = paramName.newStatus to keep the code clean.
onBeforeChangeStatus({ newStatus, app, page, dialog }) {
}
Conditional checks determine if I'm on the Schedule or Work Order Details pages to retrieve the work order specifics.
onBeforeChangeStatus({ newStatus, app, page, dialog }) {
let workorder = page.name === 'schedule' ? page.state.woItem :
page.getMainDatasource().currentItem;
}
Subsequently, I embarked on constructing the validation. Given that my validations vary based on the chosen new status, I established two variables to determine the feasibility of the status change.
onBeforeChangeStatus({ newStatus, app, page, dialog }) {
let workorder = page.name === 'schedule' ? page.state.woItem :
page.getMainDatasource().currentItem;
let valid = true;
let message = '';
if (newStatus === 'INPRG') {
valid = !!workorder.schedstart;
message = 'BMXAA4195E - A value is required for the Scheduled Start field on the WORKORDER object.';
}
if (!valid) {
app.error(message);
dialog.state.canChangeStatus = false;
}
}
And here is the icing on the cake. After showing the error message on line 11 above, I update my dialogue's state for potential future use. Since the dialogInitialized
method is invoked once, I incorporated a dynamic condition to assess the status change viability.
This state now integrates seamlessly into my override function.
dialogInitialized(dialog) {
if (dialog.name == 'woStatusChangeDialog') {
// Filtering the data for the fist change status dialog loading
this._filterWOStatusList(dialog);
// start everriding the changeStatus function
let controller = dialog.controllers[0];
// Save the OOTB Change status function
let coreChangeStatus = controller.changeStatus.bind(controller);
controller.changeStatus = (evt) => {
// Custom validation on before changing status
log.t(TAG + '[%s] - before-change-status fired', dialog.name);
this.app.emit('before-change-status', {
newStatus: dialog.state.selectedStatus,
app: this.app,
page: this.page,
dialog
});
log.t(TAG + '[%s] - Can change status: %b', dialog.name,
dialog.state.canChangeStatus);
// dynamically test if I can change the status
if (dialog.state.canChangeStatus) {
coreChangeStatus();
} else {
dialog.state.canChangeStatus = true;
dialog.closeDialog();
}
};
}
}
To wrap things up, it's crucial to bind our novel onBeforeChangeStatus
function, ensuring its accessibility by the app.
applicationInitialized(app) {
this.app = app;
this.app.on('before-change-status', this.onBeforeChangeStatus);
}
In conclusion, the intricacies of Maximo Mobile customisation offer developers a rich tapestry of tools to refine and enhance the user experience, particularly when it comes to work order status changes. By harnessing the power of event handlers, conditional filtering, and validation mechanisms, developers can seamlessly align the mobile platform with specific organisational workflows and requirements. This level of adaptability not only ensures that Maximo Mobile remains a robust and responsive tool but also underscores its potential to be a tailored solution, primed to address the nuanced challenges of the modern mobile landscape and guarantee optimal user satisfaction.
If you'd like to learn more about anything from this blog, CLICK HERE to get in touch with us - we'd love to hear from you! This blog is part of a series aimed at enabling you to customise your Maximo Mobile application to your specific needs. If you're interested in delving deeper into this topic, don't hesitate to subscribe to our newsletter. We're here to help you on your journey towards mastering Maximo Mobile customisation!
Link to Episode 1:
Episode 1: Maximo Mobile Customisation
Link to Episode 2:
Episode 2: Maximo Mobile Event Emitter