Search This Blog

Wednesday, December 23, 2020

MAXIMO Work Center Customization

Work Centers offer you a simpler, task-based, and role-based interface that is responsive on desktops, notebooks, tablets, and smartphones. 

Several related tools are available for activities such as designing data sets for business analysis
, creating inspection forms, and quickly entering service requests.
But as of today there is not much documentation or tool support available to customize work 
centers ,
IBM have a work center designer in their road map but that's at least an year away.
So I am covering simple use case for customization Work Center which may be helpful to 
community while we await on more detailed tooling and documentation support from IBM available 
publicly.


Recently my client had requirement for customized Inspection Work Center having text response field to
 be enhanced as below :

  •  Text Response filed should be customized to use multiline text box instead of Out-of-Box textbox.
  • Newly updated text response filed control/component should have default width and 
  • height to display larger size component.
  • Text Response field should be able to handle 10000 characters instead of default limited size.
So first lets take look at existing text response field on Inspection Work Center before 
achieve this customization.



Now lets take look on which all artifacts will need to be changed to achieve this:

So we will need to replace default “maximo-text” component for free-form response
field on Inspection Work Center forms, to use “maximo-textarea” component. 

Also additionally “maximo-textarea” component needs be enhanced or customized to have 
different increased default width and height , as well 1000 character limit on the 
text area as requested by Client.

So we will need to change below files 
1. Take back up of existing files.
  •  <maximo-install-root>\maximo-x\script\maximocards\inspection\inspector\maximo-response-field\maximo-response-field.js
  •    <maximo-install-root>\maximo-x\script\maximocomponents\maximo-textarea\maximo-textarea-css.html
  •    <maximo-install-root>\maximo-x\script\maximocomponents\maximo-textarea\maximo-textarea.js

