PredicateCore.js

  1. /* eslint no-unused-vars: "off" */
  2. /**
  3. * PredicateCore
  4. * @module core
  5. * @namespace core
  6. * @since 1.0.0
  7. * @note rules are 100% tested from PredicateCore.test.js
  8. */
  9. const {
  10. merge,
  11. find,
  12. curry,
  13. pipe,
  14. filter,
  15. map,
  16. takeLast,
  17. insert,
  18. } = require('ramda');
  19. const option = require('option');
  20. const { EventEmitter } = require('events');
  21. function head(list) {
  22. return option.fromNullable(list[0]).value();
  23. }
  24. module.exports = function({ dataclasses, invariants, errors, rules, UITypes }) {
  25. const { CompoundPredicate, ComparisonPredicate, Predicate } = dataclasses;
  26. /**
  27. * Get a type by its type_id
  28. * @param {array} types types
  29. * @param {string} type_id type id name
  30. * @return {?dataclasses.Type} a Type
  31. * @private
  32. * @since 1.0.0
  33. */
  34. const _getTypeById = (types, type_id) =>
  35. option.fromNullable(find(type => type.type_id === type_id, types));
  36. /**
  37. * Get a target by its target_id
  38. * @param {Array<dataclasses.Target>} targets targets
  39. * @param {string} target_id target id name
  40. * @return {Promise<dataclasses.Target, errors.Target_idMustReferToADefinedTarget>} resolved promise will yield a Target, rejected promise will yield `Target_idMustReferToADefinedTarget` if target_id was not found in `targets`
  41. * @private
  42. * @since 1.0.0
  43. */
  44. const _getTargetById = (targets, target_id) =>
  45. invariants.Target_idMustReferToADefinedTarget(
  46. find(target => target.target_id === target_id, targets)
  47. );
  48. /**
  49. * Get a logical type by its logicalType_id
  50. * @param {Array<dataclasses.LogicalType>} logicalTypes logicalTypes
  51. * @param {string} logicalType_id logicalType id name
  52. * @return {Promise<dataclasses.LogicalType, errors.LogicalType_idMustReferToADefinedLogicalType>} resolved promise will yield a LogicalType, rejected promise will yield `LogicalType_idMustReferToADefinedLogicalType` if logicalType was not found in `logicalTypes`.
  53. * @private
  54. * @since 1.0.0
  55. */
  56. const _getLogicalTypeById = (logicalTypes, logicalType_id) =>
  57. invariants.LogicalType_idMustReferToADefinedLogicalType(
  58. find(
  59. logicalType => logicalType.logicalType_id === logicalType_id,
  60. logicalTypes
  61. )
  62. );
  63. /**
  64. * Get an operator by its operator_id
  65. * @param {Array<dataclasses.Operator>} operators operators
  66. * @param {string[]} operator_id operator_id
  67. * @return {Promise<dataclasses.Operator, errors.Operator_idMustReferToADefinedOperator>} resolved promise will yield an Operator, rejected promise will yield `Operator_idMustReferToADefinedOperator` if operator was not found in `operators`.
  68. * @private
  69. * @since 1.0.0
  70. */
  71. const _getOperatorById = (operators, operator_id) =>
  72. invariants.Operator_idMustReferToADefinedOperator(
  73. find(operator => operator.operator_id === operator_id, operators)
  74. );
  75. /**
  76. * _getOperatorsByIds
  77. * @param {Object} operators operators
  78. * @param {string[]} operator_ids operator_ids
  79. * @return {Array<dataclasses.operator>}
  80. * @private
  81. * @since 1.0.0
  82. */
  83. const _getOperatorsByIds = curry((operators, operator_ids) =>
  84. pipe(filter(({ operator_id }) => operator_ids.includes(operator_id)))(
  85. operators
  86. )
  87. );
  88. const _set$operatorsToType = curry((columns, type) => {
  89. type.$operators = _getOperatorsByIds(columns.operators, type.operator_ids);
  90. return type;
  91. });
  92. const _set$typeToTarget = curry((columns, target) => {
  93. const typeOption = _getTypeById(columns.types, target.type_id);
  94. return invariants
  95. .TargetMustReferToADefinedType(typeOption, target)
  96. .then(type => {
  97. target.$type = type;
  98. return target;
  99. });
  100. });
  101. /**
  102. * Tap for Promise
  103. * @param {Function} f function to call
  104. * @return {Function} function that accept a promise and will call `f`
  105. * @private
  106. */
  107. const _tapPromise = f => {
  108. return function(promise) {
  109. return promise.then(result => {
  110. f();
  111. return result;
  112. });
  113. };
  114. };
  115. /**
  116. * Run `fAfter()` (without any arguments) after `fBefore`, it will yield the promise yield from fBefore
  117. * @param {Function} fBefore fBefore
  118. * @param {Function} fAfter fAfter
  119. * @return {Promise} promise from fBefore
  120. * @private
  121. */
  122. const _afterPromise = (fBefore, fAfter) => pipe(fBefore, _tapPromise(fAfter));
  123. // columns => Promise[columns]
  124. const _initializeColumns = columns => {
  125. // at first I used lenses, but the code was way harder to read so it's better that way :)
  126. // wrap operators
  127. columns.operators = map(dataclasses.Operator, columns.operators);
  128. // wrap logicalTypes
  129. columns.logicalTypes = map(dataclasses.LogicalType, columns.logicalTypes);
  130. // wrap argumentTypes (allow argumentTypes to be undefined)
  131. columns.argumentTypes = map(
  132. dataclasses.ArgumentType,
  133. columns.argumentTypes || []
  134. );
  135. // wrap types and set `$operators` attribute on each type
  136. const wrapType = pipe(dataclasses.Type, _set$operatorsToType(columns));
  137. columns.types = map(wrapType, columns.types);
  138. // wrap targets and set `$type` attribut on each target
  139. const wrapTarget = pipe(dataclasses.Target, _set$typeToTarget(columns));
  140. return Promise.all(map(wrapTarget, columns.targets)).then(targets => {
  141. columns.targets = targets;
  142. return columns;
  143. });
  144. };
  145. /**
  146. * Create a new PredicateCore
  147. * @param {Object} args Predicate Core parameters
  148. * @param {?dataclasses.CompoundPredicate} [args.data=core.defaults.options.getDefaultData] data
  149. * @param {Object} [args.columns] columns definition
  150. * @example <caption>Example of columns definition</caption>
  151. * // don't forget to take a look at the Storybook https://ui-predicate.fgribreau.com/ui-predicate-vue/latest#/examples
  152. * {
  153. * // first define the operator list available along with what type of argument they need
  154. * operators: [
  155. * {
  156. * operator_id: 'is',
  157. * label: 'is',
  158. * argumentType_id: 'smallString',
  159. * },
  160. * {
  161. * operator_id: 'contains',
  162. * label: 'Contains',
  163. * argumentType_id: 'smallString',
  164. * },
  165. * {
  166. * operator_id: 'isLowerThan',
  167. * label: '<',
  168. * argumentType_id: 'number',
  169. * },
  170. * {
  171. * operator_id: 'isEqualTo',
  172. * label: '=',
  173. * argumentType_id: 'number',
  174. * },
  175. * {
  176. * operator_id: 'isHigherThan',
  177. * label: '>',
  178. * argumentType_id: 'number',
  179. * },
  180. * {
  181. * operator_id: 'is_date',
  182. * label: 'is',
  183. * argumentType_id: 'datepicker',
  184. * },
  185. * {
  186. * operator_id: 'isBetween_date',
  187. * label: 'is between',
  188. * argumentType_id: 'daterangepicker',
  189. * },
  190. * ],
  191. * // then define the type, think of them as aggregate of operators so you can attach them to targets
  192. * types: [
  193. * {
  194. * type_id: 'int',
  195. * operator_ids: ['isLowerThan', 'isEqualTo', 'isHigherThan'],
  196. * },
  197. * {
  198. * type_id: 'string',
  199. * operator_ids: ['is', 'contains'],
  200. * },
  201. * {
  202. * type_id: 'datetime',
  203. * operator_ids: ['is', 'isBetween'],
  204. * },
  205. * ],
  206. * // finally define targets, don't forget to specify their associated `type_id`
  207. * targets: [
  208. * {
  209. * target_id: 'title',
  210. * label: 'Title',
  211. * type_id: 'string',
  212. * },
  213. * {
  214. * target_id: 'videoCount',
  215. * label: 'Video count',
  216. * type_id: 'int',
  217. * },
  218. * {
  219. * target_id: 'publishedAt',
  220. * label: 'Created at',
  221. * type_id: 'datetime',
  222. * },
  223. * ],
  224. * // define supported logical type
  225. * logicalTypes: [
  226. * {
  227. * logicalType_id: 'any',
  228. * label: 'Any',
  229. * },
  230. * {
  231. * logicalType_id: 'all',
  232. * label: 'All',
  233. * },
  234. * {
  235. * logicalType_id: 'none',
  236. * label: 'None',
  237. * },
  238. * ],
  239. * // (optional) finally define how to display each argumentType_id
  240. * argumentTypes: [
  241. * // here we don't define `component` because it depends on the UI Framework you are using (e.g. Vue, React, Angular, ...)
  242. * // since we are in ui-predicate-core here we don't know the UI Framework library that will be used
  243. * // read your UI Framework adapter (e.g. ui-predicate-vue) on how to set the component.
  244. * // if no argumentType is defined for argumentType_id, then UIPredicateCore will fallback on the default UI component (thanks to getDefaultArgumentComponent)
  245. * // { argumentType_id: 'datepicker', component: ? },
  246. * // { argumentType_id: 'daterangepicker', component: ? },
  247. * // { argumentType_id: 'smallString', component: ? },
  248. * // { argumentType_id: 'number', component: ? },
  249. * ]}
  250. * @param {Object} args.columns.operators operators
  251. * @param {Object} args.columns.types types
  252. * @param {Object} args.columns.targets targets
  253. * @param {Object} args.columns.logicalTypes logicalTypes
  254. * @param {?Object} args.columns.argumentTypes argumentTypes
  255. * @param {Object} [args.options=core.defaults.options] options
  256. * @return {Promise<core.PredicateCoreAPI, errors<*>>} resolved promise yield Predicate Core public API, rejected promise yield an error {@link errors}
  257. * @memberof core
  258. */
  259. function PredicateCore(args) {
  260. const { data, columns, ui, options } = args;
  261. return new Promise((resolve, reject) => {
  262. try {
  263. dataclasses._requireProps(
  264. columns,
  265. 'operators,logicalTypes,types,targets'
  266. );
  267. } catch (err) {
  268. reject(err);
  269. return;
  270. }
  271. resolve();
  272. })
  273. .then(() => _initializeColumns(columns))
  274. .then(_columns => {
  275. let _root;
  276. let _api;
  277. const _events = new EventEmitter();
  278. const _options = merge(PredicateCore.defaults.options, options);
  279. /**
  280. * Loop through the predicate tree and update flags (e.g. $canBeRemoved)
  281. * @return {undefined} nothing.
  282. * @private
  283. */
  284. function _apply$flags() {
  285. const canRemoveAnyPredicate = !rules.predicateToRemoveIsTheLastComparisonPredicate(
  286. _root,
  287. CompoundPredicate,
  288. ComparisonPredicate
  289. );
  290. CompoundPredicate.forEach(_root, predicate => {
  291. predicate.$canBeRemoved =
  292. canRemoveAnyPredicate &&
  293. !rules.predicateToRemoveIsRootPredicate(_root, predicate);
  294. });
  295. }
  296. function _emitChangedEvent() {
  297. _events.emit('changed', _api);
  298. }
  299. const _afterWrite = pipe(_apply$flags, _emitChangedEvent);
  300. /**
  301. * Set PredicateCore data
  302. * @param {dataclasses.CompoundPredicate} root CompoundPredicate
  303. * @return {Promise<undefined, errors.RootPredicateMustBeACompoundPredicate>} resolved promise yield nothing, rejected promise yield RootPredicateMustBeACompoundPredicate error
  304. * @since 1.0.0
  305. * @memberof core.api
  306. */
  307. function setData(root) {
  308. return invariants
  309. .RootPredicateMustBeACompoundPredicate(root, CompoundPredicate)
  310. .then(() => {
  311. _root = root;
  312. });
  313. }
  314. /**
  315. * @param {object} json user defined JSON
  316. * @return {Promise<Predicate, errors<*>>} resolved promise yield the root CompoundPredicate, rejected promise yield an errors
  317. */
  318. function _fromJSON(json) {
  319. return Predicate.fromJSON(json, {
  320. getTargetById: target_id =>
  321. _getTargetById(_columns.targets, target_id),
  322. getLogicalTypeById: logicalType_id =>
  323. _getLogicalTypeById(_columns.logicalTypes, logicalType_id),
  324. getOperatorById: operator_id =>
  325. _getOperatorById(_columns.operators, operator_id),
  326. });
  327. }
  328. /**
  329. * Add a ComparisonPredicate or CompoundPredicate
  330. * @param {Object} option option object
  331. * @param {string} options.type what type of Predicate to add
  332. * @param {string} [options.how=after] should we insert it before, after or instead of? (currently only after is supported)
  333. * @param {dataclasses.Predicate} options.where current element
  334. * @return {Promise<dataclasses.Predicate>} inserted predicate
  335. * @since 1.0.0
  336. * @memberof core.api
  337. */
  338. function add({ where, how = 'after', type }) {
  339. // currently only after is supported
  340. return (
  341. Promise.resolve()
  342. .then(() => invariants.AddOnlySupportsAfter(how))
  343. .then(() =>
  344. invariants.PredicateTypeMustBeValid(type, Predicate.Types)
  345. )
  346. // generate the Predicates
  347. .then(() => _options[`getDefault${type}`](_columns, _options))
  348. // then add it
  349. .then(predicate => {
  350. const isComparisonPredicate = ComparisonPredicate.is(where);
  351. if (isComparisonPredicate || CompoundPredicate.is(where)) {
  352. if (isComparisonPredicate) {
  353. // it's a comparisonpredicate
  354. // first find predicates array that contains the element
  355. const path = _find(where);
  356. // we are starting from a ComparisonPredicate that always live inside a CompoundPredicate.predicates array
  357. const [compoundpredicate, [_, index]] = takeLast(2, path);
  358. compoundpredicate.predicates = insert(
  359. index + 1,
  360. predicate,
  361. compoundpredicate.predicates
  362. );
  363. } else {
  364. // it's a compoundpredicate
  365. // we want to add a CompoundPredicate after a compound predicate
  366. // so we need to add it as its first .predicates entry
  367. where.predicates.unshift(predicate);
  368. }
  369. return predicate;
  370. }
  371. return Promise.reject(
  372. new errors.CannotAddSomethingElseThanACompoundPredicateOrAComparisonPredicate()
  373. );
  374. })
  375. );
  376. }
  377. /**
  378. * Remove a ComparisonPredicate or CompoundPredicate
  379. * @param {(dataclasses.ComparisonPredicate|dataclasses.CompoundPredicate)} predicate predicate
  380. * @return {Promise<dataclasses.Predicate>} yield the removed predicate, will reject the promise if remove was called with the root CompoundPredicate or the last ComparisonPredicate of the root CompoundPredicate
  381. * @since 1.0.0
  382. * @memberof core.api
  383. */
  384. function remove(predicate) {
  385. return Promise.resolve()
  386. .then(() =>
  387. invariants.RemovePredicateMustDifferFromRootPredicate(
  388. _root,
  389. predicate
  390. )
  391. )
  392. .then(() =>
  393. invariants.RemovePredicateCannotBeTheLastComparisonPredicate(
  394. _root,
  395. predicate,
  396. CompoundPredicate,
  397. ComparisonPredicate
  398. )
  399. )
  400. .then(() => {
  401. if (
  402. CompoundPredicate.is(predicate) ||
  403. ComparisonPredicate.is(predicate)
  404. ) {
  405. const path = _find(predicate);
  406. // we are starting from a ComparisonPredicate that always live
  407. // inside a CompoundPredicate.predicates array
  408. const [parentCompoundpredicate, [_, index]] = takeLast(2, path);
  409. parentCompoundpredicate.predicates.splice(index, 1);
  410. if (parentCompoundpredicate.predicates.length === 0) {
  411. // if there are not any more predicates
  412. // inside the parentCompoundpredicate, we should also remove it
  413. return remove(parentCompoundpredicate);
  414. }
  415. return predicate;
  416. }
  417. return Promise.reject(
  418. new errors.CannotRemoveSomethingElseThanACompoundPredicateOrAComparisonPredicate()
  419. );
  420. });
  421. }
  422. /**
  423. * Change a CompoundPredicate logical
  424. * @param {dataclasses.CompoundPredicate} predicate predicate
  425. * @param {string} newLogicalType_id newLogicalType_id
  426. * @return {Promise<undefined, errors.PredicateMustBeACompoundPredicate>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeACompoundPredicate error
  427. * @since 1.0.0
  428. * @memberof core.api
  429. */
  430. function setPredicateLogicalType_id(predicate, newLogicalType_id) {
  431. return invariants
  432. .PredicateMustBeACompoundPredicate(predicate, CompoundPredicate)
  433. .then(() => {
  434. // first change the logical type
  435. return _getLogicalTypeById(
  436. _columns.logicalTypes,
  437. newLogicalType_id
  438. );
  439. })
  440. .then(logicalType => {
  441. predicate.logic = logicalType;
  442. });
  443. }
  444. /**
  445. * Change a predicate's target
  446. * @param {dataclasses.ComparisonPredicate} predicate predicate
  447. * @param {string} newTarget_id newTarget_id
  448. * @return {Promise<undefined, errors.PredicateMustBeAComparisonPredicate>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeAComparisonPredicate error
  449. * @since 1.0.0
  450. * @memberof core.api
  451. */
  452. function setPredicateTarget_id(predicate, newTarget_id) {
  453. return (
  454. invariants
  455. .PredicateMustBeAComparisonPredicate(
  456. predicate,
  457. ComparisonPredicate
  458. )
  459. // first change the target
  460. .then(() => _getTargetById(_columns.targets, newTarget_id))
  461. .then(target => {
  462. predicate.target = target;
  463. // then change the operator to the first operator for this target
  464. return setPredicateOperator_id(
  465. predicate,
  466. head(predicate.target.$type.$operators).operator_id
  467. );
  468. })
  469. );
  470. }
  471. /**
  472. * Change a predicate's operator
  473. * @param {dataclasses.ComparisonPredicate} predicate predicate
  474. * @param {string} newOperator_id newOperator_id
  475. * @return {Promise<undefined, errors.Operator_idMustReferToADefinedOperator>} yield nothing if everything went right, otherwise yield a reject promise with the PredicateMustBeAComparisonPredicate error
  476. * @since 1.0.0
  477. * @memberof core.api
  478. */
  479. function setPredicateOperator_id(predicate, newOperator_id) {
  480. return (
  481. Promise.resolve()
  482. // find operator
  483. .then(() =>
  484. invariants.Operator_idMustReferToADefinedOperator(
  485. predicate.target.$type.$operators.find(
  486. operator => operator.operator_id === newOperator_id
  487. )
  488. )
  489. )
  490. // change the operator
  491. .then(operator => {
  492. predicate.operator = operator;
  493. // then reset arguments
  494. predicate.argument = null;
  495. })
  496. );
  497. }
  498. /**
  499. * Change a predicate's operator value
  500. * @param {dataclasses.ComparisonPredicate} predicate predicate
  501. * @param {*} newValue newValue
  502. * @return {Promise<undefined>} yield nothing if everything went right, currently everything always go right ;)
  503. * @since 1.0.0
  504. * @memberof core.api
  505. */
  506. function setArgumentValue(predicate, newValue) {
  507. return Promise.resolve().then(() => {
  508. predicate.argument = newValue;
  509. });
  510. }
  511. /**
  512. * Get a UI Component (e.g. Vue Component) based on the argumentType_id
  513. * @param {string} argumentType_id the argumentType id to find
  514. * @return {*} it will either yield the argumentType id associated component or fallback on {@link core.defaults.getArgumentTypeComponentById} to yield the default component
  515. * @since 1.0.0
  516. * @memberof core.api
  517. */
  518. function getArgumentTypeComponentById(argumentType_id) {
  519. return option
  520. .fromNullable(
  521. _columns.argumentTypes.find(
  522. argumentType => argumentType.argumentType_id === argumentType_id
  523. )
  524. )
  525. .map(argumentType => argumentType.component)
  526. .valueOrElse(() =>
  527. _options.getDefaultArgumentComponent(_columns, _options)
  528. );
  529. }
  530. /**
  531. * Get default or overrided ui component
  532. * @param {ui} name the UIType key to get the right component
  533. * @return {any} component
  534. * @since 1.0.0
  535. * @memberof core.api
  536. */
  537. function getUIComponent(name) {
  538. return ui[name];
  539. }
  540. /**
  541. * Compute the JSON pointer path the element
  542. * @param {Object} element (http://jsonpatch.com/)
  543. * @return {?Array} null if not found
  544. * @readonly
  545. * @since 1.0.0
  546. */
  547. function _find(element) {
  548. return CompoundPredicate.reduce(
  549. _root,
  550. (acc, predicate, parents) => {
  551. return element === predicate ? parents : acc;
  552. },
  553. null
  554. );
  555. }
  556. /**
  557. * Yield a serializable object without internal flags ($xxx properties)
  558. * @example console.log(JSON.stringify(ctrl, null, 2)); // will call ctrl.toJSON() underneath
  559. * @return {Object} a serializable object
  560. * @since 1.0.0
  561. * @memberof core.api
  562. */
  563. function toJSON() {
  564. return Predicate.toJSON(_root);
  565. }
  566. /**
  567. * Adds the `listener` function to the end of the listeners array for the event named `eventName`.
  568. * No checks are made to see if the `listener` has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.
  569. * @param {string} eventName available event names are : (`changed`, api)
  570. * @param {function} listener listener
  571. * @return {undefined}
  572. * @since 1.0.0
  573. * @memberof core.api
  574. */
  575. function on(eventName, listener) {
  576. _events.on(eventName, listener);
  577. }
  578. /**
  579. * Adds a *one-time* `listener` function for the event named `eventName`. The next time `eventName` is triggered, this `listener` is removed and then invoked.
  580. * @param {string} eventName see {@link core.api.on} for available event names
  581. * @param {function} listener listener
  582. * @return {undefined}
  583. * @see core.api.on
  584. * @since 1.0.0
  585. * @memberof core.api
  586. */
  587. function once(eventName, listener) {
  588. _events.once(eventName, listener);
  589. }
  590. /**
  591. * Remove listener(s)
  592. * If off() will remove every listeners
  593. * If off(eventName) will only remove listeners to this specific eventName
  594. * If off(eventName, listener) will remove the `listener` to the `eventName`
  595. * @param {?string} eventName see {@link core.api.on} for available event names
  596. * @param {?function} listener listener
  597. * @return {undefined}
  598. * @since 1.0.0
  599. * @memberof core.api
  600. */
  601. function off(eventName, listener) {
  602. if (!eventName) {
  603. // because removeAllListeners treat "undefined" as an event name :facepalm:
  604. _events.removeAllListeners();
  605. } else if (!listener) {
  606. _events.removeAllListeners(eventName);
  607. } else {
  608. _events.removeListener(eventName, listener);
  609. }
  610. }
  611. // get data for initialization
  612. return (
  613. (data ? _fromJSON(data) : _options.getDefaultData(_columns, _options))
  614. // setup PredicateCore data
  615. .then(_afterPromise(setData, _afterWrite))
  616. // expose public API
  617. .then(() => {
  618. /**
  619. * ui-predicate core public API
  620. * @typedef {object} PredicateCoreAPI
  621. * @namespace core.api
  622. */
  623. _api = {
  624. on,
  625. once,
  626. off,
  627. setData: _afterPromise(setData, _afterWrite),
  628. add: _afterPromise(add, _afterWrite),
  629. remove: _afterPromise(remove, _afterWrite),
  630. setPredicateTarget_id: _afterPromise(
  631. setPredicateTarget_id,
  632. _afterWrite
  633. ),
  634. setPredicateOperator_id: _afterPromise(
  635. setPredicateOperator_id,
  636. _afterWrite
  637. ),
  638. setPredicateLogicalType_id: _afterPromise(
  639. setPredicateLogicalType_id,
  640. _afterWrite
  641. ),
  642. setArgumentValue: _afterPromise(setArgumentValue, _afterWrite),
  643. getArgumentTypeComponentById,
  644. /**
  645. * Enumeration of overridable core ui-predicate component
  646. * @enum {String}
  647. */
  648. UITypes,
  649. /**
  650. * Get core UI component (e.g. target selector)
  651. * @param {core.ui} ui component name
  652. * @return {Object} component
  653. * @memberof core.api
  654. */
  655. getUIComponent,
  656. toJSON,
  657. /**
  658. * Get root CompoundPredicate
  659. * @return {dataclasses.CompoundPredicate} root CompoundPredicate
  660. * @memberof core.api
  661. */
  662. get root() {
  663. return _root;
  664. },
  665. // used for testing
  666. get columns() {
  667. return _columns;
  668. },
  669. // used for testing
  670. get options() {
  671. return _options;
  672. },
  673. };
  674. return _api;
  675. })
  676. );
  677. });
  678. }
  679. /**
  680. * Defaults configuration of PredicateCore
  681. * @type {Object}
  682. * @namespace core.defaults
  683. */
  684. PredicateCore.defaults = {
  685. /**
  686. * Defaults options of PredicateCore
  687. * @type {Object}
  688. * @namespace core.defaults.options
  689. */
  690. options: {
  691. /**
  692. * When data is not set at construction time PredicateCore default behavior will be to use the first target and its first operator with empty argument
  693. * @param {Object} columns every necessary data class
  694. * @param {Object} options PredicateCore available options
  695. * @return {Promise<dataclasses.CompoundPredicate>} root CompoundPredicate
  696. * @since 1.0.0
  697. * @memberof core.defaults.options
  698. */
  699. getDefaultData(columns, options) {
  700. return options
  701. .getDefaultComparisonPredicate(columns, options)
  702. .then(comparisonPredicate => {
  703. return options.getDefaultCompoundPredicate(columns, options, [
  704. comparisonPredicate,
  705. ]);
  706. });
  707. },
  708. /**
  709. * Default compount predicate to use
  710. *
  711. * This function is called whenever a new CompoundPredicate is added to the UIPredicate
  712. * @param {Object} columns specified columns
  713. * @param {Object} options PredicateCore available options
  714. * @param {Array<dataclasses.Predicate>} predicates array of predicates to include into the CompoundPredicate
  715. * @return {Promise<dataclasses.CompoundPredicate>} a CompoundPredicate
  716. * @since 1.0.0
  717. * @memberof core.defaults.options
  718. */
  719. getDefaultCompoundPredicate(columns, options, predicates) {
  720. return (!Array.isArray(predicates) || predicates.length === 0
  721. ? options
  722. .getDefaultComparisonPredicate(columns, options)
  723. .then(comparisonPredicate => [comparisonPredicate])
  724. : Promise.resolve(predicates)
  725. ).then(predicates =>
  726. options
  727. .getDefaultLogicalType(predicates, columns, options)
  728. .then(logicalType => CompoundPredicate(logicalType, predicates))
  729. );
  730. },
  731. /**
  732. * Default comparison predicate to use
  733. *
  734. * This function is called whenever a new ComparisonPredicate is added to the UIPredicate
  735. * @param {Object} columns specified columns
  736. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  737. * @return {Promise<dataclasses.ComparisonPredicate>} a Comparison
  738. * @since 1.0.0
  739. * @memberof core.defaults.options
  740. */
  741. getDefaultComparisonPredicate(columns, options) {
  742. const firstTarget = head(columns.targets);
  743. return ComparisonPredicate(
  744. firstTarget,
  745. head(firstTarget.$type.$operators)
  746. );
  747. },
  748. /**
  749. * Default logical type to use when a new comparison predicate is created
  750. *
  751. * This function is called whenever a new ComparisonPredicate is added to the UIPredicate
  752. * @param {Array<dataclasses.Predicate>} predicates specified columns
  753. * @param {Object} columns specified columns
  754. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  755. * @return {Promise<dataclasses.LogicalType>} a logical type
  756. * @since 1.0.0
  757. * @memberof core.defaults.options
  758. */
  759. getDefaultLogicalType(predicates, columns, options) {
  760. return Promise.resolve(head(columns.logicalTypes));
  761. },
  762. /**
  763. * Get the default UI component for any argument. Also used if no registered UI component match `argumentType_id`
  764. * @param {Object} columns specified columns
  765. * @param {Object} [options=PredicateCore.defaults.options] PredicateCore available options
  766. * @return {*} yield a UI Component (depending on the UI Framework used)
  767. * @throws if the UI Framework adapter did not override this function. Each UI Framework adapter (e.g. ui-predicate-vue, ui-predicate-react, ...) must implement this and let user override it
  768. * @memberof core.defaults.options
  769. */
  770. getDefaultArgumentComponent(columns, options) {
  771. throw new errors.UIFrameworkMustImplementgetDefaultArgumentComponent(
  772. 'UIFrameworkMustImplementgetDefaultArgumentComponent'
  773. );
  774. },
  775. },
  776. };
  777. return PredicateCore;
  778. };