Merge pull request #22188 from totten/master-uninstall
[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'),
488560cc 37 ctrl = this;
e7515b5b 38
5623bf2a
CW
39 this.isSuperAdmin = CRM.checkPerm('all CiviCRM permissions and ACLs');
40 this.aclBypassHelp = ts('Only users with "all CiviCRM permissions and ACLs" can disable permission checks.');
41
9e827e8e
CW
42 this.preview = this.stale = false;
43
cc7246dd 44 // Extra (non-field) colum types
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 128 this.getColLabel = function(col) {
cc7246dd 129 if (col.type === 'field' || col.type === 'image') {
daa4e55a
CW
130 return ctrl.getFieldLabel(col.key);
131 }
132 return ctrl.colTypes[col.type].label;
133 };
134
cc7246dd
CW
135 this.toggleEmptyVal = function(col) {
136 if (col.empty_value) {
137 delete col.empty_value;
138 } else {
139 col.empty_value = ts('None');
140 }
141 };
142
b2ee26f0
CW
143 this.toggleRewrite = function(col) {
144 if (col.rewrite) {
145 col.rewrite = '';
146 } else {
147 col.rewrite = '[' + col.key + ']';
148 delete col.editable;
149 }
150 };
151
a58b7052 152 this.toggleImage = function(col) {
cc7246dd 153 if (col.type === 'image') {
a58b7052 154 delete col.image;
cc7246dd 155 col.type = 'field';
a58b7052
KJ
156 } else {
157 col.image = {
158 alt: this.getColLabel(col)
159 };
160 delete col.editable;
cc7246dd 161 col.type = 'image';
a58b7052
KJ
162 }
163 };
164
cc7246dd
CW
165 this.canBeImage = function(col) {
166 var expr = ctrl.getExprFromSelect(col.key),
167 info = searchMeta.parseExpr(expr);
168 return info.args[0] && info.args[0].field && info.args[0].field.input_type === 'File';
169 };
170
b2ee26f0
CW
171 this.toggleEditable = function(col) {
172 if (col.editable) {
173 delete col.editable;
c0fcc640
CW
174 } else {
175 col.editable = true;
b2ee26f0 176 }
b2ee26f0
CW
177 };
178
c0fcc640 179 this.canBeEditable = function(col) {
b2ee26f0
CW
180 var expr = ctrl.getExprFromSelect(col.key),
181 info = searchMeta.parseExpr(expr);
cc7246dd 182 return !col.rewrite && !col.link && !info.fn && info.args[0] && info.args[0].field && !info.args[0].field.readonly;
b2ee26f0
CW
183 };
184
f808224c
CW
185 // Checks if a column contains a sortable value
186 // Must be a real sql expression (not a pseudo-field like `result_row_num`)
850492de 187 this.canBeSortable = function(col) {
488560cc
CW
188 // Column-header sorting is incompatible with draggable sorting
189 if (ctrl.display.settings.draggable) {
190 return false;
191 }
850492de
CW
192 var expr = ctrl.getExprFromSelect(col.key),
193 info = searchMeta.parseExpr(expr),
f808224c 194 arg = (info && info.args && _.findWhere(info.args, {type: 'field'})) || {};
850492de
CW
195 return arg.field && arg.field.type !== 'Pseudo';
196 };
197
2fe33e6c
CW
198 // Aggregate functions (COUNT, AVG, MAX) cannot display as links, except for GROUP_CONCAT
199 // which gets special treatment in APIv4 to convert it to an array.
200 this.canBeLink = function(col) {
201 var expr = ctrl.getExprFromSelect(col.key),
202 info = searchMeta.parseExpr(expr);
203 return !info.fn || info.fn.category !== 'aggregate' || info.fn.name === 'GROUP_CONCAT';
204 };
205
9446fbaa
CW
206 var linkProps = ['path', 'entity', 'action', 'join', 'target'];
207
2dbf2d72
CW
208 this.toggleLink = function(column) {
209 if (column.link) {
9446fbaa 210 ctrl.onChangeLink(column, {});
2dbf2d72 211 } else {
c0fcc640 212 delete column.editable;
d6704532 213 var defaultLink = ctrl.getLinks(column.key)[0];
9446fbaa 214 ctrl.onChangeLink(column, defaultLink || {path: 'civicrm/'});
2dbf2d72
CW
215 }
216 };
217
9446fbaa
CW
218 this.onChangeLink = function(column, afterLink) {
219 column.link = column.link || {};
220 var beforeLink = column.link.action && _.findWhere(ctrl.getLinks(column.key), {action: column.link.action});
221 if (!afterLink.action && !afterLink.path) {
222 if (beforeLink && beforeLink.text === column.title) {
2dbf2d72
CW
223 delete column.title;
224 }
225 delete column.link;
9446fbaa
CW
226 return;
227 }
228 if (afterLink.text && ((!column.title && !beforeLink) || (beforeLink && beforeLink.text === column.title))) {
229 column.title = afterLink.text;
230 } else if (!afterLink.text && (beforeLink && beforeLink.text === column.title)) {
2dbf2d72
CW
231 delete column.title;
232 }
9446fbaa
CW
233 _.each(linkProps, function(prop) {
234 column.link[prop] = afterLink[prop] || '';
235 });
2dbf2d72
CW
236 };
237
d6704532 238 this.getLinks = function(columnKey) {
daa4e55a 239 if (!ctrl.links) {
957358aa 240 ctrl.links = {'*': ctrl.crmSearchAdmin.buildLinks()};
daa4e55a 241 }
d6704532
CW
242 if (!columnKey) {
243 return ctrl.links['*'];
244 }
245 var expr = ctrl.getExprFromSelect(columnKey),
246 info = searchMeta.parseExpr(expr),
957358aa 247 joinEntity = searchMeta.getJoinEntity(info);
d6704532 248 if (!ctrl.links[joinEntity]) {
957358aa 249 ctrl.links[joinEntity] = _.filter(ctrl.links['*'], {join: joinEntity});
d6704532
CW
250 }
251 return ctrl.links[joinEntity];
daa4e55a
CW
252 };
253
daa4e55a
CW
254 this.pickIcon = function(model, key) {
255 searchMeta.pickIcon().then(function(icon) {
256 model[key] = icon;
257 });
258 };
259
03b55607 260 // Helper function to sort active from hidden columns and initialize each column with defaults
69f0bd2b 261 this.initColumns = function(defaults) {
03b55607
CW
262 if (!ctrl.display.settings.columns) {
263 ctrl.display.settings.columns = _.transform(ctrl.savedSearch.api_params.select, function(columns, fieldExpr) {
28218ad6 264 columns.push(searchMeta.fieldToColumn(fieldExpr, defaults));
03b55607 265 });
969245e4 266 ctrl.hiddenColumns = [];
03b55607 267 } else {
a3caaf9e
CW
268 var activeColumns = _.collect(ctrl.display.settings.columns, 'key'),
269 selectAliases = _.map(ctrl.savedSearch.api_params.select, function(select) {
270 return _.last(select.split(' AS '));
271 });
969245e4 272 ctrl.hiddenColumns = _.transform(ctrl.savedSearch.api_params.select, function(hiddenColumns, fieldExpr) {
a3caaf9e
CW
273 var key = _.last(fieldExpr.split(' AS '));
274 if (!_.includes(activeColumns, key)) {
28218ad6 275 hiddenColumns.push(searchMeta.fieldToColumn(fieldExpr, defaults));
03b55607
CW
276 }
277 });
a3caaf9e 278 _.eachRight(activeColumns, function(key, index) {
daa4e55a 279 if (key && !_.includes(selectAliases, key)) {
03b55607
CW
280 ctrl.display.settings.columns.splice(index, 1);
281 }
282 });
03b55607
CW
283 }
284 };
285
e7515b5b
CW
286 this.previewDisplay = function() {
287 ctrl.preview = !ctrl.preview;
288 ctrl.stale = false;
289 if (!ctrl.preview) {
290 $timeout(function() {
291 ctrl.preview = true;
292 }, 100);
293 }
294 };
295
9e827e8e
CW
296 this.fieldsForSort = function() {
297 function disabledIf(key) {
298 return _.findIndex(ctrl.display.settings.sort, [key]) >= 0;
299 }
300 return {
9a0f174b
CW
301 results: [
302 {
303 text: ts('Random'),
304 icon: 'crm-i fa-random',
305 id: 'RAND()',
306 disabled: disabledIf('RAND()')
307 },
308 {
309 text: ts('Columns'),
310 children: ctrl.crmSearchAdmin.getSelectFields(disabledIf)
311 }
312 ].concat(ctrl.crmSearchAdmin.getAllFields('', ['Field', 'Custom'], disabledIf))
9e827e8e
CW
313 };
314 };
315
316 // Generic function to add to a setting array if the item is not already there
317 this.pushSetting = function(name, value) {
318 ctrl.display.settings[name] = ctrl.display.settings[name] || [];
319 if (_.findIndex(ctrl.display.settings[name], value) < 0) {
320 ctrl.display.settings[name].push(value);
321 }
322 };
323
e7515b5b
CW
324 $scope.$watch('$ctrl.display.settings', function() {
325 ctrl.stale = true;
326 }, true);
327 }
328 });
329
330})(angular, CRM.$, CRM._);