2. Apply Changes to files :
**********************************************************************************
a) maximo-response-field.js :
   Update respective methods to use "maximo-textarea" components by replacing "maximo-text"
 component
    setTextResponse: function(response) {
        var input = this.$.responseContainer.querySelector('maximo-textarea');
        input.value = response;
        input.fire('change');
        this.notifyResponseSet();
  },

 buildTextInput: function() {
        var self = this;
        var resultRecord = this.resultrecord;
        var itemfield = this.item;
        var responseFieldType = this.getResponseFieldType(
        itemfield.fieldtype_maxvalue, itemfield.metertype_maxvalue);
        // Create component attributes
        var attrs = {};
        attrs.id = itemfield._id + 'textresponse';
        attrs.placeholder = $M.localize('uitext', 'mxapiinspection',
        'enter_your_answer-here');
        attrs.type = 'text';
        attrs.label = itemfield.description;
            // Set required
        attrs.readonly = false;
        if (!resultRecord || resultRecord.status_maxvalue === 'COMPLETED') 
        {
          attrs.readonly = true;
        }
            // Set require flag
        if (itemfield.required === true) {
          this._setRequired(true);
        } else {
          this._setRequired(false);
        }
        // Create component
        var responseField = Polymer.Base.create('maximo-textarea', attrs);
        // Fetching response 
        //var response = this.filterResult(resultRecord, itemfield);
        var response = itemfield._outerinspfieldresult;
          // Add change listener on input
          responseField.addEventListener('change', function(event) {
          event.stopPropagation();
          self.clearMessages();
          var lastResponse = self.fieldResponse ? self.fieldResponse : response;
          var payload = {
              inspresult: resultRecord,
              respfield: self.prepareResponseField(resultRecord, itemfield, 
              this.value, lastResponse),
              successCallback: self.updateResponseSuccessHandler.bind(self),
              errorCallback: self.updateResponseFailureHandler.bind(self)
          };
          self.fire('update-response-field', payload);
        });
        // Prevent scroll to top
        responseField.$.label.addEventListener('click', this.preventScroll);
        // Append input in document
        this.$.responseContainer.appendChild(responseField);
        // Set proper ID
        if ('_setIdentification' in responseField) {
          responseField._setIdentification();
        }
        // Need to be after inserted in DOM tree
        // Set value and store it locally    
        if (response) {
          // Store response object
          if (response[responseFieldType] !== undefined) {
            var respVal = response[responseFieldType];
            responseField.value = respVal;
            // Unecessary since completion has been processed before in container
            this.async(function() {
              this._responseChanged(responseFieldType, response);
            });
          }
        }
        // Add style
        responseField.setAttribute('style', 'text-align:left;');
        responseField.$.input.setAttribute('style', 'font-size:129%;width:400%;
        hight:450%');
        responseField.$.label.setAttribute('style', 'font-size:129%;');
        if (!responseField.label) {
        responseField.$.label.setAttribute('hidden', true);
        }
  },

restorePreviousValue: function() {
    var type = this.item.fieldtype_maxvalue;
    var responsefield = this.getResponseFieldType(
        this.item.fieldtype_maxvalue, this.item.metertype_maxvalue);
    var input;
    if (type === 'TR') {
      input = Polymer.dom(this.$.responseContainer).querySelector('maximo-textarea');
      input.value = this.fieldResponse[responsefield];
    } else if (type === 'SE') {
      input = Polymer.dom(this.$.responseContainer).querySelector('maximo-text');
      input.value = this.fieldResponse[responsefield];
    } else if (type === 'FU') {
      console.warn('Problem to fetch inspection result data');
    } else if (type === 'MM') {
      var meterType = this.item.metertype_maxvalue;
      if (meterType === 'CHARACTERISTIC') {
        input = Polymer.dom(this.$.responseContainer).querySelector('maximo-select');
      } else {
        input = Polymer.dom(this.$.responseContainer).querySelector('maximo-text');
      }
      input.value = this.fieldResponse[responsefield];
        }
    },

buildGenericInput: function() {

    var resultRecord = this.resultrecord;
    var itemfield = this.item;
    $j(this.$.responseContainer).empty();

    //filter fields down to only ones that exist on result record
    var self = this;
    var responseArray = [];
    if (resultRecord && resultRecord.inspfieldresult && itemfield.inspfieldresult) 
    {
      responseArray = itemfield.inspfieldresult.filter(function(el) 
      {
        return el.orgid === resultRecord.orgid && 
          el.resultnum === resultRecord.resultnum;
      });
    }
    var responseField = '';
    var responseFieldType = this
    .getResponseFieldType(itemfield.fieldtype_maxvalue);

    if (itemfield.fieldtype_maxvalue === 'TR' || 
        itemfield.fieldtype_maxvalue === 'SE') {
      responseField = Polymer.Base.create('maximo-textarea', {
        'id': itemfield._id + 'textresponse'
      });
      if (itemfield.fieldtype_maxvalue === 'TR') {
        responseField.placeholder = $M.localize('uitext', 'mxapiinspection',
        'enter_your_answer_here');
      } else {
        responseField.placeholder = $M.localize('uitext', 'mxapiinspresult',
        'numeric_response');
      }
    } else {
      var placeholdertext = $M.localize('uitext', 'mxapibase', 'select_option');
      responseField = Polymer.Base.create('maximo-select', {
        'id': itemfield._id + 'responseFieldresponse','placeholder': 
         placeholdertext
      });
    }

    if (resultRecord && resultRecord.inspfieldresult && responseArray[0] && 
      responseArray.length > 0) {
      if (!responseArray || responseArray.length === 0 || 
          responseArray[0][responseFieldType] === undefined) {
        responseField.setAttribute('value', '');
      } else {
        responseField.setAttribute('value', responseArray[0][responseFieldType]);
      }
    } else {
      responseField.setAttribute('value', '');
    }

    //Dynamically build Text, Numeric and Single Select Response Fields
    if (itemfield.fieldtype_maxvalue === 'TR' ||  
        itemfield.fieldtype_maxvalue === 'SE') {
      responseField.label = itemfield.description;

      if (itemfield.fieldtype_maxvalue === 'TR') {
        responseField.setAttribute('id', itemfield._id + 'textresponse');
        responseField.setAttribute('type', 'text');
      } else if (itemfield.fieldtype_maxvalue === 'SE') {
        responseField.setAttribute('id', itemfield._id + 'numericresponse');
        responseField.setAttribute('type', 'text');

        $j(responseField.$.input).attr('class',
           'input-no-spinner style-scope maximo-text');
        $j(responseField.$.input).attr('step', 'any');
      }

      $j(responseField.$.input).on('change', function(e) {
        self._updateResponseField(e);
        self.domHost.fire('maximo-response-field-changed', e);
      });

      if (!this.resultrecord) {
        responseField.setAttribute('readonly', true);
      } else {
        $j(responseField.$.input)
        .keyup(
            function() {

              if (responseField.required === true && !responseField.label) {
                if ($j(this).val().length > 0) {
                  $j(responseField.$.input)[0].parentElement.removeAttribute
                    ('required');
                  $j(responseField.$.input)[0].parentElement.setAttribute
                    ('notrequired', '');
                } else {
                  $j(responseField.$.input)[0].parentElement.removeAttribute
                    ('notrequired');
                  $j(responseField.$.input)[0].parentElement.setAttribute
                    ('required', '');
                }
              }

              //only process for single numeric entry
              if ($j(this)[0].parentElement.parentElement.parentElement.
                    parentElement.parentElement.item.fieldtype_maxvalue === 'SE') 
                {
                    var num = $j(this).val();

                var reg = new RegExp('^(?!.*?\\.{2})
                                    (?!.*\,\,)(?!.*--)[0-9\.\,\-\/]+$');

                if (!reg.test(num)) {
                  num = num.substring(0, num.length - 1);
                  $j(this).val(num);
                }

                //needed for edge browser to trigger change event when field is 
                    cleared
                if (num === '') {
                  $j(responseField.$.input).trigger('change');
                }

              }
            });

      }

      if (!this.resultrecord) {
        if (itemfield.required === true) {
          $j(responseField.$.input)[0].parentElement.setAttribute('required','');
        }
      } else {
        if (itemfield.required === true && !itemfield.description) {
          if (responseField.getAttribute('value').length > 0) {
            $j(responseField.$.input)[0].parentElement.removeAttribute('required');
            $j(responseField.$.input)[0].parentElement.setAttribute('notrequired', '');
          } else {
            $j(responseField.$.input)[0].parentElement.removeAttribute('notrequired');
            $j(responseField.$.input)[0].parentElement.setAttribute('required','');
          }
        }
      }

      $j(responseField.$.input).attr('style', 'font-size:129%;width:50%;');

      $j(responseField.$.input).attr('id', itemfield._id + '_input_response');

      if (resultRecord && resultRecord.status_maxvalue === 'COMPLETED') {
        responseField.setAttribute('readonly', 'true');
      }

    } else if (itemfield.fieldtype_maxvalue === 'SO') {
      //SINGLE SELECT SECTION a.k.a Single Option
      var res = [];

      if (resultRecord && resultRecord.status_maxvalue === 'COMPLETED') {
        $j(responseField.$.select).attr('disabled', 'disabled');
      }

      //sort collection to make sure data is in sequence order
      itemfield.inspfieldoption.sort(function(a, b) {
        return parseFloat(a.sequence) - parseFloat(b.sequence);
      });

      itemfield.inspfieldoption.forEach(function(inspfieldoption) {
        res.push(inspfieldoption.description);
      });

      var valuelist = res.join(',');

      //responseField.setAttribute('id',itemfield._id+'responseFieldresponse');
      responseField.label = itemfield.description;
      responseField.values = valuelist;

      if (this.resultrecord) {
        $j(responseField.$.select).on('change',function(e) {
              if (responseField.required === true && !responseField.label) {
                if ($j(this).val().length > 0) {
                  $j(responseField.$.select)[0].parentElement.removeAttribute
                      ('required');
                  $j(responseField.$.select)[0].parentElement.setAttribute
                      ('notrequired', '');
                } else {
                  $j(responseField.$.select)[0].parentElement.removeAttribute
                       ('notrequired');
                  $j(responseField.$.select)[0].parentElement.setAttribute
                        ('required', '');
                }
              }
              self._updateResponseField(e);
              self.domHost.fire('maximo-response-field-changed', e);
            });
      } else {
        responseField.setAttribute('readonly', true);
      }

      if (itemfield.required === true && !itemfield.description) {
        if (responseField.getAttribute('value').length > 0) {
          $j(responseField.$.select)[0].parentElement.removeAttribute('required');
          $j(responseField.$.select)[0].parentElement.setAttribute('notrequired', '');
        } else {
          $j(responseField.$.select)[0].parentElement
          .removeAttribute('notrequired');
          $j(responseField.$.select)[0].parentElement.setAttribute('required','');
        }
      }

      $j(responseField.$.select).attr('id', itemfield._id + '_select_option');

    } else {
      console.warn('Unable to build field type');
    }

    //set required flag on required response fields
    if (itemfield.required === true) {
      responseField.setAttribute('required', '');
      //hide required flag when label is empty
      if (!responseField.label) {
        $j(responseField.$.label).attr('hidden', '');
      }
    }

    responseField.setAttribute('style', 'text-align:left;');
    $j(responseField.$.label).attr('style', 'font-size:129%;');

    this.parentElement.setAttribute('style',
    'padding-left:21px;padding-bottom:20px;');
    responseField.fieldEvent = itemfield;

    this.$.responseContainer.appendChild(responseField);
  },
   
*********************************************************************************
b) maximo-textarea-css.html :
        <dom-module id="maximo-textarea-css">
<template>
     <style>
    
     :host {
     --font-style:normal;
     --font-size:14px;
     }
    
     .font-style{
     font-style:var(--font-style);
     }
    
     .wrapper {
     margin: 0px;
     display: inline-block;
     }
    
     textarea {
     padding: 10px 3px;
     transition: all .5s;
     border: 1px solid var(--Cool-gray30);
     outline: none;
     margin-bottom: 3px;
     text-align: left;
     overflow-y:auto;
width: 1000px;
     background-color: var(--Primary-white);
     display: inline-block;
     box-sizing: border-box;
     font-size: var(--font-size);
     }

     textarea:focus {
     border-color: var(--Primary-blue30);
     }

     textarea[readonly]{
background: var(--Neutral-gray2);
     }

     label {
color: var(--Primary-gray50);
display: block;
     }
    
     label[required]:before {
     content: '* ';
     font-size: 110%;
     color : orange;
     }
    
     /* Custom class for textarea */
     @media (max-width: 1024px){
     .textarea-maxWidth{
     max-width: var(--max-width);
     float: var(--float-left);
     }
     }

</style>
</template>
</dom-module>

**********************************************************************************
c) maximo-textarea.js :

