by | Aug 20, 2023

    Episode 2: Maximo Mobile Event Emitter

    Introduction

    Welcome to Episode 2 of "Everything You Need to Know About Maximo Mobile". In today's installment, we'll explore the intricacies of the Event Emitter.

     

     

    JavaScript event emitter is a powerful tool for creating custom events in JavaScript applications. It allows us to trigger events, bind event listeners, and pass data between components. This feature paves the way for an event-driven architecture, letting different sections of an application communicate seamlessly without being tightly intertwined. As a staple in web development, the event emitter pattern can vastly improve the modularity and maintainability of our code. Through the adept use of event emitters, we are better equipped to develop adaptable applications capable of navigating intricate workflows and user interactions.

    IBM's Maximo Mobile leverages this event emitter architecture, empowering users to broadcast their own events. To begin, we'll examine the default events emitted by Maximo Mobile on its data sources, which are subsequently managed by the methods detailed in the MAF Development Information

     

    Default events emitted by Maximo Mobile

    onDatasourceInitialized(Datasource)

    Called when the datasource initialises. We get access to the datasource and might choose to manipulate the options or query, etc.

    onBeforeLoadData(Datasource, Query)

    Called before the datasource loads data. We can manipulate the query.

    onAfterLoadData(Datasource)

    Called after the data was loaded, and we got the items. This is used most frequently to manipulate data in the items Array. E.g., we might need to add custom fields or manipulate the response before it goes to the datasource.

    onLoadDataFailed(Error, Datasource, Query)

    Called if the data load failed.

    onBeforeSaveData({datasource, items, throwError})

    Called before saving data. It allows the app controller to inspect/manipulate the data being saved.

    onAfterSaveData({datasource, items, hasNewItems})

    Called immediately after the save is successful and can indicate if new items result from the save.

    onSaveDataFailed({datasource, items, warnings})

    Called if the data fails to save. It will include the list of warnings that prevented the save.

    onBeforeAddData({datasource, item, options})

    Called before add is called. It allows the app controller to inspect/manipulate the data being added.

    onAddDataFailed({datasource, item, error})

    Called if the item fails to be added. It will include the list of warnings that prevented the data inclusion.

    onBeforeAddNewData({datasource, options})

    Called before addNew is called.

    onAddNewRecord({datasource, item})

    Called after the addNew is called and the item is added to the datasource.

    onAddNewFailed({error, datasource, response})

    Called if the add new fails. It will include the error that prevented the call and the complete response.

    onValueChanged({datasource, item, field, oldValue, newValue, changes})

    This is the most commonly used event. It is called after data has changed on a field in a datasource. It allows for an opportunity to reject the change. While we can't reject it (it already happened), we can use other datasource APIs to alert the user of failures, etc.

    Other event handlers for events emitted by Maximo Mobile for the entire application and pages are:

    applicationInitialized(Application) - applicationInitialized will be called with the Application instance when the application is initialized. The typical usage for this method is to capture the application instance and store it so that you can later use it in other event callbacks. This method is only ever called once.

    onContextReceived({context, app}) - onContextReceived will be called when an application is launched with a given context. Launching in context is an advanced operation whereby you communicate information between 2 applications. The context can be any JSON object.

    onAppSerialize({app}) - onAppSerialize is called when the application is being pushed to the background, allowing the application developer to inject additional information into the application state for serialization.

    onAppDeserialize({app}) - onAppDeserialize is called when the application is being restored, and it allows the application developer to restore some state or configure the application after being restored.

    pageInitialized(Page, Application) - pageInitialized will be called when the page is initialized. It will be passed to the Page and Application instances. This method is only ever called once. It is called once all aspects of a page, such as data sources, have been initialized. A possible use for this method might be to set up various Page state variables based on some complex conditions or to store the Page and Application instances for later use in other event callbacks.

    pageResumed(Page, Application) - pageResumed will be called when returning to a page. It will be passed the Page instance. A possible use for this method might be to reset the state of the page when you return to it.

    pagePaused(Page, Application) - Called when the page leaves scope. i.e., when you change pages, pagePaused will be called for your current page before calling pageResumed on the next page.

    getPageStack(Array<Page>, Page) - Called when the page is resumed/initialized to allow a controller to inject additional information into the Array <Page> stack. This is useful for manipulating the Breadcrumb.

    onPageSerialize({page}) - Called when the application leaves to go to another application, allowing the page to save additional stateful information that can be restored once the application resumes.

    onPageDeserialize({page}) - Called when the application resumes from another application.

    The pageResumed is always called even the first time the page is created. So if there is work that needs to be done for pageInitialized and pageResumed, you can do it in the pageResumed, and it will be called every time the page is created or resumed.

     

    How it works, and what can we do?

    On the AppCustomizations.js or any custom Controller, we can use one of the above functions and start validating the data sources, application, or page. Here is an example of changing the datasource after the data was fetched and loaded.

    async onAfterLoadData(datasource) { 
              if (datasource.name === 'resolutioncategoryds') {  
                 datasource.setQBE('domainid', '=', 'RESOLUTION');    
                 await datasource.searchQBE();  
              }
    }

    However, sometimes, we want to use a different function to handle the events for a specific page, datasource or user action. For example, to listen to a particular event on a datasource, we can use the on function to attach a listener to it.

    Let's say I want to create a custom function to change the work order status and get the validation from Maximo to present to the user. The OOTB change status in Maximo Mobile will use the invokeAction function from the Datasource class, passing the action as changeStatus, and we can listen to this event and act in case it fails.

    In the code below, I have attached the function onChangeStatusFailed to listen to the event invoke-action-failed. My method will be called if the change status action fails to execute.

    /**
    * Capture when the Change Status fails because of Maximo's validation*/
    onChangeStatusFailed(obj) {
          this.page.state.changeStatusFailed = true;       
          this.page.state.changeStatusFailedReasonCode = obj.error; 
          this.page.state.workloading = false;
    }

    async _myChangeStatus(newStatus) {
         let mainDataSource = this.page.getMainDatasource();
         let action = 'changeStatus';
         let option = [...]
         try {  
             await mainDataSource.invokeAction(action, option);
         } catch (error) {  
             log.i(TAG, error);
         } finally {  
             mainDataSource.off('invoke-action-failed', this.onChangeStatusFailed);
         }
    }

    Note that I executed the action inside a try/catch block, so I could turn off the listener to not keep listening to all invoke actions in the App.

    It is also important to bind the actual execution scope to the function to access other functions in AppCustomizations.js and the app and page scope.

    pageResumed(page, app) {
          this.app = app;
          this.page = page; 

           // bind the scope of this on custom function. Thus, we can access the page and application scope.
           this.onChangeStatusFailed = this.onChangeStatusFailed.bind(this);
    }

    Here is a table with all events in version 8.10 that a datasource can emit.

     

    How do I emit my Event?

    Now, you might wonder, "What if I need a specific event that doesn't already exist? How can I cater to that?" The answer is direct and uncomplicated: Simply emit your own event.

    The Maximo Mobile framework makes the process of event emission quite user-friendly. To broadcast our unique events, we simply utilize the app.emit function. Here's an example to illustrate this:

    this.app.emit('my-custom-event', object);

    The object variable can carry any pertinent information you might require when your designated function is summoned to handle the event. Similarly, we anchor our custom function to the app, ensuring it's always on standby to detect and process the event. 

    applicationInitialized(app) {
          this.app = app;  

          this.app.on('my-custom-event', this.onMyCustomEventHandler);
    }

    In conclusion, event listeners empower developers to customise the behaviour of Maximo Mobile according to distinct business mandates. Whether it's monitoring events like data modifications, page loads, device orientations, or even bespoke events, developers have the liberty to institute logic and workflows congruent with their organization's singular operations. Such a degree of customization transforms Maximo Mobile into a bespoke solution, finely tuned to meet specific demands and ensure optimal efficiency.

    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 customization!

     

    Link to Episode 1:
    Episode 1: Maximo Mobile Customisation

     

     

     

    Sign up to our free newsletter to explore emerging technologies, industry events and Maximo best practice.