Source

mixins/lifeCycle.js

import dataconversor from '@devoinc/applications-data-library/utils/dataConversor';

/**
 * Contains methods related to life cycle data of the widget.
 *
 * The life cyle of a widget pass for the following steps:
 *
 * 1. Set the widget in loading view
 * 2. Call the requests
 * 3. Validate the requests
 * 4. Normalize the data
 * 5. Process the data
 * 6. Validate the process
 * 7. Render the data
 * @category Widgets
 * @subcategory Mixins
 * @module lifeCycle
 **/
export default (self) => {
  self = Object.assign(self, {
    data: null, // Data value
    loadingVisibility: true, // Show loading or not
  });

  return {
    /**
     * Set the requests objects
     * @param {Array} requests - Array of request to perform
     * @param {boolean} [bindChange=false] - Subscribe to the Request <i>"change"</i>.
     * @instance
     */
    setRequests(requests, bindChange = false) {
      self.requests = requests;
      if (bindChange) {
        for (let req of self.requests) {
          req.subscribe('change', this.refresh.bind(this));
        }
      }
    },

    /**
     * Set the data to the widget from outside of the request process.
     * @param {Array} data - Data to use in the widget.
     * @instance
     */
    setData(data) {
      self.data = data;
    },

    /**
     * Get the data structure of the widget.
     * @returns - Data structure.
     * @instance
     */
    getData() {
      return self.data;
    },

    /**
     * Set the visibility of the loading icon.
     * @param {boolean} bool - True for see loading, false to hide it.
     * @instance
     */
    setLoadingVisibility(bool) {
      self.loadingVisibility = bool;
    },

    /**
     * Show stats of the widget.
     * @instance
     */
    getStats() {
      console.log(self);
    },

    /**
     * Process the whole life cycle to draw a widget.
     * @instance
     */
    refresh() {
      if (self.hidden) {
        // If the widget is not visible then mark for refresh next time
        self.forceRefresh = true;
        return;
      }

      // Show loading
      if (self.loadingVisibility) this.showLoading();

      // Start the life cycle process
      Promise.resolve()

        // Request Segment
        // ---------------------------------------------------------------------

        // Requests
        .then(() => this.callRequests())

        // Validation
        .then((data) => this.validateRequests(data))

        // Process Segment
        // ---------------------------------------------------------------------

        // Normalize
        .then((data) => this.normalize(data))

        // Process
        .then((data) => (this.process ? this.process(data) : data))
        .then((data) => this.validateProcess(data))

        // Render Segment
        // ---------------------------------------------------------------------

        // Hide loading
        .then((data) => {
          if (self.loadingVisibility) this.hideLoading();
          return data;
        })

        // Render
        .then((data) => this.render(data))
        .then((data) => (this.afterRender ? this.afterRender(data) : data))

        // Common task for every life cycle
        .then(() => {
          self.renderTimes++;

          // TODO debug method for general debugging
          // console.log(`${self.id} rendered ${self.renderTimes} times`);

          // Force refresh if not visible
          self.forceRefresh = !self.visible;
        })
        .catch((err) => {
          console.error(
            `${self.id}: Unmanaged error caught on lifecycle.js: ${err}`
          );
        });
    },

    // Request Segment
    // -------------------------------------------------------------------------
    /**
     * Call the requests set before
     * @ignore
     */
    callRequests() {
      try {
        if (self.requests.length > 0) {
          // If there are requests return promise all
          return Promise.all(self.requests.map((req) => req.call()));
        } else if (self.data) {
          // If there are manual data return this data
          return Promise.resolve(self.data);
        }
        // In other cases show NO DATA
        return this.debugError(
          {
            msg: 'NO DATA',
            console: {
              method: 'warn',
              msg: `No requests or data set on widget`,
            },
          },
          true
        );
      } catch (err) {
        return this.debugError({
          msg: 'ERROR',
          console: {
            method: 'error',
            msg: `Error on callRequests(): ${err.stack}`,
          },
        });
      }
    },

    /**
     * Verify and validate the responses of the requests
     * @returns {any}
     * @ignore
     */
    validateRequests(data) {
      try {
        if (data && Array.isArray(data)) {
          let bad = 0;
          let unauthorized = 0;
          let error = 0;
          for (let res of data) {
            if (res.status !== 0) {
              // If the request is bad
              if (res.error)
                console.error(`${self.id} -> Error for: ${res.request}
                  ${res.error}`);
              else
                console.error(
                  `${self.id} -> Bad Request for: ${res.request}
                  ${res.msg}
                  ${res.object}`
                );
              bad++;
              if (res.status === 403)
                // If the request is unauthorized
                unauthorized++;
              // If the request responded with error
              else error++;
            } else if (
              res.object.length === 0 ||
              (res.object.d && res.object.d.length === 0)
            ) {
              // If the request is empty
              console.warn(`${self.id} -> Empty request for: ${res.request}`);
              bad++;
            }
          }
          if (bad === data.length) {
            // If all the request are bad or empty
            if (unauthorized)
              return this.debugError(
                {
                  msg: 'NOT AUTHORIZED',
                  console: {
                    method: 'error',
                    msg: `Not authorized to access tables`,
                  },
                },
                true
              );
            else if (error)
              return this.debugError(
                {
                  msg: 'ERROR',
                  console: {
                    method: 'error',
                    msg: `Error on request`,
                  },
                },
                true
              );
            else
              return this.debugError(
                {
                  msg: 'NO DATA',
                  console: {
                    method: 'warn',
                    msg: `All requests are bad or empty`,
                  },
                },
                true
              );
          } else {
            // If all is correct continue
            return Promise.resolve(data);
          }
        } else {
          // If data is something else return invalid data
          return this.debugError(
            {
              msg: 'INVALID DATA',
              console: {
                method: 'error',
                msg: `Invalid data`,
              },
            },
            true
          );
        }
      } catch (err) {
        // If some error is happening with this section
        return this.debugError(
          {
            msg: 'ERROR',
            console: {
              method: 'error',
              msg: `Error on validateRequests(): ${err.stack}`,
            },
          },
          true
        );
      }
    },

    // Process Segment
    // -------------------------------------------------------------------------

    /**
     * Return an array of data matrix or raw data of the input
     * sources for use in process
     * @param {Object|Array} data
     * @returns {any}
     * @ignore
     */
    normalize(data) {
      try {
        if (Array.isArray(data)) {
          // In case we have an array of data check if is from requests and can
          // be converted to data matrix
          let result = [];

          for (let i = 0; i < data.length; i++) {
            if (data[i].type) {
              // If the data is a requests result
              result.push(dataconversor(data[i]));
            } else {
              // If the data is not from request
              result.push(data[i]);
            }
          }

          // If there is only one result pass directly
          if (result.length === 1) result = result[0];
          return result;
        }
        // Direct data passed right away
        return data;
      } catch (err) {
        return this.debugError(
          {
            msg: 'ERROR',
            console: {
              method: 'error',
              msg: `Error on normalize(): ${err.stack}`,
            },
          },
          true
        );
      }
    },

    /**
     * If the data is an array pick the first result else pick the
     * whole result.
     * @param {*} data
     * @returns {any}
     * @ignore
     */
    process(data) {
      return Array.isArray(data) ? data[0] : data;
    },

    /**
     * Validate Process
     * @param {*} data
     * @returns {any}
     * @ignore
     */
    validateProcess(data) {
      try {
        // Accept objects
        if (data !== null && typeof data === 'object') return data;
        // Accept arrays
        if (Array.isArray(data)) {
          if (data.length > 0) return data;
          return this.debugError({
            msg: 'NO DATA',
            console: {
              method: 'warn',
              msg: `No data after process on widget`,
            },
          });
        }
        // In other cases
        return this.debugError(
          {
            msg: 'ERROR',
            console: {
              method: 'error',
              msg: `Bad data after process on widget`,
            },
          },
          true
        );
      } catch (err) {
        return this.debugError(
          {
            msg: 'ERROR',
            console: {
              method: 'error',
              msg: `Error on validateProcess(): ${err.stack}`,
            },
          },
          true
        );
      }
    },

    // Render segment
    // -------------------------------------------------------------------------
    /**
     * Render the data in the widget.
     * To be defined in each widget.
     * @returns {any}
     * @instance
     */
    render() {
      return this.debugError(
        {
          msg: 'NO DATA',
          console: {
            method: 'warn',
            msg: `Render method not defined`,
          },
        },
        true
      );
    },
    afterRender: null,
  };
};