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