Polymer({
  is: 'maximo-textarea',
  behaviors : [ BaseComponent , DirtyBehavior],
  properties: {
  /** Set component to readonly */
      readonly: {
          type: Boolean,
          value: false,
          observer: '_setReadonly'
      },
      /** turn off spell checking */
      spellCheckOff: {
       type: Boolean,
       value: false
      },
      /** Set component to required */
      required: {
          type: Boolean,
          value: false,
          observer: '_setRequired'
      },
      /** Set component placeholder */
      placeholder: {
          type: String,
          value: '',
          notify: true
      },
      _internalplaceholder: {
          type: String,
          value: ''
      },
      /** Value of component */
      value: {
       type: String,
       value: '',
       notify : true,
       observer: '_updateTextarea'
      },
      /** Set the width of the textarea */
      width: {
       type: String,
       value : '1000px'
      },
      /** Set the height of the textarea */
      height: {
       type: String,
       value : '300px'
      },
      // Length for text area updated to 10000
      maxlength: {
       type: Number,
       value: 10000
      },
  resize: {
  type: String
  }
    },
    ready: function() {
     if(this.readonly){
     $j(this.$.input).prop('readonly', 'readonly');
     }
     $j(this.$.label).attr({'for':this.$.input.id});
     $j(this.$.input).attr({'name':this.$.input.id});
     if(this.width){
     $j(this.$.input).css({'width':this.width});
     }
     if(this.height){
     $j(this.$.input).css({'height':this.height});
     }
     if(this.spellCheckOff){
     $j(this.$.input).attr({'spellcheck':'false'});
     }if(this.resize){
     $j(this.$.input).css({'resize':this.resize});
     }
    },
    attached: function(){
     if(this.placeholder){
     this._internalplaceholder = ' '+this.placeholder;
     }
     this._setReadonly(this.readonly?this.readonly:false);
     this._setRequired(this.required);
    },
    _setReadonly: function(val){
        $j(this.$.input).attr({'readonly':val,'aria-readonly':val});
    },
    _setRequired: function(val){
        $j(this.$.input).attr({'aria-required':val});
    },
    _onChange: function(e){
     this.value = $j(this.$.input).val();
    },
    _updateTextarea: function(value){
     if (this.value !== undefined) {
     $j(this.$.input).val(value);
    $j(this.$.input).toggleClass('placeHolder', 
                            this.value.trim()==='');
    }
    },
    _onblur: function(e){
     this.placeholder = this._internalplaceholder;
     $j(this.$.input).toggleClass('placeHolder', this.value.trim()==='');
    },
    _onfocus: function(e){
     if(!this.readonly){
     this.placeholder = '';
     $j(this.$.input).toggleClass('placeHolder', false);
     }
    },
});

