Source: components/criteria_builder/CriteriaBuilder.es.js

  1. /**
  2. * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
  3. *
  4. * This library is free software; you can redistribute it and/or modify it under
  5. * the terms of the GNU Lesser General Public License as published by the Free
  6. * Software Foundation; either version 2.1 of the License, or (at your option)
  7. * any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful, but WITHOUT
  10. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  12. * details.
  13. */
  14. import PropTypes from 'prop-types';
  15. import React, {Component} from 'react';
  16. import {
  17. conjunctionShape,
  18. criteriaShape,
  19. propertyShape,
  20. propertyTypesShape,
  21. } from '../../utils/types.es';
  22. import {
  23. insertAtIndex,
  24. removeAtIndex,
  25. replaceAtIndex,
  26. sub,
  27. } from '../../utils/utils.es';
  28. import CriteriaGroup from './CriteriaGroup.es';
  29. class CriteriaBuilder extends Component {
  30. static propTypes = {
  31. criteria: criteriaShape,
  32. editing: PropTypes.bool.isRequired,
  33. emptyContributors: PropTypes.bool.isRequired,
  34. /**
  35. * Name of the entity that a set of properties belongs to, for example,
  36. * "User". This value it not displayed anywhere. Only used in
  37. * CriteriaRow for requesting a field value's name.
  38. * @default undefined
  39. * @type {?(string|undefined)}
  40. */
  41. entityName: PropTypes.string.isRequired,
  42. /**
  43. * Name displayed to label a contributor and its' properties.
  44. * @default undefined
  45. * @type {?(string|undefined)}
  46. */
  47. modelLabel: PropTypes.string,
  48. onChange: PropTypes.func,
  49. propertyKey: PropTypes.string.isRequired,
  50. supportedConjunctions: PropTypes.arrayOf(conjunctionShape),
  51. supportedOperators: PropTypes.array,
  52. supportedProperties: PropTypes.arrayOf(propertyShape).isRequired,
  53. supportedPropertyTypes: propertyTypesShape,
  54. };
  55. /**
  56. * Cleans criteria items by performing the following:
  57. * 1. Remove any groups with no items.
  58. * 2. Flatten groups that directly contain a single group.
  59. * 3. Flatten groups that contain a single criterion.
  60. * @param {Array} criteriaItems A list of criterion and criteria groups
  61. * @param {boolean} root True if the criteriaItems are from the root group.
  62. * to clean.
  63. * @returns {*}
  64. */
  65. _cleanCriteriaMapItems(criteriaItems, root) {
  66. const criteria = criteriaItems
  67. .filter(({items}) => {
  68. return items ? items.length : true;
  69. })
  70. .map((item) => {
  71. let cleanedItem = item;
  72. if (item.items) {
  73. if (item.items.length === 1) {
  74. const soloItem = item.items[0];
  75. if (soloItem.items) {
  76. cleanedItem = {
  77. conjunctionName: soloItem.conjunctionName,
  78. groupId: soloItem.groupId,
  79. items: this._cleanCriteriaMapItems(
  80. soloItem.items
  81. ),
  82. };
  83. }
  84. else {
  85. cleanedItem = root ? item : soloItem;
  86. }
  87. }
  88. else {
  89. cleanedItem = {
  90. ...item,
  91. items: this._cleanCriteriaMapItems(item.items),
  92. };
  93. }
  94. }
  95. return cleanedItem;
  96. });
  97. return criteria;
  98. }
  99. /**
  100. * Cleans and updates the criteria with the newer criteria.
  101. * @param {Object} newCriteria The criteria with the most recent changes.
  102. */
  103. _handleCriteriaChange = (newCriteria) => {
  104. const items = this._cleanCriteriaMapItems([newCriteria], true);
  105. this.props.onChange(items[items.length - 1], this.props.propertyKey);
  106. };
  107. /**
  108. * Moves the criterion to the specified index by removing and adding, and
  109. * updates the criteria.
  110. * @param {string} startGroupId Group ID of the item to remove.
  111. * @param {number} startIndex Index in the group to remove.
  112. * @param {string} destGroupId Group ID of the item to add.
  113. * @param {number} destIndex Index in the group where the criterion will
  114. * be added.
  115. * @param {object} criterion The criterion that is being moved.
  116. * @param {boolean} replace True if the destIndex should replace rather than
  117. * insert.
  118. */
  119. _handleCriterionMove = (...args) => {
  120. const newCriteria = this._searchAndUpdateCriteria(
  121. this.props.criteria,
  122. ...args
  123. );
  124. this._handleCriteriaChange(newCriteria);
  125. };
  126. /**
  127. * Checks if an item is a group item by checking if it contains an items
  128. * property with at least 1 item.
  129. * @param {object} item The criteria item to check.
  130. * @returns True if the item is a group.
  131. */
  132. _isGroupItem(item) {
  133. return item.items && item.items.length;
  134. }
  135. /**
  136. * Searches through the criteria object and adds or replaces and removes
  137. * the criterion at their respective specified index. insertAtIndex must
  138. * come before removeAtIndex since the startIndex is incremented by 1
  139. * when the destination index comes before the start index in the same
  140. * group. The startIndex is not incremented if a replace is occurring.
  141. * This is used for moving a criterion between groups.
  142. * @param {object} criteria The criteria object to update.
  143. * @param {string} startGroupId Group ID of the item to remove.
  144. * @param {number} startIndex Index in the group to remove.
  145. * @param {string} destGroupId Group ID of the item to add.
  146. * @param {number} destIndex Index in the group where the criterion will
  147. * be added.
  148. * @param {object} addCriterion The criterion that is being moved.
  149. * @param {boolean} replace True if the destIndex should replace rather than
  150. * insert.
  151. */
  152. _searchAndUpdateCriteria = (
  153. criteria,
  154. startGroupId,
  155. startIndex,
  156. destGroupId,
  157. destIndex,
  158. addCriterion,
  159. replace
  160. ) => {
  161. let updatedCriteriaItems = criteria.items;
  162. if (criteria.groupId === destGroupId) {
  163. updatedCriteriaItems = replace
  164. ? replaceAtIndex(addCriterion, updatedCriteriaItems, destIndex)
  165. : insertAtIndex(addCriterion, updatedCriteriaItems, destIndex);
  166. }
  167. if (criteria.groupId === startGroupId) {
  168. updatedCriteriaItems = removeAtIndex(
  169. updatedCriteriaItems,
  170. destGroupId === startGroupId &&
  171. destIndex < startIndex &&
  172. !replace
  173. ? startIndex + 1
  174. : startIndex
  175. );
  176. }
  177. return {
  178. ...criteria,
  179. items: updatedCriteriaItems.map((item) => {
  180. return this._isGroupItem(item)
  181. ? this._searchAndUpdateCriteria(
  182. item,
  183. startGroupId,
  184. startIndex,
  185. destGroupId,
  186. destIndex,
  187. addCriterion,
  188. replace
  189. )
  190. : item;
  191. }),
  192. };
  193. };
  194. render() {
  195. const {
  196. criteria,
  197. editing,
  198. emptyContributors,
  199. entityName,
  200. modelLabel,
  201. propertyKey,
  202. supportedConjunctions,
  203. supportedOperators,
  204. supportedProperties,
  205. supportedPropertyTypes,
  206. } = this.props;
  207. return (
  208. <div className="criteria-builder-root">
  209. <h4 className="sheet-subtitle">
  210. {sub(
  211. Liferay.Language.get('x-with-property-x'),
  212. [modelLabel, ''],
  213. false
  214. )}
  215. </h4>
  216. {(!emptyContributors || editing) && (
  217. <CriteriaGroup
  218. criteria={criteria}
  219. editing={editing}
  220. emptyContributors={emptyContributors}
  221. entityName={entityName}
  222. groupId={criteria && criteria.groupId}
  223. modelLabel={modelLabel}
  224. onChange={this._handleCriteriaChange}
  225. onMove={this._handleCriterionMove}
  226. propertyKey={propertyKey}
  227. root
  228. supportedConjunctions={supportedConjunctions}
  229. supportedOperators={supportedOperators}
  230. supportedProperties={supportedProperties}
  231. supportedPropertyTypes={supportedPropertyTypes}
  232. />
  233. )}
  234. </div>
  235. );
  236. }
  237. }
  238. export default CriteriaBuilder;