Afform - Allow selecting search operator for filter fields
authorColeman Watts <coleman@civicrm.org>
Sat, 15 Apr 2023 19:21:23 +0000 (15:21 -0400)
committerColeman Watts <coleman@civicrm.org>
Sun, 16 Apr 2023 15:04:23 +0000 (11:04 -0400)
ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js
ext/afform/core/ang/af/afField.component.js
ext/afform/core/ang/af/fields/ChainSelect.html
ext/afform/core/ang/af/fields/CheckBox.html
ext/afform/core/ang/af/fields/Date.html
ext/afform/core/ang/af/fields/Number.html
ext/afform/core/ang/af/fields/Radio.html
ext/afform/core/ang/af/fields/RichTextEditor.html
ext/afform/core/ang/af/fields/Text.html
ext/afform/core/ang/af/fields/TextArea.html

index 668ec720b752203683936749047c1f69aa9876a9..2ab4c16dfdcf65198a851dfde760f8380eb437a0 100644 (file)
     {{:: ts('Search by range') }}
   </a>
 </li>
+<li ng-if="$ctrl.isSearch()">
+  <div href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
+    <label>{{:: ts('Operator:') }}</label>
+    <select class="form-control" ng-model="getSet('search_operator')" ng-model-options="{getterSetter: true}" title="{{:: ts('Field type') }}">
+      <option ng-repeat="(name, label) in $ctrl.searchOperators" value="{{ name }}">{{ label }}</option>
+    </select>
+  </div>
+</li>
 <li role="separator" class="divider" ng-if="hasOptions()"></li>
 <li ng-if="hasOptions()" ng-click="$event.stopPropagation()">
   <a href ng-click="resetOptions()" title="{{:: ts('Reset the option list for this field') }}">
index 66136f86f6574d446b0bff755fe57bea057f9db7..d5fa1149a1c4158d7c46c5f9385bfdfe30928709 100644 (file)
         $scope.editingOptions = val;
       };
 
+      this.searchOperators = {
+        '': ts('Auto'),
+        '=': '=',
+        '!=': '≠',
+        '>': '>',
+        '<': '<',
+        '>=': '≥',
+        '<=': '≤',
+        'CONTAINS': ts('Contains'),
+        'IN': ts('Is One Of'),
+        'NOT IN': ts('Not One Of'),
+        'LIKE': ts('Is Like'),
+        'NOT LIKE': ts('Not Like'),
+        'REGEXP': ts('Matches Pattern'),
+        'NOT REGEXP': ts("Doesn't Match Pattern"),
+      };
+
       // Returns a reference to a path n-levels deep within an object
       function drillDown(parent, path) {
         var container = parent;
index 9e7ac0e0c848654455dbefd833c8f968a414521a..1e6173d58a5c6e3c2fc16b3747c0346d68aaaded 100644 (file)
         ) {
           value =  value.split(',');
         }
-        $scope.dataProvider.getFieldData()[ctrl.fieldName] = value;
+        $scope.getSetValue(value);
       }
 
       // Get the repeat index of the entity fieldset (not the join)
         };
       };
 
+      // Getter/Setter function for most fields (except select & entityRef)
+      $scope.getSetValue = function(val) {
+        var currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
+        // Setter
+        if (arguments.length) {
+          if (ctrl.defn.search_operator) {
+            if (typeof currentVal !== 'object') {
+              $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
+            }
+            return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.defn.search_operator] = val);
+          }
+          return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
+        }
+        // Getter
+        if (ctrl.defn.search_operator) {
+          return (currentVal || {})[ctrl.defn.search_operator];
+        }
+        return currentVal;
+      };
+
       // Getter/Setter function for fields of type select or entityRef.
       $scope.getSetSelect = function(val) {
         var currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
           else if (ctrl.defn.search_range) {
             return ($scope.dataProvider.getFieldData()[ctrl.fieldName]['>='] = val);
           }
+          else if (ctrl.defn.search_operator) {
+            if (typeof currentVal !== 'object') {
+              $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
+            }
+            return ($scope.dataProvider.getFieldData()[ctrl.fieldName][ctrl.defn.search_operator] = val);
+          }
           return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
         }
         // Getter
         else if (ctrl.defn.search_range) {
           return currentVal['>='];
         }
+        else if (ctrl.defn.search_operator) {
+          return (currentVal || {})[ctrl.defn.search_operator];
+        }
         return currentVal;
       };
 