**********************************************************************************

3. Take back up of existing maximo-x.war file 
        (<maximo-install-root>\maximo\deployment\default)
4. Run the command buildmaximo-xwar.bat/sh 
            (<maximo-install-root>\maximo\deployment)
5. Deploy newly built maximo-x.war file to  Application server.

Now we can Test the change :
Login and launch inspection work center and open any existing inspection form 
and observe default textbox response field is changed to text area and also 
it has 10000 characters limit :

    


Next we will cover some additional customization which will need code changes 
to display different tabs on Inspection Work Center to display additional status wise 
filtered inspection records.

Cheers !!
 

Tuesday, July 14, 2020

MAXIMO REST API : Message Tracking for synchronous requests


IBM has added capability to to track synchronous messages starting latest version 7.6.1.1

In previous versions provided message tracking capabilities for asynchronous messages. 
Those services are Enterprise services and Publish Channels associated with external systems using JMS queues.

The new functionality provides the capability to track web service requests, Rest calls and HTTP requests.
This would help troubleshooting and reprocessing messages and better error handling for synchronous processing.

Below steps are involved to set up required system properties :

Synchronous message tracking is enabled in the Maximo System Properties application.

The set of properties enables message tracking for each request type.  You set the value to 1, save the changes, then perform a live refresh.  The default value is 0 (disabled).

