From aa0c560270faef4aba459bda712f2f93f6e80256 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sat, 22 Jan 2022 12:01:11 -0500 Subject: [PATCH] SearchKit - Improve field/operator/value selection UI Carves out yet another component to handle the logic of deciding which operators are appropriate to which fields, and how the input values should be rendered. --- .../crmSearchAdminLinkGroup.component.js | 9 +- .../crmSearchAdminLinkGroup.html | 13 ++- .../crmSearchClause.component.js | 60 +----------- .../ang/crmSearchAdmin/crmSearchClause.html | 3 +- .../crmSearchCondition.component.js | 91 +++++++++++++++++++ .../crmSearchAdmin/crmSearchCondition.html | 2 + .../common/searchAdminCssRules.component.js | 1 - .../displays/common/searchAdminCssRules.html | 4 +- .../crmSearchInput.component.js | 10 -- .../crmSearchInput/crmSearchInput.html | 6 +- .../crmSearchInputVal.component.js | 18 +++- 11 files changed, 124 insertions(+), 93 deletions(-) create mode 100644 ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.component.js create mode 100644 ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.html diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js index 524eb5f6b5..4956b302de 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js @@ -17,18 +17,11 @@ ctrl = this, linkProps = ['path', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style', 'condition']; - var permissionOperators = [ + ctrl.permissionOperators = [ {key: '=', value: ts('Has')}, {key: '!=', value: ts('Lacks')} ]; - this.getOperators = function(clause) { - if (clause[0] === 'check user permission') { - return permissionOperators; - } - return CRM.crmSearchAdmin.operators; - }; - this.styles = CRM.crmSearchAdmin.styles; this.getStyle = function(item) { diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html index 86af85b404..51d0d2a47c 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html @@ -37,14 +37,17 @@ -
+
-
- -
+ +
diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.component.js index 8b97626693..cd235a7312 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.component.js @@ -15,12 +15,11 @@ deleteGroup: '&' }, templateUrl: '~/crmSearchAdmin/crmSearchClause.html', - controller: function ($scope, $element, $timeout, searchMeta) { + controller: function ($scope, $element, searchMeta) { var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), ctrl = this, meta = {}; this.conjunctions = {AND: ts('And'), OR: ts('Or'), NOT: ts('Not')}; - this.operators = {}; this.sortOptions = { axis: 'y', connectWith: '.api4-clause-group-sortable', @@ -32,36 +31,8 @@ this.$onInit = function() { ctrl.hasParent = !!$element.attr('delete-group'); - _.each(ctrl.clauses, updateOperators); }; - // Return a list of operators allowed for the field in a given clause - this.getOperators = function(clause) { - var field = ctrl.getField(clause[0]); - if (!field || !field.operators) { - return CRM.crmSearchAdmin.operators; - } - var opKey = field.operators.join(); - if (!ctrl.operators[opKey]) { - ctrl.operators[opKey] = _.filter(CRM.crmSearchAdmin.operators, function(operator) { - return _.includes(field.operators, operator.key); - }); - } - return ctrl.operators[opKey]; - }; - - // Ensures a clause is using an operator that is allowed for the field - function updateOperators(clause) { - // Recurse into AND/OR/NOT groups - if (ctrl.conjunctions[clause[0]]) { - _.each(clause[1], updateOperators); - } - else if (!ctrl.skip && (!clause[1] || !_.includes(_.pluck(ctrl.getOperators(clause), 'key'), clause[1]))) { - clause[1] = ctrl.getOperators(clause)[0].key; - ctrl.changeClauseOperator(clause); - } - } - // Gets the first arg of type "field" function getFirstArgFromExpr(expr) { if (!(expr in meta)) { @@ -112,7 +83,6 @@ if (value) { var newIndex = ctrl.clauses.length; ctrl.clauses.push([value, '=', '']); - updateOperators(ctrl.clauses[newIndex]); } }; @@ -124,34 +94,6 @@ this.changeClauseField = function(clause, index) { if (clause[0] === '') { ctrl.deleteRow(index); - } else { - updateOperators(clause); - } - }; - - // Returns false for 'IS NULL', 'IS EMPTY', etc. true otherwise. - this.operatorTakesInput = function(operator) { - return operator.indexOf('IS ') !== 0; - }; - - this.changeClauseOperator = function(clause) { - // Add/remove value depending on whether operator allows for one - if (!ctrl.operatorTakesInput(clause[1])) { - clause.length = 2; - } else { - if (clause.length === 2) { - clause.push(''); - } - // Change multi/single value to/from an array - var shouldBeArray = (clause[1] === 'IN' || clause[1] === 'NOT IN' || clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN'); - if (!_.isArray(clause[2]) && shouldBeArray) { - clause[2] = []; - } else if (_.isArray(clause[2]) && !shouldBeArray) { - clause[2] = ''; - } - if (clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN') { - clause[2].length = 2; - } } }; diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.html index 9cc00440cf..b5a3ef25fb 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchClause.html @@ -19,8 +19,7 @@ - - +
diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.component.js new file mode 100644 index 0000000000..c30f72e7e7 --- /dev/null +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.component.js @@ -0,0 +1,91 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('crmSearchAdmin').component('crmSearchCondition', { + bindings: { + field: '<', + clause: '<', + format: '<', + optionKey: '<', + offset: '@' + }, + templateUrl: '~/crmSearchAdmin/crmSearchCondition.html', + controller: function ($scope, $element, searchMeta) { + var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), + ctrl = this; + this.operators = {}; + + this.$onInit = function() { + $scope.$watch('$ctrl.field', updateOperators); + }; + + function getOperator() { + return ctrl.clause[ctrl.offset]; + } + + function setOperator(op) { + if (op !== getOperator()) { + ctrl.clause[ctrl.offset] = op; + ctrl.changeClauseOperator(); + } + } + + // Return a list of operators allowed for the current field + this.getOperators = function() { + var field = ctrl.field || {}, + allowedOps = field.operators; + if (!allowedOps && field.data_type === 'Boolean') { + allowedOps = ['=', '!=', 'IS EMPTY', 'IS NOT EMPTY']; + } + if (!allowedOps && _.includes(['Boolean', 'Float', 'Date'], field.data_type)) { + allowedOps = ['=', '!=', '<', '>', '<=', '>=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'IS EMPTY', 'IS NOT EMPTY']; + } + if (!allowedOps) { + return CRM.crmSearchAdmin.operators; + } + var opKey = allowedOps.join(); + if (!ctrl.operators[opKey]) { + ctrl.operators[opKey] = _.filter(CRM.crmSearchAdmin.operators, function(operator) { + return _.includes(allowedOps, operator.key); + }); + } + return ctrl.operators[opKey]; + }; + + // Ensures clause is using an operator that is allowed for the field + function updateOperators() { + if ((!getOperator() || !_.includes(_.pluck(ctrl.getOperators(), 'key'), getOperator()))) { + setOperator(ctrl.getOperators()[0].key); + } + } + + // Returns false for 'IS NULL', 'IS EMPTY', etc. true otherwise. + this.operatorTakesInput = function() { + return getOperator().indexOf('IS ') !== 0; + }; + + this.changeClauseOperator = function() { + // Add/remove value depending on whether operator allows for one + if (!ctrl.operatorTakesInput()) { + ctrl.clause.length = ctrl.offset + 1; + } else { + if (ctrl.clause.length === ctrl.offset + 1) { + ctrl.clause.push(''); + } + // Change multi/single value to/from an array + var shouldBeArray = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], getOperator()); + if (!_.isArray(ctrl.clause[ctrl.offset + 1]) && shouldBeArray) { + ctrl.clause[ctrl.offset + 1] = []; + } else if (_.isArray(ctrl.clause[ctrl.offset + 1]) && !shouldBeArray) { + ctrl.clause[ctrl.offset + 1] = ''; + } + if (_.includes(['BETWEEN', 'NOT BETWEEN'], getOperator())) { + ctrl.clause[ctrl.offset + 1].length = 2; + } + } + }; + + } + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.html new file mode 100644 index 0000000000..e58bac413c --- /dev/null +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchCondition.html @@ -0,0 +1,2 @@ + + diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.component.js b/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.component.js index 40163abb7d..3111e4a8b5 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.component.js @@ -64,7 +64,6 @@ return !this.item.cssRules || !this.item.cssRules.length || _.last(this.item.cssRules)[1]; }; - this.operators = CRM.crmSearchAdmin.operators; } }); diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.html b/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.html index 6b6d573bbe..662ebfa16a 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.html +++ b/ext/search_kit/ang/crmSearchAdmin/displays/common/searchAdminCssRules.html @@ -22,9 +22,7 @@ - - + diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.component.js b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.component.js index b3bce0409d..9267fc09dd 100644 --- a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.component.js +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.component.js @@ -14,16 +14,6 @@ var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), ctrl = this; - this.isMulti = function() { - // If there's a search operator, return `true` if the operator takes multiple values, else `false` - if (ctrl.op) { - return ctrl.op === 'IN' || ctrl.op === 'NOT IN'; - } - // If no search operator this is an input for e.g. the bulk update action - // Return `true` if the field is multi-valued, else `null` - return ctrl.field && (ctrl.field.serialize || ctrl.field.data_type === 'Array') ? true : null; - }; - this.$onInit = function() { $scope.$watch('$ctrl.value', function() { diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.html b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.html index bf434d9cf4..3f3a110dfb 100644 --- a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.html +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInput.html @@ -1,13 +1,13 @@
- + - - +
- +
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInputVal.component.js b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInputVal.component.js index 99205c45c8..2c9ac721c5 100644 --- a/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInputVal.component.js +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchInput/crmSearchInputVal.component.js @@ -4,7 +4,7 @@ angular.module('crmSearchTasks').component('crmSearchInputVal', { bindings: { field: '<', - 'multi': '<', + 'op': '<', 'optionKey': '<' }, require: {ngModel: 'ngModel'}, @@ -46,6 +46,16 @@ } }; + this.isMulti = function() { + // If there's a search operator, return `true` if the operator takes multiple values, else `false` + if (ctrl.op) { + return ctrl.op === 'IN' || ctrl.op === 'NOT IN'; + } + // If no search operator this is an input for e.g. the bulk update action + // Return `true` if the field is multi-valued, else `null` + return ctrl.field && (ctrl.field.serialize || ctrl.field.data_type === 'Array') ? true : null; + }; + this.changeDateType = function() { switch (ctrl.dateType) { case 'fixed': @@ -88,6 +98,10 @@ this.getTemplate = function() { var field = ctrl.field || {}; + if (_.includes(['LIKE', 'NOT LIKE', 'REGEXP', 'NOT REGEXP'], ctrl.op)) { + return '~/crmSearchTasks/crmSearchInput/text.html'; + } + if (field.input_type === 'Date') { return '~/crmSearchTasks/crmSearchInput/date.html'; } @@ -100,7 +114,7 @@ return '~/crmSearchTasks/crmSearchInput/select.html'; } - if (field.fk_entity || field.name === 'id') { + if ((field.fk_entity || field.name === 'id') && !_.includes(['>', '<', '>=', '<='], ctrl.op)) { return '~/crmSearchTasks/crmSearchInput/entityRef.html'; } -- 2.25.1