lib/entity/utils.js

/**
 * Invoke an entity resolution component
 * @param {object} component - component instance
 * @param {EntityResolutionContext} context - context derived for this invocation
 * @private
 */
async function invokeResolveEntitiesEventHandlers(component, context) {
  let logger = context.logger();
  let entityHandlers = (typeof component.handlers === 'function') ? component.handlers() : component.handlers;  
  for (var event of context.getRequest().events) {
    let eventName = event.name;
    let itemName = event.eventItem;
    let handler;
    let handlerPath;
    if (itemName) {
      // retrieve item event handler
      // nested bag items are separated using dot in item name.
      let itemNames = itemName.split('.');
      let handlerObject = entityHandlers.items[itemNames[0]];
      if (itemNames.length > 1) {
        for (let i = 1; i < itemNames.length; i++) {
          handlerObject = handlerObject.items[itemNames[i]];
        }
      }
      handler = handlerObject[eventName];
      handlerPath = `${itemName}.${eventName}`;
      // handlerPath = `${itemName.replace(/\./g,'.items.')}.${eventName}`;
    } else if (event.custom) {
      // retrieve custom event handler
      handler = entityHandlers.custom[eventName];
      handlerPath = `custom.${eventName}`;
    } else {
      // retrieve entity event handler
      handler = entityHandlers.entity[eventName];
      handlerPath = `entity.${eventName}`;
    }
    if (handler) {
      // event handlers can be async (returning a promise), but we dont want to enforce
      // every event handler is async, hence Promise.resolve wrapping of invocation
      let returnValue = await Promise.resolve(handler(event.properties || {}, context));
      // make sure return value is a boolean
      let retValue = returnValue === undefined ? true : (returnValue+''==='true')   
      logger.debug(`${eventName} returned ${retValue}`);
      if (eventName==='shouldPrompt') {        
        context._getShouldPromptCache()[itemName] = retValue;
        if (retValue) {
          // only invoke next shouldPrompt handler when current handler returned false
          break;
        } 
      } else if (eventName==='validate') {
        if (retValue && Object.keys(context.getValidationErrors()).length>0) {
          // if validation error is registered, return value should always be false
          retValue = false;
        }
        context.getResponse().validationResults[handlerPath] = retValue; 
        if (!retValue && !context.getEntityResolutionStatus().editFormMode) {
          // only invoke next validate handler when current handler returned true
          // or we are in edit form mode
          break;
        } 
      }
    } else {
      logger.error(`No handler found for event: ${handlerPath}`);
      break;
    }              
  } 
}

/**
 * Resolve the event handlers defined by the component
 * @param {object} component - component implementation
 * @private
 */
function getResolveEntitiesEventHandlers(component) {
  let events = [];
  let handlers = (typeof component.handlers === 'function') ? component.handlers() : component.handlers;
  if (handlers) {
    Object.keys(handlers).forEach(key => {
      if (key === 'entity') {
        Object.keys(handlers[key]).forEach(event => {
          events.push(`entity.${event}`);
        });
      }
      if (key === 'items') {
        // recursive call to get all (nested) item handlers
        getItemEventhandlers(events, '', handlers[key]);
      }   
      if (key === 'custom') {
        Object.keys(handlers[key]).forEach(event => {
          events.push(`custom.${event}`);
        });
      }
    });    
  }
  return events;
}

/**
 * Recursively add item-level event handlers for all (nested) bag items.
 * @param {*} events 
 * @param {*} eventPath 
 * @param {*} itemsNode 
 */
function getItemEventhandlers(events, eventPath, itemsNode) {
  Object.keys(itemsNode).forEach(itemKey => {
    Object.keys(itemsNode[itemKey]).forEach(event => {
      if (event === 'items') {
        // child bag items, make recursive call
        getItemEventhandlers(events, `${eventPath}items.${itemKey}.`, itemsNode[itemKey].items);
      } else {
        events.push(`${eventPath}items.${itemKey}.${event}`);
      }
    });
  });

}

module.exports = {
  getResolveEntitiesEventHandlers,
  invokeResolveEntitiesEventHandlers,
};