index ed756897ad8d8a359e217cd2cb055321bd5e3a74..32607f1b8c84e8f78c2ba911d5bc9372b63f592a 100644 (file)
@@ -1 +1 @@
-<input class="form-control" ng-required="$ctrl.defn.required" crm-ui-select="{data: select2Options, multiple: $ctrl.defn.input_attrs.multiple, placeholder: $ctrl.defn.input_attrs.placeholder}" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" />
+<input class="form-control" ng-required="$ctrl.defn.required" crm-ui-select="{data: select2Options, multiple: $ctrl.defn.input_attrs.multiple, placeholder: $ctrl.defn.input_attrs.placeholder}" id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" />
index 169baa2599b73cd6db287e72c7954f505f7ac1f2..ba9b8aac933631fa0c69935d6133213b4db39cda 100644 (file)
@@ -4,4 +4,4 @@
     <label for="{{ fieldId + opt.id }}">{{:: opt.label }}</label>
   </li>
 </ul>
-<input type="checkbox" ng-required="$ctrl.defn.required" ng-if="!$ctrl.defn.options" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" />
+<input type="checkbox" ng-required="$ctrl.defn.required" ng-if="!$ctrl.defn.options" id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" />
index 2722191d28815fbf2a1adafb0f5ae29b2b62d8f9..bb34e2df84b8681e4a56be23ded7d6166b72b1f6 100644 (file)
@@ -1,4 +1,4 @@
-<input ng-if=":: !$ctrl.defn.search_range" class="form-control" crm-ui-datepicker=":: $ctrl.defn.input_attrs" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" />
+<input ng-if=":: !$ctrl.defn.search_range" class="form-control" crm-ui-datepicker=":: $ctrl.defn.input_attrs" id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" />
 <div ng-if=":: $ctrl.defn.search_range" class="form-inline">
   <input class="form-control" ng-required="$ctrl.defn.required" crm-ui-datepicker=":: $ctrl.inputAttrs[1]" id="{{:: fieldId }}1" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]['>=']" />
   <span class="af-field-range-sep">-</span>
index 3980ec65a8cb6cde94cc8aa999c2d6751217b546..754b4abedea82d07cad7819f60b6c6e1f381c97e 100644 (file)
@@ -1,4 +1,4 @@
-<input ng-if=":: !$ctrl.defn.search_range" class="form-control" ng-required="$ctrl.defn.required" type="number" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
+<input ng-if=":: !$ctrl.defn.search_range" class="form-control" ng-required="$ctrl.defn.required" type="number" id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
 <div ng-if=":: $ctrl.defn.search_range" class="form-inline">
   <input class="form-control" type="number" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]['>=']" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
   <span class="af-field-range-sep">-</span>
index bd50c6d88df68bda3a776182d75f52c197830fad..189fa309fd667d9233b8adeebb92ed65cb04ff42 100644 (file)
@@ -1,5 +1,5 @@
 <label ng-repeat="opt in getOptions() track by opt.id" >
-  <input class="crm-form-radio" type="radio" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" ng-value="opt.id" />
+  <input class="crm-form-radio" type="radio" ng-model="getSetValue" ng-model-options="{getterSetter: true}" ng-value="opt.id" />
   {{:: opt.label }}
 </label>
 <a ng-if="!$ctrl.defn.required" class="crm-hover-button" title="{{:: ts('Clear') }}" ng-show="!!dataProvider.getFieldData()[$ctrl.fieldName] || dataProvider.getFieldData()[$ctrl.fieldName] === false || dataProvider.getFieldData()[$ctrl.fieldName] === 0" ng-click="dataProvider.getFieldData()[$ctrl.fieldName] = null">
index af4d4aabaa86befbbe7c96fda39bba6550618aba..f8ea177b539b4e1d2f195507c6c105ed3843533c 100644 (file)
@@ -1 +1 @@
-<textarea crm-ui-richtext id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" ></textarea>
+<textarea crm-ui-richtext id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" ></textarea>
index fe9c3cf138f8e501a98bd78acd7889ae6baa9a1b..6b8755f0d141daa837016b3e5ff4a3f46273211e 100644 (file)
@@ -1,4 +1,4 @@
-<input ng-if=":: !$ctrl.defn.search_range" class="form-control" type="text" ng-required="$ctrl.defn.required" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
+<input ng-if=":: !$ctrl.defn.search_range" class="form-control" type="text" ng-required="$ctrl.defn.required" id="{{:: fieldId }}" ng-model="getSetValue" ng-model-options="{getterSetter: true}" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
 <div ng-if=":: $ctrl.defn.search_range" class="form-inline">
   <input class="form-control" type="text" id="{{:: fieldId }}" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]['>=']" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
   <span class="af-field-range-sep">-</span>
index 3d10e3acbf3457f98b434762a256ed1c39e9c3d7..cfb9942fd0d86c198a916d32b4c0d9168ca9177a 100644 (file)
@@ -1 +1 @@
-<textarea class="crm-form-textarea" id="{{:: fieldId }}" ng-required="$ctrl.defn.required" ng-model="dataProvider.getFieldData()[$ctrl.fieldName]" ></textarea>
+<textarea class="crm-form-textarea" id="{{:: fieldId }}" ng-required="$ctrl.defn.required" ng-model="getSetValue" ng-model-options="{getterSetter: true}" ></textarea>