mxe.int.trackrestrequests - Track rest api request
mxe.int.tracksoaprequests - Track soap synchronous request
mxe.int.tracksynchttprequests - Track xml-http synchronous request

The next set of properties determine if message details will be stored in the database.  You set the value to 1, save the changes, then perform a live refresh.  The default value is 0 (disabled).

mxe.int.storerestmsg - store sync http messages from MIF
mxe.int.storesoapmsg - store sync soap messages for tracking from MIF
mxe.int.storesynchttppmsg - store sync http messages from MIF

Note enabling message tracking of asynchronous requests will have a negative impact on performance and should only be enabled if message volume is low.  Further isolating these properties to the specific servers or clusters that will receive tracked requests using the maximo.properties file should considered to reduce the impact to users.

Friday, March 6, 2020

MAXIMO Java Script : BMXAA7837E – An error occurred that prevented the ScriptName for the LaunchPointName from running."importPackage" is not defined in <eval> at line number

 
Maximo Automation Script : Fixing Java Script error post updating to Java 1.8 / Oracle Nashorn scripting engine

Error :  BMXAA7837E – An error occurred that prevented the <ScriptName> script for the <LaunchPointNamelaunch point from running."importPackage\" is not defined in <eval> at line number X.


If you have upgraded your maximo version to latest release ( 7.6.0.6+) and have existing automation scripts written in Java Script ( using older script engines e.g. Mozilla Rhino) , then you will receive 
this error when upgrade version of maximo has Java 1.8 based scripting engines ( Oracle Nashorn) for this style import statements : 

imporClass(Packages.psdi.util.logging.MXLoggerFactory)




Reason for this error is that, Nashorn script engine (got updated via Java 1.8) does not support  the import of whole Java packages as it was in Mozilla Rhino engine.
To fix this error, workaround is to add below line of code beginning /prior to import statements
                            load("nashorn:mozilla_compat.js");

Cheers!!!