SearchKit - Fix reloading a search with an OR group in the WHERE clause
[civicrm-core.git] / ext / search_kit / ang / crmSearchAdmin / crmSearchClause.component.js
1 (function(angular, $, _) {
2 "use strict";
3
4 angular.module('crmSearchAdmin').component('crmSearchClause', {
5 bindings: {
6 fields: '<',
7 clauses: '<',
8 format: '@',
9 op: '@',
10 skip: '<',
11 label: '@',
12 hideLabel: '@',
13 placeholder: '<',
14 deleteGroup: '&'
15 },
16 templateUrl: '~/crmSearchAdmin/crmSearchClause.html',
17 controller: function ($scope, $element, $timeout, searchMeta) {
18 var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
19 ctrl = this,
20 meta = {};
21 this.conjunctions = {AND: ts('And'), OR: ts('Or'), NOT: ts('Not')};
22 this.operators = {};
23 this.sortOptions = {
24 axis: 'y',
25 connectWith: '.api4-clause-group-sortable',
26 containment: $element.closest('.api4-clause-fieldset'),
27 over: onSortOver,
28 start: onSort,
29 stop: onSort
30 };
31
32 this.$onInit = function() {
33 ctrl.hasParent = !!$element.attr('delete-group');
34 _.each(ctrl.clauses, updateOperators);
35 };
36
37 // Return a list of operators allowed for the field in a given clause
38 this.getOperators = function(clause) {
39 var field = ctrl.getField(clause[0]);
40 if (!field || !field.operators) {
41 return CRM.crmSearchAdmin.operators;
42 }
43 var opKey = field.operators.join();
44 if (!ctrl.operators[opKey]) {
45 ctrl.operators[opKey] = _.filter(CRM.crmSearchAdmin.operators, function(operator) {
46 return _.includes(field.operators, operator.key);
47 });
48 }
49 return ctrl.operators[opKey];
50 };
51
52 // Ensures a clause is using an operator that is allowed for the field
53 function updateOperators(clause) {
54 // Recurse into AND/OR/NOT groups
55 if (ctrl.conjunctions[clause[0]]) {
56 _.each(clause[1], updateOperators);
57 }
58 else if (!ctrl.skip && (!clause[1] || !_.includes(_.pluck(ctrl.getOperators(clause), 'key'), clause[1]))) {
59 clause[1] = ctrl.getOperators(clause)[0].key;
60 ctrl.changeClauseOperator(clause);
61 }
62 }
63
64 this.getField = function(expr) {
65 if (!meta[expr]) {
66 meta[expr] = searchMeta.parseExpr(expr);
67 }
68 return meta[expr].field;
69 };
70
71 this.getOptionKey = function(expr) {
72 if (!meta[expr]) {
73 meta[expr] = searchMeta.parseExpr(expr);
74 }
75 return meta[expr].suffix ? meta[expr].suffix.slice(1) : 'id';
76 };
77
78 this.addGroup = function(op) {
79 ctrl.clauses.push([op, []]);
80 };
81
82 function onSort(event, ui) {
83 $($element).closest('.api4-clause-fieldset').toggleClass('api4-sorting', event.type === 'sortstart');
84 $('.api4-input.form-inline').css('margin-left', '');
85 }
86
87 // Indent clause while dragging between nested groups
88 function onSortOver(event, ui) {
89 var offset = 0;
90 if (ui.sender) {
91 offset = $(ui.placeholder).offset().left - $(ui.sender).offset().left;
92 }
93 $('.api4-input.form-inline.ui-sortable-helper').css('margin-left', '' + offset + 'px');
94 }
95
96 this.addClause = function() {
97 $timeout(function() {
98 if (ctrl.newClause) {
99 var newIndex = ctrl.clauses.length;
100 ctrl.clauses.push([ctrl.newClause, '=', '']);
101 ctrl.newClause = null;
102 updateOperators(ctrl.clauses[newIndex]);
103 }
104 });
105 };
106
107 this.deleteRow = function(index) {
108 ctrl.clauses.splice(index, 1);
109 };
110
111 // Remove empty values
112 this.changeClauseField = function(clause, index) {
113 if (clause[0] === '') {
114 ctrl.deleteRow(index);
115 } else {
116 updateOperators(clause);
117 }
118 };
119
120 // Returns false for 'IS NULL', 'IS EMPTY', etc. true otherwise.
121 this.operatorTakesInput = function(operator) {
122 return operator.indexOf('IS ') !== 0;
123 };
124
125 this.changeClauseOperator = function(clause) {
126 // Add/remove value depending on whether operator allows for one
127 if (!ctrl.operatorTakesInput(clause[1])) {
128 clause.length = 2;
129 } else {
130 if (clause.length === 2) {
131 clause.push('');
132 }
133 // Change multi/single value to/from an array
134 var shouldBeArray = (clause[1] === 'IN' || clause[1] === 'NOT IN' || clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN');
135 if (!_.isArray(clause[2]) && shouldBeArray) {
136 clause[2] = [];
137 } else if (_.isArray(clause[2]) && !shouldBeArray) {
138 clause[2] = '';
139 }
140 if (clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN') {
141 clause[2].length = 2;
142 }
143 }
144 };
145
146 }
147 });
148
149 })(angular, CRM.$, CRM._);