SearchKit - Expose default display to the UI
[civicrm-core.git] / ext / search_kit / ang / crmSearchAdmin / crmSearchAdminDisplay.component.js
CommitLineData
e7515b5b
CW
1(function(angular, $, _) {
2 "use strict";
3
493f83d4 4 angular.module('crmSearchAdmin').component('crmSearchAdminDisplay', {
e7515b5b
CW
5 bindings: {
6 savedSearch: '<',
7 display: '<'
8 },
9e827e8e
CW
9 require: {
10 crmSearchAdmin: '^crmSearchAdmin'
11 },
e7515b5b
CW
12 template: function() {
13 // Dynamic template generates switch condition for each display type
14 var html =
493f83d4 15 '<div ng-include="\'~/crmSearchAdmin/crmSearchAdminDisplay.html\'"></div>\n' +
e7515b5b 16 '<div ng-switch="$ctrl.display.type">\n';
493f83d4 17 _.each(CRM.crmSearchAdmin.displayTypes, function(type) {
e7515b5b 18 html +=
ecb9d1eb
CW
19 '<div ng-switch-when="' + type.id + '">\n' +
20 ' <search-admin-display-' + type.id + ' api-entity="$ctrl.savedSearch.api_entity" api-params="$ctrl.savedSearch.api_params" display="$ctrl.display"></search-admin-display-' + type.id + '>\n' +
e7515b5b
CW
21 ' <hr>\n' +
22 ' <button type="button" class="btn btn-{{ !$ctrl.stale ? \'success\' : $ctrl.preview ? \'warning\' : \'primary\' }}" ng-click="$ctrl.previewDisplay()" ng-disabled="!$ctrl.stale">\n' +
23 ' <i class="crm-i ' + type.icon + '"></i>' +
24 ' {{ $ctrl.preview && $ctrl.stale ? ts("Refresh") : ts("Preview") }}\n' +
25 ' </button>\n' +
26 ' <hr>\n' +
27 ' <div ng-if="$ctrl.preview">\n' +
406f1014 28 ' <' + type.name + ' api-entity="{{:: $ctrl.savedSearch.api_entity }}" search="$ctrl.savedSearch" display="$ctrl.display" settings="$ctrl.display.settings"></' + type.name + '>\n' +
e7515b5b
CW
29 ' </div>\n' +
30 '</div>\n';
31 });
32 html += '</div>';
33 return html;
34 },
03b55607 35 controller: function($scope, $timeout, searchMeta) {
33e81cf6 36 var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
02c7fc51
CW
37 ctrl = this,
38 afforms;
e7515b5b 39
5623bf2a
CW
40 this.isSuperAdmin = CRM.checkPerm('all CiviCRM permissions and ACLs');
41 this.aclBypassHelp = ts('Only users with "all CiviCRM permissions and ACLs" can disable permission checks.');
42
9e827e8e
CW
43 this.preview = this.stale = false;
44
daa4e55a
CW
45 this.colTypes = {
46 links: {
47 label: ts('Links'),
48 icon: 'fa-link',
49 defaults: {
50 links: []
51 }
52 },
53 buttons: {
54 label: ts('Buttons'),
55 icon: 'fa-square-o',
56 defaults: {
57 size: 'btn-sm',
58 links: []
59 }
60 },
61 menu: {
62 label: ts('Menu'),
63 icon: 'fa-bars',
64 defaults: {
65 text: ts('Actions'),
66 style: 'default',
67 size: 'btn-sm',
68 icon: 'fa-bars',
69 links: []
70 }
71 },
2ee83785
CW
72 include: {
73 label: ts('Custom Code'),
74 icon: 'fa-code',
75 defaults: {
76 path: ''
77 }
78 }
daa4e55a
CW
79 };
80
0458a613 81 // Drag-n-drop settings for reordering columns
969245e4
CW
82 this.sortableOptions = {
83 connectWith: '.crm-search-admin-edit-columns',
0458a613
CW
84 containment: '.crm-search-admin-edit-columns-wrapper',
85 cancel: 'input,textarea,button,select,option,a,label'
969245e4
CW
86 };
87
daa4e55a
CW
88 this.styles = CRM.crmSearchAdmin.styles;
89
90 this.addCol = function(type) {
91 var col = _.cloneDeep(this.colTypes[type].defaults);
92 col.type = type;
93 if (this.display.type === 'table') {
94 col.alignment = 'text-right';
95 }
96 ctrl.display.settings.columns.push(col);
97 };
98
969245e4 99 this.removeCol = function(index) {
daa4e55a
CW
100 if (ctrl.display.settings.columns[index].type === 'field') {
101 ctrl.hiddenColumns.push(ctrl.display.settings.columns[index]);
102 }
969245e4
CW
103 ctrl.display.settings.columns.splice(index, 1);
104 };
105
106 this.restoreCol = function(index) {
107 ctrl.display.settings.columns.push(ctrl.hiddenColumns[index]);
108 ctrl.hiddenColumns.splice(index, 1);
109 };
110
a3caaf9e
CW
111 this.getExprFromSelect = function(key) {
112 var match;
113 _.each(ctrl.savedSearch.api_params.select, function(expr) {
114 var parts = expr.split(' AS ');
115 if (_.includes(parts, key)) {
116 match = parts[0];
117 return false;
118 }
119 });
120 return match;
121 };
122
123 this.getFieldLabel = function(key) {
124 var expr = ctrl.getExprFromSelect(key);
125 return searchMeta.getDefaultLabel(expr);
126 };
127
daa4e55a
CW
128 this.getColLabel = function(col) {
129 if (col.type === 'field') {
130 return ctrl.getFieldLabel(col.key);
131 }
132 return ctrl.colTypes[col.type].label;
133 };
134
b2ee26f0
CW
135 this.toggleRewrite = function(col) {
136 if (col.rewrite) {
137 col.rewrite = '';
138 } else {
139 col.rewrite = '[' + col.key + ']';
140 delete col.editable;
141 }
142 };
143
a58b7052
KJ
144 this.toggleImage = function(col) {
145 if (col.image) {
146 delete col.image;
147 } else {
148 col.image = {
149 alt: this.getColLabel(col)
150 };
151 delete col.editable;
152 }
153 };
154
b2ee26f0
CW
155 this.toggleEditable = function(col) {
156 if (col.editable) {
157 delete col.editable;
c0fcc640
CW
158 } else {
159 col.editable = true;
b2ee26f0 160 }
b2ee26f0
CW
161 };
162
c0fcc640 163 this.canBeEditable = function(col) {
b2ee26f0
CW
164 var expr = ctrl.getExprFromSelect(col.key),
165 info = searchMeta.parseExpr(expr);
7891ca94 166 return !col.image && !col.rewrite && !col.link && !info.fn && info.args[0] && info.args[0].field && !info.args[0].field.readonly;
b2ee26f0
CW
167 };
168
f808224c
CW
169 // Checks if a column contains a sortable value
170 // Must be a real sql expression (not a pseudo-field like `result_row_num`)
850492de
CW
171 this.canBeSortable = function(col) {
172 var expr = ctrl.getExprFromSelect(col.key),
173 info = searchMeta.parseExpr(expr),
f808224c 174 arg = (info && info.args && _.findWhere(info.args, {type: 'field'})) || {};
850492de
CW
175 return arg.field && arg.field.type !== 'Pseudo';
176 };
177
2fe33e6c
CW
178 // Aggregate functions (COUNT, AVG, MAX) cannot display as links, except for GROUP_CONCAT
179 // which gets special treatment in APIv4 to convert it to an array.
180 this.canBeLink = function(col) {
181 var expr = ctrl.getExprFromSelect(col.key),
182 info = searchMeta.parseExpr(expr);
183 return !info.fn || info.fn.category !== 'aggregate' || info.fn.name === 'GROUP_CONCAT';
184 };
185
9446fbaa
CW
186 var linkProps = ['path', 'entity', 'action', 'join', 'target'];
187
2dbf2d72
CW
188 this.toggleLink = function(column) {
189 if (column.link) {
9446fbaa 190 ctrl.onChangeLink(column, {});
2dbf2d72 191 } else {
c0fcc640 192 delete column.editable;
d6704532 193 var defaultLink = ctrl.getLinks(column.key)[0];
9446fbaa 194 ctrl.onChangeLink(column, defaultLink || {path: 'civicrm/'});
2dbf2d72
CW
195 }
196 };
197
9446fbaa
CW
198 this.onChangeLink = function(column, afterLink) {
199 column.link = column.link || {};
200 var beforeLink = column.link.action && _.findWhere(ctrl.getLinks(column.key), {action: column.link.action});
201 if (!afterLink.action && !afterLink.path) {
202 if (beforeLink && beforeLink.text === column.title) {
2dbf2d72
CW
203 delete column.title;
204 }
205 delete column.link;
9446fbaa
CW
206 return;
207 }
208 if (afterLink.text && ((!column.title && !beforeLink) || (beforeLink && beforeLink.text === column.title))) {
209 column.title = afterLink.text;
210 } else if (!afterLink.text && (beforeLink && beforeLink.text === column.title)) {
2dbf2d72
CW
211 delete column.title;
212 }
9446fbaa
CW
213 _.each(linkProps, function(prop) {
214 column.link[prop] = afterLink[prop] || '';
215 });
2dbf2d72
CW
216 };
217
d6704532 218 this.getLinks = function(columnKey) {
daa4e55a 219 if (!ctrl.links) {
957358aa 220 ctrl.links = {'*': ctrl.crmSearchAdmin.buildLinks()};
daa4e55a 221 }
d6704532
CW
222 if (!columnKey) {
223 return ctrl.links['*'];
224 }
225 var expr = ctrl.getExprFromSelect(columnKey),
226 info = searchMeta.parseExpr(expr),
957358aa 227 joinEntity = searchMeta.getJoinEntity(info);
d6704532 228 if (!ctrl.links[joinEntity]) {
957358aa 229 ctrl.links[joinEntity] = _.filter(ctrl.links['*'], {join: joinEntity});
d6704532
CW
230 }
231 return ctrl.links[joinEntity];
daa4e55a
CW
232 };
233
daa4e55a
CW
234 this.pickIcon = function(model, key) {
235 searchMeta.pickIcon().then(function(icon) {
236 model[key] = icon;
237 });
238 };
239
03b55607 240 // Helper function to sort active from hidden columns and initialize each column with defaults
69f0bd2b 241 this.initColumns = function(defaults) {
03b55607
CW
242 if (!ctrl.display.settings.columns) {
243 ctrl.display.settings.columns = _.transform(ctrl.savedSearch.api_params.select, function(columns, fieldExpr) {
28218ad6 244 columns.push(searchMeta.fieldToColumn(fieldExpr, defaults));
03b55607 245 });
969245e4 246 ctrl.hiddenColumns = [];
03b55607 247 } else {
a3caaf9e
CW
248 var activeColumns = _.collect(ctrl.display.settings.columns, 'key'),
249 selectAliases = _.map(ctrl.savedSearch.api_params.select, function(select) {
250 return _.last(select.split(' AS '));
251 });
969245e4 252 ctrl.hiddenColumns = _.transform(ctrl.savedSearch.api_params.select, function(hiddenColumns, fieldExpr) {
a3caaf9e
CW
253 var key = _.last(fieldExpr.split(' AS '));
254 if (!_.includes(activeColumns, key)) {
28218ad6 255 hiddenColumns.push(searchMeta.fieldToColumn(fieldExpr, defaults));
03b55607
CW
256 }
257 });
a3caaf9e 258 _.eachRight(activeColumns, function(key, index) {
daa4e55a 259 if (key && !_.includes(selectAliases, key)) {
03b55607
CW
260 ctrl.display.settings.columns.splice(index, 1);
261 }
262 });
03b55607
CW
263 }
264 };
265
e7515b5b
CW
266 this.previewDisplay = function() {
267 ctrl.preview = !ctrl.preview;
268 ctrl.stale = false;
269 if (!ctrl.preview) {
270 $timeout(function() {
271 ctrl.preview = true;
272 }, 100);
273 }
274 };
275
9e827e8e
CW
276 this.fieldsForSort = function() {
277 function disabledIf(key) {
278 return _.findIndex(ctrl.display.settings.sort, [key]) >= 0;
279 }
280 return {
9a0f174b
CW
281 results: [
282 {
283 text: ts('Random'),
284 icon: 'crm-i fa-random',
285 id: 'RAND()',
286 disabled: disabledIf('RAND()')
287 },
288 {
289 text: ts('Columns'),
290 children: ctrl.crmSearchAdmin.getSelectFields(disabledIf)
291 }
292 ].concat(ctrl.crmSearchAdmin.getAllFields('', ['Field', 'Custom'], disabledIf))
9e827e8e
CW
293 };
294 };
295
296 // Generic function to add to a setting array if the item is not already there
297 this.pushSetting = function(name, value) {
298 ctrl.display.settings[name] = ctrl.display.settings[name] || [];
299 if (_.findIndex(ctrl.display.settings[name], value) < 0) {
300 ctrl.display.settings[name].push(value);
301 }
302 };
303
e7515b5b
CW
304 $scope.$watch('$ctrl.display.settings', function() {
305 ctrl.stale = true;
306 }, true);
307 }
308 });
309
310})(angular, CRM.$, CRM._);