SearchKit - Expose default display to the UI
[civicrm-core.git] / ext / afform / admin / ang / afGuiEditor / afGuiSearch.component.js
1 // https://civicrm.org/licensing
2 (function(angular, $, _) {
3 "use strict";
4
5 angular.module('afGuiEditor').component('afGuiSearch', {
6 templateUrl: '~/afGuiEditor/afGuiSearch.html',
7 bindings: {
8 display: '<'
9 },
10 require: {editor: '^^afGuiEditor'},
11 controller: function ($scope, $timeout, afGui) {
12 var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin');
13 var ctrl = this;
14 $scope.controls = {};
15 $scope.fieldList = [];
16 $scope.calcFieldList = [];
17 $scope.blockList = [];
18 $scope.blockTitles = [];
19 $scope.elementList = [];
20 $scope.elementTitles = [];
21
22 $scope.getField = afGui.getField;
23
24 this.buildPaletteLists = function() {
25 var search = $scope.controls.fieldSearch ? $scope.controls.fieldSearch.toLowerCase() : null;
26 buildCalcFieldList(search);
27 buildFieldList(search);
28 buildBlockList(search);
29 buildElementList(search);
30 };
31
32 function buildCalcFieldList(search) {
33 $scope.calcFieldList.length = 0;
34 _.each(_.cloneDeep(ctrl.display.calc_fields), function(field) {
35 if (!search || _.contains(field.defn.label.toLowerCase(), search)) {
36 $scope.calcFieldList.push(field);
37 }
38 });
39 }
40
41 function buildBlockList(search) {
42 $scope.blockList.length = 0;
43 $scope.blockTitles.length = 0;
44 _.each(afGui.meta.blocks, function(block, directive) {
45 if (!search || _.contains(directive, search) || _.contains(block.name.toLowerCase(), search) || _.contains(block.title.toLowerCase(), search)) {
46 var item = {"#tag": directive};
47 $scope.blockList.push(item);
48 $scope.blockTitles.push(block.title);
49 }
50 });
51 }
52
53 function buildFieldList(search) {
54 $scope.fieldList.length = 0;
55 var entity = afGui.getEntity(ctrl.display['saved_search_id.api_entity']),
56 entityCount = {};
57 entityCount[entity.entity] = 1;
58 $scope.fieldList.push({
59 entityType: entity.entity,
60 label: ts('%1 Fields', {1: entity.label}),
61 fields: filterFields(entity.fields)
62 });
63
64 _.each(ctrl.display['saved_search_id.api_params'].join, function(join) {
65 var joinInfo = join[0].split(' AS '),
66 entity = afGui.getEntity(joinInfo[0]),
67 alias = joinInfo[1];
68 entityCount[entity.entity] = (entityCount[entity.entity] || 0) + 1;
69 $scope.fieldList.push({
70 entityType: entity.entity,
71 label: ts('%1 Fields', {1: entity.label + (entityCount[entity.entity] > 1 ? ' ' + entityCount[entity.entity] : '')}),
72 fields: filterFields(entity.fields, alias)
73 });
74 });
75
76 function filterFields(fields, prefix) {
77 return _.transform(fields, function(fieldList, field) {
78 if (!search || _.contains(field.name, search) || _.contains(field.label.toLowerCase(), search)) {
79 fieldList.push(fieldDefaults(field, prefix));
80 }
81 }, []);
82 }
83
84 function fieldDefaults(field, prefix) {
85 var tag = {
86 "#tag": "af-field",
87 name: (prefix ? prefix + '.' : '') + field.name
88 };
89 if (field.input_type === 'Select' || field.input_type === 'ChainSelect') {
90 tag.defn = {input_attrs: {multiple: true}};
91 } else if (field.input_type === 'Date') {
92 tag.defn = {input_type: 'Select', search_range: true};
93 } else if (field.options) {
94 tag.defn = {input_type: 'Select', input_attrs: {multiple: true}};
95 }
96 return tag;
97 }
98 }
99
100 function buildElementList(search) {
101 $scope.elementList.length = 0;
102 $scope.elementTitles.length = 0;
103 _.each(afGui.meta.elements, function(element, name) {
104 if (!search || _.contains(name, search) || _.contains(element.title.toLowerCase(), search)) {
105 var node = _.cloneDeep(element.element);
106 if (name === 'fieldset') {
107 return;
108 }
109 $scope.elementList.push(node);
110 $scope.elementTitles.push(element.title);
111 }
112 });
113 }
114
115 // This gets called from jquery-ui so we have to manually apply changes to scope
116 $scope.buildPaletteLists = function() {
117 $timeout(function() {
118 $scope.$apply(function() {
119 ctrl.buildPaletteLists();
120 });
121 });
122 };
123
124 // Checks if a field is on the form or set as a value
125 $scope.fieldInUse = function(fieldName) {
126 return check(ctrl.editor.layout['#children'], {'#tag': 'af-field', name: fieldName});
127 };
128
129 // Checks if fields in a block are already in use on the form.
130 // Note that if a block contains no fields it can be used repeatedly, so this will always return false for those.
131 $scope.blockInUse = function(block) {
132 if (block['af-join']) {
133 return check(ctrl.editor.layout['#children'], {'af-join': block['af-join']});
134 }
135 var fieldsInBlock = _.pluck(afGui.findRecursive(afGui.meta.blocks[block['#tag']].layout, {'#tag': 'af-field'}), 'name');
136 return check(ctrl.editor.layout['#children'], function(item) {
137 return item['#tag'] === 'af-field' && _.includes(fieldsInBlock, item.name);
138 });
139 };
140
141 // Check for a matching item for this entity
142 // Recursively checks the form layout, including block directives
143 function check(group, criteria, found) {
144 if (!found) {
145 found = {};
146 }
147 if (_.find(group, criteria)) {
148 found.match = true;
149 return true;
150 }
151 _.each(group, function(item) {
152 if (found.match) {
153 return false;
154 }
155 if (_.isPlainObject(item)) {
156 // Recurse through everything
157 if (item['#children']) {
158 check(item['#children'], criteria, found);
159 }
160 // Recurse into block directives
161 else if (item['#tag'] && item['#tag'] in afGui.meta.blocks) {
162 check(afGui.meta.blocks[item['#tag']].layout, criteria, found);
163 }
164 }
165 });
166 return found.match;
167 }
168
169 this.$onInit = function() {
170 // When a new block is saved, update the list
171 this.meta = afGui.meta;
172 $scope.$watchCollection('$ctrl.meta.blocks', function() {
173 $scope.controls.fieldSearch = '';
174 ctrl.buildPaletteLists();
175 });
176 };
177 }
178 });
179
180 })(angular, CRM.$, CRM._);