Source

LocalizedController.js

import {EVENT_SSAPP_HAS_LOADED, EVENT_SEND_ERROR, EVENT_SEND_MESSAGE, EVENT_REFRESH, EVENT_NAVIGATE_TAB, CSS, BUTTON_ROLES} from "../constants/events";
import WebController from "./WebController";

let BaseController;
try {
  const {WebcController} = WebCardinal.controllers;
  BaseController = WebcController;
} catch (e) {
  BaseController = WebController;
}

import wizard from '../services/WizardService';


/**
 * Master Controller to provide access to the Localization features provided by WebCardinal.
 *
 * Also provides integration with pdm's web components error events
 *
 * All child classes must perform the following on their constructor:
 * <pre code=javascript>
 *      constructor(element, history){
 *          super(element, history);
 *          super.bindLocale(this, pageName);
 *          this.model = this.initializeModel();
 *      }
 * </pre>
 * @memberOf Controllers
 * @class LocalizedController
 * @extends WebcController
 * @abstract
 */
export default class LocalizedController extends BaseController {

  /**
   * Should return the initialized model for the controller when needed.
   * <strong>MUST</strong> be called on the constructor for locale binding via {@link WebcLocaleService}
   * @return {{}}
   */
  initializeModel = () => ({});

  /**
   * Shows an Ionic model
   * @param {string} modalName the modal's name registered via:
   * <pre>
   *     customElements.define('registration-modal', class extends HTMLElement{
   *          connectedCallback(){
   *              this.innerHTML = `
   *                  <ion-header class="ion-padding">
   *                      <ion-title>Title</ion-title>
   *                      <ion-content>
   *                          Content
   *                      </ion-content>
   *                  </ion-header>`
   *              }
   *          });
   * </pre>
   * @param {boolean} [swipeToClose]: enables slideToClose when available. defaults to false
   * @param {object} [params]: passes param to modal (ionic functionality)
   * @deprecated
   */
  showIonicModal(modalName, swipeToClose, params) {
    if (typeof swipeToClose === 'object') {
      params = swipeToClose;
      swipeToClose = false;
    }
    swipeToClose = !!swipeToClose;
    this.modal = this.createAndAddElement('ion-modal', {
      id: `Id${modalName}`,
      component: modalName,
      backdropDismiss: false,
      swipeToClose: swipeToClose,
      componentProps: params
    });
    this.modal.present();
  }

  /**
   * @deprecated
   */
  closeIonicModal() {
    if (this.modal)
      this.modal.dismiss();
  }

  /**
   * Shows a Toast via {@link showToast} with
   *  - header: 'Error'
   *  - cssClass: 'danger
   *  - button: 'Close'
   * @param {string} message
   * @param {err} [err]
   */
  showErrorToast(message, err) {
    if (this.useEvents)
      return this.send(EVENT_SEND_ERROR, message, {capture: true});
    return this.showToast(message + (err ? `\n$err` : ''), 'Error', 'danger', 'Close');
  }

  /**
   * Integrates with {@link PdmBarcodeScannerController}. that mean that element needs to be somewhere,
   * typically inside ion-tabs
   * @param {{}} [props] props to pass to scanner:
   *  - title: the modal title. falls back to its barcode-title prop
   * @param {function(err, result)} callback
   */
  showBarcodeScanner(props, callback){
    if (!callback && typeof props === 'function'){
      callback = props;
      props= undefined;
    }
    const getScannerByHost = function(host){
      return host.querySelector('pdm-barcode-scanner-controller');
    }

    let scannerEl = getScannerByHost(this.element) || getScannerByHost(document.body);
    if (!scannerEl)
      return callback(`Could not find the mandatory 'pdm-barcode-scanner-controller' element`);
    scannerEl.present(props, callback);
  }

