Source: expression.js

  1. import invariant from 'invariant';
  2. import forIn from 'lodash/forIn';
  3. import CONST from './constant';
  4. import registry from './registry';
  5. import { useCMFContext } from './useContext';
  6. const regexExpression = new RegExp('(.*)Expression');
  7. /**
  8. * @typedef {Object} Context
  9. * @property {string} store
  10. * @property {string} registry
  11. */
  12. /**
  13. * This module define expression which are just function
  14. * used to be called in the way you want in your APP with a context and a payload as params
  15. * @module react-cmf/lib/expression
  16. */
  17. /**
  18. * register an expression
  19. * @param {string} id the id of the expression to call it later
  20. * @param {function} func the function you want to register under this id
  21. * @param {Context} context React context is optional
  22. */
  23. function register(id, func, context) {
  24. registry.addToRegistry(`${CONST.REGISTRY_EXPRESSION_PREFIX}:${id}`, func, context);
  25. }
  26. /**
  27. * get an expression from it's id
  28. * @param {string} id of the expression you want to get
  29. * @param {Context} context React context is optional
  30. */
  31. function get(id, context) {
  32. return registry.getFromRegistry(`${CONST.REGISTRY_EXPRESSION_PREFIX}:${id}`, context);
  33. }
  34. /**
  35. * expressions are registred function which can be called through configuration
  36. * @param {string|object} expression to call
  37. * @param {object} React context
  38. * @param {object} payload will be in expression argument
  39. */
  40. function call(expression, context, payload) {
  41. let id;
  42. let args;
  43. if (typeof expression === 'object') {
  44. id = expression.id;
  45. args = expression.args;
  46. } else if (typeof expression === 'string') {
  47. id = expression;
  48. args = [];
  49. }
  50. if (!id) {
  51. invariant(process.env.NODE_ENV === 'production', 'you must provide an expression id');
  52. }
  53. const check = get(id, context);
  54. if (!check) {
  55. invariant(process.env.NODE_ENV === 'production', `you must register expression ${id} first`);
  56. }
  57. return check({ context, payload }, ...args);
  58. }
  59. /**
  60. * this function will try to find all props.properties that should be evaluated agains
  61. * a registered function, the attrs parameter will be deprecated.
  62. * Each parameter name ending with Expression will be automaticaly evaluated
  63. * against their registered Expression and the result put inside a properties with name
  64. * matching the original expression attributes minus the 'Expression' part
  65. *
  66. * @param {Object.<string, *>} props React props
  67. * @param {Array.<string>} attrs of attribute to get
  68. * @param {Context} context React context
  69. * @param {payload} payload optional payload to pass
  70. * @deprecated the array param will be deprecated and replaced with context
  71. * @deprecated the context will be replaced by the payload
  72. */
  73. function getProps(props, attrs, context, payload = {}) {
  74. const newProps = { ...props, ...payload };
  75. attrs.forEach(attr => {
  76. const value = props[attr];
  77. if (typeof value === 'string' || typeof value === 'object') {
  78. // eslint-disable-next-line
  79. console.warn(
  80. `beware this is present just for the sake of backward compatibility,
  81. you should use properties ending with Expression to see them evaluated
  82. example: instead of using ${attr}, ${attr}Expression will be evaluated
  83. and result put in ${attr}`,
  84. );
  85. newProps[attr] = call(value, context, newProps);
  86. }
  87. });
  88. forIn(props, (value, key) => {
  89. const match = regexExpression.exec(key);
  90. if (match) {
  91. newProps[match[1]] = call(props[match[0]], context, newProps);
  92. delete newProps[match[0]];
  93. }
  94. });
  95. return newProps;
  96. }
  97. /**
  98. * Internal: you should not have to use it
  99. * This function will compute a new props object with extra props
  100. * using the convention `fooExpression` will return { foo };
  101. * @param {object} state redux state
  102. * @param {object} ownProps any props you want to process with expression
  103. */
  104. function mapStateToProps(state, ownProps, ctx = {}) {
  105. const props = {};
  106. const context = {
  107. store: {
  108. getState: () => state,
  109. },
  110. registry: registry.getRegistry(),
  111. ...ctx,
  112. };
  113. forIn(ownProps, (value, key) => {
  114. const match = regexExpression.exec(key);
  115. if (match) {
  116. props[match[1]] = call(ownProps[match[0]], context, ownProps);
  117. }
  118. });
  119. return props;
  120. }
  121. /**
  122. * Internal: you should not have to use it
  123. * this function cleanup the object by returning a new one by removing
  124. * all key that finish with Expression (ie `fooExpression`);
  125. * @param {object} props any props object
  126. */
  127. function mergeProps(props) {
  128. const newProps = { ...props };
  129. forIn(newProps, (value, key) => {
  130. const match = regexExpression.exec(key);
  131. if (match) {
  132. delete newProps[match[0]];
  133. }
  134. });
  135. return newProps;
  136. }
  137. /**
  138. *
  139. * @param {any} Component
  140. * @param {*} attrs
  141. */
  142. function withExpression(Component, attrs) {
  143. function WithExpression(props) {
  144. const context = useCMFContext();
  145. return <Component {...getProps(props, attrs, context)} />;
  146. }
  147. WithExpression.displayName = `WithExpression(${Component.displayName || Component.name})`;
  148. return WithExpression;
  149. }
  150. const registerMany = registry.getRegisterMany(register);
  151. export default {
  152. register,
  153. registerMany,
  154. get,
  155. call,
  156. getProps,
  157. withExpression,
  158. mapStateToProps,
  159. mergeProps,
  160. };