  /**
   * Shows Toast Alert
   *
   * @param {string} message
   * @param {string} [header] if given will be presenter in the header
   * @param {string|string[]} [cssClass]
   * @param {string} button the text on the close button
   * @param {function()} buttonHandler
   */
  showToast(message, header, cssClass, button, buttonHandler) {
    if (this.useEvents)
      return this.send(EVENT_SEND_MESSAGE, message, {capture: true});

    const toast = this.createAndAddElement('ion-toast');
    toast.header = header;
    toast.message = message;
    toast.position = 'bottom';
    toast.duration = 2000;
    if (cssClass)
      toast.cssClass = cssClass || CSS.TOAST;
    if (button)
      toast.buttons = [
        {
          text: button,
          role: BUTTON_ROLES.CANCEL,
          handler: buttonHandler ? buttonHandler : () => {
            console.log('Cancel clicked');
          }
        }
      ];

    return toast.present();
  }

  /**
   * Shows an alert with the specified message.
   *
   * Standard Usage:
   * <pre>
   *   const alert = await controller.showAlert('this is an <strong>alert</strong> message');
   *   const { role } = await alert.onDidDismiss();
   *   console.log('onDidDismiss resolved with role', role);
   *   if (role === 'confirm') ...
   *   if (role === 'cancel') ...
   * </pre>
   *
   * @param {string} message
   * @param {object} options object with additional configuration options:
   * <pre>
   *   {
   *     cssClass: '...' css class to be used. defaults to 'ssapp-alert'
   *     header: '...' Title text (optional)
   *     subHeader: '...' subTitle text (optional)
   *     buttons: [
   *       {
   *         text: '...' button text,
   *         role: '...' sets a role to be caught by the handler method
   *         cssClass: '...' additional styling
   *         handler: (e) => {      (optional and not recommended)
   *            console.log(e);
   *         }
   *       }
   *     ]
   *   }
   * </pre>
   *
   * defaults to:
   * <pre>
   *   {
   *    buttons: [
   *      {
   *        text: 'Ok',
   *        role: 'confirm'
   *      },
   *      {
   *        text: 'Cancel',
   *        role: 'cancel'
   *      }
   *    ]
   *   }
   * </pre>
   * @return {HTMLElement}
   */
  async showAlert(message, options){
    options = Object.assign( {
      buttons: [
        {
          text: 'Cancel',
          role: BUTTON_ROLES.CANCEL
        },
        {
          text: 'Ok',
          role: BUTTON_ROLES.CONFIRM
        }
      ]
    }, options || {});

    const alert = document.createElement('ion-alert');
    alert.cssClass = options.cssClass || CSS.ALERT;
    alert.header = options.header;
    alert.subHeader = options.subHeader;
    alert.message = message;
    alert.animated = options.animated !== false;
    alert.backDropDismiss = options.backDropDismiss !== false;
    alert.buttons = options.buttons;
    alert.inputs = options.inputs;

    document.body.appendChild(alert);
    await alert.present();
    return alert;
  }

  /**
   * Shows a confirmation Popup
   * @param {string} message the message
   * @param {string} confirmText the ok button text
   * @param {string} cancelText the cancel button text
   * @return {Promise<HTMLElement>}
   */
  async showConfirm(message, confirmText, cancelText = "Cancel"){
    return this.showAlert(message,
      {
        buttons: [
          {
            text: cancelText,
            role: 'cancel'
          },
          {
            text: confirmText,
            role: 'confirm'
          }
        ]
      });
  }

  /**
  *
  * @returns {Promise<HTMLElement>}
  * @param {{}} popupOptions: {
  *   message: string; generic message
  *   confirmText: string; cancel button label
  *   cancelText: string; cancel button label
  *   options: alert ionic options
  * }
  * @param {function(err)} [callback]
  */
async  showPopup(popupOptions, callback = undefined) {
  let { message, confirmButtonLabel, cancelButtonLabel, options } = popupOptions;
  const buttons = [{
    text: cancelButtonLabel || 'Cancel',
    role: 'cancel',
  },
  {
    text: confirmButtonLabel || 'Ok',
    role: 'confirm',
    handler: evt => {
      if (!!callback) callback(evt);
    }
  }];
  options = Object.assign({buttons}, options || {});
  return this.showAlert(message, options);
}


  /**
   * Instantiates a new Spinner
   *
   * API:
   * <pre>
   *     const loader = controller._getLoader('message', options);
   *     await loader.present();
   *     ...
   *     await loader.dismiss();
   * </pre>
   *
   * for styling the class
   *
   * @param {string} message
   * @param {object} [options] accepts params:
   *  - duration duration in ms. (no duration or 0) makes the spinner stay until dismissed (defaults to 0);
   *  - cssClass css class to append. defaults to 'ion-loading'
   *  - translucent defaults to true
   *
   * @return {ion-loading} a spinner
   * @protected
   */
  _getLoader(message, options){
    options = options || {};
    let {duration, cssClass, translucent} = options;
    duration = duration || 0;
    cssClass = cssClass || CSS.SPINNER;
    translucent = translucent !== false;
    const loading = document.createElement('ion-loading');
    loading.cssClass = cssClass;
    loading.message = message;
    loading.translucent = translucent;
    loading.duration = duration;
    document.body.appendChild(loading);
    return loading;
  }

  /**
   * Adds the locale info to the model.
   * @param {LocalizedController} controller
   * @param {string} [pageName]
   * @param {boolean} [enableValidations] defaults to false. If provided enabled Ionic Inputs form validations
   */
  bindLocale(controller, pageName, enableValidations) {
    wizard.LocaleService.bindToLocale(controller, pageName);
    if (enableValidations)
      wizard.Model.Validations.bindIonicValidation(controller);
  }

  /**
   * Makes the controller listen for the {@link EVENT_SEND_ERROR} event and show the error toast to the user
   * @protected
   */
  _bindErrorHandler(){
    let self = this;
    self.on(EVENT_SEND_ERROR, (evt) => {
      evt.preventDefault();
      evt.stopImmediatePropagation();
      self.showErrorToast(evt.detail);
    }, {capture: true});
  }

  /**
   * Makes the controller listen for the {@link EVENT_SEND_MESSAGE} event and show the toast to the user
   * @protected
   */
  _bindMessageHandler(){
    let self = this;
    self.on(EVENT_SEND_MESSAGE, (evt) => {
      evt.preventDefault();
      evt.stopImmediatePropagation();
      self.showToast(evt.detail);
    }, {capture: true});
  }

  /**
   * Calls Refresh on the controller
   * @param {{}} [detail] option props to pass on the event
   */
  refresh(detail){
    this.send(EVENT_REFRESH, detail || {}, {capture: true});
  }

  /**
   *
   * @param tabName
   * @param props
   */
  navigateToTab(tabName, props){
    this.send(EVENT_NAVIGATE_TAB, {
      tab: tabName,
      props: props
    });
  }

  /**
   * Override just for the special case of {@link EVENT_SSAPP_HAS_LOADED} to set it on document body and not the element it self because its raised on the body level
   * @param eventName
   * @param handler
   * @param options
   * @override
   */
  on(eventName, handler, options){
    if (eventName === EVENT_SSAPP_HAS_LOADED && document && document.body)
      return document.body.addEventListener(eventName, handler, options);
    super.on(eventName, handler, options);
  }

  static tabs ={
    'tab-dashboard': 'Dashboard',
    'tab-order"': 'Order',
    'tab-received-orders': 'Received Orders',
    'tab-issued-orders': 'Issued Orders',
    'tab-issued-shipments': 'Issued Shipments',
    'tab-received-shipments': 'Incoming Shipments',
    'tab-stock': 'Stock',
    'tab-shipment': 'Shipment',
    'tab-products': 'Products',
    'tab-batches': 'Batches',
    'tab-shipment-lines': 'Shipment Lines',
    'tab-individual-product': 'Product',
}

  /**
   *
   * @param {boolean} [bindMessageHandlers] defaults to false. binds (or not) the error handler to the controller
   * @param args the args for {@link WebcController}
   */
  constructor(bindMessageHandlers, ...args) {
    super(...args);
    if (typeof bindMessageHandlers === 'undefined'){
      this.useEvents = false;
    } else if(typeof bindMessageHandlers === 'boolean'){
      if (bindMessageHandlers){
        this._bindErrorHandler();
        this._bindMessageHandler();
        this.useEvents = false;
      } else {
        this.useEvents = true;
      }
    }
  }
}