Commit | Line | Data |
---|---|---|
25523059 CW |
1 | (function(angular, $, _) { |
2 | "use strict"; | |
3 | ||
493f83d4 | 4 | angular.module('crmSearchAdmin').component('crmSearchAdmin', { |
25523059 | 5 | bindings: { |
2894db84 | 6 | savedSearch: '<' |
25523059 | 7 | }, |
493f83d4 | 8 | templateUrl: '~/crmSearchAdmin/crmSearchAdmin.html', |
b40e49df | 9 | controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta, formatForSelect2) { |
25523059 CW |
10 | var ts = $scope.ts = CRM.ts(), |
11 | ctrl = this; | |
5fcd63f4 CW |
12 | |
13 | this.DEFAULT_AGGREGATE_FN = 'GROUP_CONCAT'; | |
14 | ||
25523059 CW |
15 | this.selectedRows = []; |
16 | this.limit = CRM.cache.get('searchPageSize', 30); | |
17 | this.page = 1; | |
493f83d4 | 18 | this.displayTypes = _.indexBy(CRM.crmSearchAdmin.displayTypes, 'name'); |
25523059 | 19 | // After a search this.results is an object of result arrays keyed by page, |
c419e6ed | 20 | // Initially this.results is an empty string because 1: it's falsey (unlike an empty object) and 2: it doesn't throw an error if you try to access undefined properties (unlike null) |
25523059 CW |
21 | this.results = ''; |
22 | this.rowCount = false; | |
2894db84 | 23 | this.allRowsSelected = false; |
25523059 CW |
24 | // Have the filters (WHERE, HAVING, GROUP BY, JOIN) changed? |
25 | this.stale = true; | |
25523059 | 26 | |
f9197b41 | 27 | $scope.controls = {tab: 'compose'}; |
25523059 | 28 | $scope.joinTypes = [{k: false, v: ts('Optional')}, {k: true, v: ts('Required')}]; |
44402a2e | 29 | $scope.groupOptions = CRM.crmSearchActions.groupOptions; |
9813ae79 | 30 | $scope.entities = formatForSelect2(CRM.vars.search.schema, 'name', 'title_plural', ['description', 'icon']); |
25523059 CW |
31 | this.perm = { |
32 | editGroups: CRM.checkPerm('edit groups') | |
33 | }; | |
34 | ||
2894db84 CW |
35 | this.$onInit = function() { |
36 | this.entityTitle = searchMeta.getEntity(this.savedSearch.api_entity).title_plural; | |
37 | ||
f9197b41 | 38 | this.savedSearch.displays = this.savedSearch.displays || []; |
44402a2e CW |
39 | this.savedSearch.groups = this.savedSearch.groups || []; |
40 | this.groupExists = !!this.savedSearch.groups.length; | |
f9197b41 | 41 | |
b40e49df CW |
42 | if (!this.savedSearch.id) { |
43 | $scope.$bindToRoute({ | |
44 | param: 'params', | |
45 | expr: '$ctrl.savedSearch.api_params', | |
b1603dbd | 46 | deep: true, |
b40e49df CW |
47 | default: { |
48 | version: 4, | |
49 | select: getDefaultSelect(), | |
50 | orderBy: {}, | |
51 | where: [], | |
52 | } | |
53 | }); | |
2894db84 CW |
54 | } |
55 | ||
56 | $scope.$watchCollection('$ctrl.savedSearch.api_params.select', onChangeSelect); | |
57 | ||
58 | $scope.$watch('$ctrl.savedSearch.api_params.where', onChangeFilters, true); | |
59 | ||
60 | if (this.paramExists('groupBy')) { | |
61 | this.savedSearch.api_params.groupBy = this.savedSearch.api_params.groupBy || []; | |
62 | $scope.$watchCollection('$ctrl.savedSearch.api_params.groupBy', onChangeFilters); | |
63 | } | |
64 | ||
65 | if (this.paramExists('join')) { | |
66 | this.savedSearch.api_params.join = this.savedSearch.api_params.join || []; | |
67 | $scope.$watch('$ctrl.savedSearch.api_params.join', onChangeFilters, true); | |
68 | } | |
69 | ||
70 | if (this.paramExists('having')) { | |
71 | this.savedSearch.api_params.having = this.savedSearch.api_params.having || []; | |
72 | $scope.$watch('$ctrl.savedSearch.api_params.having', onChangeFilters, true); | |
73 | } | |
74 | ||
44402a2e CW |
75 | $scope.$watch('$ctrl.savedSearch', onChangeAnything, true); |
76 | ||
2badf248 CW |
77 | // After watcher runs for the first time and messes up the status, set it correctly |
78 | $timeout(function() { | |
79 | $scope.status = ctrl.savedSearch && ctrl.savedSearch.id ? 'saved' : 'unsaved'; | |
80 | }); | |
44402a2e | 81 | |
2894db84 CW |
82 | loadFieldOptions(); |
83 | }; | |
25523059 | 84 | |
44402a2e CW |
85 | function onChangeAnything() { |
86 | $scope.status = 'unsaved'; | |
87 | } | |
88 | ||
89 | this.save = function() { | |
2badf248 CW |
90 | if (!validate()) { |
91 | return; | |
92 | } | |
44402a2e CW |
93 | $scope.status = 'saving'; |
94 | var params = _.cloneDeep(ctrl.savedSearch), | |
95 | apiCalls = {}, | |
96 | chain = {}; | |
97 | if (ctrl.groupExists) { | |
98 | chain.groups = ['Group', 'save', {defaults: {saved_search_id: '$id'}, records: params.groups}]; | |
99 | delete params.groups; | |
100 | } else if (params.id) { | |
101 | apiCalls.deleteGroup = ['Group', 'delete', {where: [['saved_search_id', '=', params.id]]}]; | |
102 | } | |
103 | if (params.displays && params.displays.length) { | |
104 | chain.displays = ['SearchDisplay', 'replace', {where: [['saved_search_id', '=', '$id']], records: params.displays}]; | |
105 | } else if (params.id) { | |
106 | apiCalls.deleteDisplays = ['SearchDisplay', 'delete', {where: [['saved_search_id', '=', params.id]]}]; | |
107 | } | |
108 | delete params.displays; | |
109 | apiCalls.saved = ['SavedSearch', 'save', {records: [params], chain: chain}, 0]; | |
110 | crmApi4(apiCalls).then(function(results) { | |
b40e49df CW |
111 | // After saving a new search, redirect to the edit url |
112 | if (!ctrl.savedSearch.id) { | |
113 | $location.url('edit/' + results.saved.id); | |
114 | } | |
2badf248 CW |
115 | // Set new status to saved unless the user changed something in the interim |
116 | var newStatus = $scope.status === 'unsaved' ? 'unsaved' : 'saved'; | |
2badf248 CW |
117 | if (results.saved.groups && results.saved.groups.length) { |
118 | ctrl.savedSearch.groups[0].id = results.saved.groups[0].id; | |
44402a2e | 119 | } |
2badf248 CW |
120 | ctrl.savedSearch.displays = results.saved.displays || []; |
121 | // Wait until after onChangeAnything to update status | |
122 | $timeout(function() { | |
123 | $scope.status = newStatus; | |
124 | }); | |
44402a2e CW |
125 | }); |
126 | }; | |
127 | ||
25523059 | 128 | this.paramExists = function(param) { |
2894db84 | 129 | return _.includes(searchMeta.getEntity(ctrl.savedSearch.api_entity).params, param); |
25523059 CW |
130 | }; |
131 | ||
f9197b41 | 132 | this.addDisplay = function(type) { |
f9197b41 | 133 | ctrl.savedSearch.displays.push({ |
44402a2e | 134 | type: type, |
f9cf8797 | 135 | label: '' |
f9197b41 | 136 | }); |
4b01551f CW |
137 | $scope.selectTab('display_' + (ctrl.savedSearch.displays.length - 1)); |
138 | }; | |
139 | ||
140 | this.removeDisplay = function(index) { | |
141 | var display = ctrl.savedSearch.displays[index]; | |
142 | if (display.id) { | |
143 | display.trashed = !display.trashed; | |
2badf248 CW |
144 | if ($scope.controls.tab === ('display_' + index) && display.trashed) { |
145 | $scope.selectTab('compose'); | |
146 | } else if (!display.trashed) { | |
147 | $scope.selectTab('display_' + index); | |
148 | } | |
4b01551f CW |
149 | } else { |
150 | $scope.selectTab('compose'); | |
151 | ctrl.savedSearch.displays.splice(index, 1); | |
152 | } | |
153 | }; | |
154 | ||
155 | this.addGroup = function() { | |
44402a2e | 156 | ctrl.savedSearch.groups.push({ |
4b01551f CW |
157 | title: '', |
158 | description: '', | |
159 | visibility: 'User and User Admin Only', | |
160 | group_type: [] | |
44402a2e | 161 | }); |
4b01551f CW |
162 | ctrl.groupExists = true; |
163 | $scope.selectTab('group'); | |
164 | }; | |
165 | ||
166 | $scope.selectTab = function(tab) { | |
167 | if (tab === 'group') { | |
168 | $scope.smartGroupColumns = searchMeta.getSmartGroupColumns(ctrl.savedSearch.api_entity, ctrl.savedSearch.api_params); | |
169 | var smartGroupColumns = _.map($scope.smartGroupColumns, 'id'); | |
170 | if (smartGroupColumns.length && !_.includes(smartGroupColumns, ctrl.savedSearch.api_params.select[0])) { | |
171 | ctrl.savedSearch.api_params.select.unshift(smartGroupColumns[0]); | |
172 | } | |
173 | } | |
174 | ctrl.savedSearch.api_params.select = _.uniq(ctrl.savedSearch.api_params.select); | |
175 | $scope.controls.tab = tab; | |
176 | }; | |
177 | ||
178 | this.removeGroup = function() { | |
179 | ctrl.groupExists = !ctrl.groupExists; | |
44402a2e CW |
180 | if (!ctrl.groupExists && (!ctrl.savedSearch.groups.length || !ctrl.savedSearch.groups[0].id)) { |
181 | ctrl.savedSearch.groups.length = 0; | |
4b01551f | 182 | } |
2badf248 CW |
183 | if ($scope.controls.tab === 'group') { |
184 | $scope.selectTab('compose'); | |
185 | } | |
f9197b41 CW |
186 | }; |
187 | ||
25523059 | 188 | $scope.getJoinEntities = function() { |
2894db84 | 189 | var joinEntities = _.transform(CRM.vars.search.links[ctrl.savedSearch.api_entity], function(joinEntities, link) { |
25523059 CW |
190 | var entity = searchMeta.getEntity(link.entity); |
191 | if (entity) { | |
192 | joinEntities.push({ | |
193 | id: link.entity + ' AS ' + link.alias, | |
9813ae79 | 194 | text: entity.title_plural, |
25523059 CW |
195 | description: '(' + link.alias + ')', |
196 | icon: entity.icon | |
197 | }); | |
198 | } | |
199 | }, []); | |
200 | return {results: joinEntities}; | |
201 | }; | |
202 | ||
203 | $scope.addJoin = function() { | |
204 | // Debounce the onchange event using timeout | |
205 | $timeout(function() { | |
206 | if ($scope.controls.join) { | |
2894db84 CW |
207 | ctrl.savedSearch.api_params.join = ctrl.savedSearch.api_params.join || []; |
208 | ctrl.savedSearch.api_params.join.push([$scope.controls.join, false]); | |
25523059 CW |
209 | loadFieldOptions(); |
210 | } | |
211 | $scope.controls.join = ''; | |
212 | }); | |
213 | }; | |
214 | ||
215 | $scope.changeJoin = function(idx) { | |
2894db84 CW |
216 | if (ctrl.savedSearch.api_params.join[idx][0]) { |
217 | ctrl.savedSearch.api_params.join[idx].length = 2; | |
25523059 CW |
218 | loadFieldOptions(); |
219 | } else { | |
220 | ctrl.clearParam('join', idx); | |
221 | } | |
222 | }; | |
223 | ||
224 | $scope.changeGroupBy = function(idx) { | |
2894db84 | 225 | if (!ctrl.savedSearch.api_params.groupBy[idx]) { |
25523059 CW |
226 | ctrl.clearParam('groupBy', idx); |
227 | } | |
5fcd63f4 | 228 | // Remove aggregate functions when no grouping |
2894db84 CW |
229 | if (!ctrl.savedSearch.api_params.groupBy.length) { |
230 | _.each(ctrl.savedSearch.api_params.select, function(col, pos) { | |
5fcd63f4 CW |
231 | if (_.contains(col, '(')) { |
232 | var info = searchMeta.parseExpr(col); | |
233 | if (info.fn.category === 'aggregate') { | |
2894db84 | 234 | ctrl.savedSearch.api_params.select[pos] = info.path + info.suffix; |
5fcd63f4 CW |
235 | } |
236 | } | |
237 | }); | |
238 | } | |
25523059 CW |
239 | }; |
240 | ||
2badf248 CW |
241 | function validate() { |
242 | var errors = [], | |
243 | errorEl, | |
244 | label, | |
245 | tab; | |
246 | if (!ctrl.savedSearch.label) { | |
247 | errorEl = '#crm-saved-search-label'; | |
248 | label = ts('Search Label'); | |
249 | errors.push(ts('%1 is a required field.', {1: label})); | |
250 | } | |
251 | if (ctrl.groupExists && !ctrl.savedSearch.groups[0].title) { | |
252 | errorEl = '#crm-search-admin-group-title'; | |
253 | label = ts('Group Title'); | |
254 | errors.push(ts('%1 is a required field.', {1: label})); | |
255 | tab = 'group'; | |
256 | } | |
257 | _.each(ctrl.savedSearch.displays, function(display, index) { | |
258 | if (!display.trashed && !display.label) { | |
259 | errorEl = '#crm-search-admin-display-label'; | |
260 | label = ts('Display Label'); | |
261 | errors.push(ts('%1 is a required field.', {1: label})); | |
262 | tab = 'display_' + index; | |
263 | } | |
264 | }); | |
265 | if (errors.length) { | |
266 | if (tab) { | |
267 | $scope.selectTab(tab); | |
268 | } | |
269 | $(errorEl).crmError(errors.join('<br>'), ts('Error Saving'), {expires: 5000}); | |
270 | } | |
271 | return !errors.length; | |
272 | } | |
273 | ||
25523059 CW |
274 | /** |
275 | * Called when clicking on a column header | |
276 | * @param col | |
277 | * @param $event | |
278 | */ | |
279 | $scope.setOrderBy = function(col, $event) { | |
280 | var dir = $scope.getOrderBy(col) === 'fa-sort-asc' ? 'DESC' : 'ASC'; | |
b1603dbd | 281 | if (!$event.shiftKey || !ctrl.savedSearch.api_params.orderBy) { |
2894db84 | 282 | ctrl.savedSearch.api_params.orderBy = {}; |
25523059 | 283 | } |
2894db84 | 284 | ctrl.savedSearch.api_params.orderBy[col] = dir; |
7da9d079 CW |
285 | if (ctrl.results) { |
286 | ctrl.refreshPage(); | |
287 | } | |
25523059 CW |
288 | }; |
289 | ||
290 | /** | |
291 | * Returns crm-i icon class for a sortable column | |
292 | * @param col | |
293 | * @returns {string} | |
294 | */ | |
295 | $scope.getOrderBy = function(col) { | |
2894db84 | 296 | var dir = ctrl.savedSearch.api_params.orderBy && ctrl.savedSearch.api_params.orderBy[col]; |
25523059 CW |
297 | if (dir) { |
298 | return 'fa-sort-' + dir.toLowerCase(); | |
299 | } | |
300 | return 'fa-sort disabled'; | |
301 | }; | |
302 | ||
303 | $scope.addParam = function(name) { | |
2894db84 CW |
304 | if ($scope.controls[name] && !_.contains(ctrl.savedSearch.api_params[name], $scope.controls[name])) { |
305 | ctrl.savedSearch.api_params[name].push($scope.controls[name]); | |
25523059 CW |
306 | if (name === 'groupBy') { |
307 | // Expand the aggregate block | |
308 | $timeout(function() { | |
309 | $('#crm-search-build-group-aggregate.collapsed .collapsible-title').click(); | |
310 | }, 10); | |
311 | } | |
312 | } | |
313 | $scope.controls[name] = ''; | |
314 | }; | |
315 | ||
316 | // Deletes an item from an array param | |
317 | this.clearParam = function(name, idx) { | |
2894db84 | 318 | ctrl.savedSearch.api_params[name].splice(idx, 1); |
25523059 CW |
319 | }; |
320 | ||
321 | // Prevent visual jumps in results table height during loading | |
322 | function lockTableHeight() { | |
323 | var $table = $('.crm-search-results', $element); | |
324 | $table.css('height', $table.height()); | |
325 | } | |
326 | ||
327 | function unlockTableHeight() { | |
328 | $('.crm-search-results', $element).css('height', ''); | |
329 | } | |
330 | ||
5fcd63f4 CW |
331 | // Ensure all non-grouped columns are aggregated if using GROUP BY |
332 | function aggregateGroupByColumns() { | |
2894db84 CW |
333 | if (ctrl.savedSearch.api_params.groupBy.length) { |
334 | _.each(ctrl.savedSearch.api_params.select, function(col, pos) { | |
5fcd63f4 | 335 | if (!_.contains(col, '(') && ctrl.canAggregate(col)) { |
2894db84 | 336 | ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(' + col + ')'; |
5fcd63f4 CW |
337 | } |
338 | }); | |
339 | } | |
340 | } | |
341 | ||
25523059 CW |
342 | // Debounced callback for loadResults |
343 | function _loadResultsCallback() { | |
344 | // Multiply limit to read 2 pages at once & save ajax requests | |
f9cf8797 CW |
345 | var params = _.merge(_.cloneDeep(ctrl.savedSearch.api_params), {debug: true, limit: ctrl.limit * 2}); |
346 | // Select the ids of joined entities (helps with displaying links) | |
347 | _.each(params.join, function(join) { | |
348 | var idField = join[0].split(' AS ')[1] + '.id'; | |
349 | if (!_.includes(params.select, idField) && !ctrl.canAggregate(idField)) { | |
350 | params.select.push(idField); | |
351 | } | |
352 | }); | |
25523059 CW |
353 | lockTableHeight(); |
354 | $scope.error = false; | |
355 | if (ctrl.stale) { | |
356 | ctrl.page = 1; | |
357 | ctrl.rowCount = false; | |
358 | } | |
359 | if (ctrl.rowCount === false) { | |
360 | params.select.push('row_count'); | |
361 | } | |
362 | params.offset = ctrl.limit * (ctrl.page - 1); | |
2894db84 | 363 | crmApi4(ctrl.savedSearch.api_entity, 'get', params).then(function(success) { |
25523059 CW |
364 | if (ctrl.stale) { |
365 | ctrl.results = {}; | |
366 | } | |
367 | if (ctrl.rowCount === false) { | |
368 | ctrl.rowCount = success.count; | |
369 | } | |
370 | ctrl.debug = success.debug; | |
371 | // populate this page & the next | |
372 | ctrl.results[ctrl.page] = success.slice(0, ctrl.limit); | |
373 | if (success.length > ctrl.limit) { | |
374 | ctrl.results[ctrl.page + 1] = success.slice(ctrl.limit); | |
375 | } | |
376 | $scope.loading = false; | |
377 | ctrl.stale = false; | |
378 | unlockTableHeight(); | |
379 | }, function(error) { | |
380 | $scope.loading = false; | |
381 | ctrl.results = {}; | |
382 | ctrl.stale = true; | |
383 | ctrl.debug = error.debug; | |
384 | $scope.error = errorMsg(error); | |
9c4a1dae CW |
385 | }) |
386 | .finally(function() { | |
387 | if (ctrl.debug) { | |
f9cf8797 | 388 | ctrl.debug.params = JSON.stringify(params, null, 2); |
8afb2047 CW |
389 | if (ctrl.debug.timeIndex) { |
390 | ctrl.debug.timeIndex = Number.parseFloat(ctrl.debug.timeIndex).toPrecision(2); | |
391 | } | |
9c4a1dae CW |
392 | } |
393 | }); | |
25523059 CW |
394 | } |
395 | ||
396 | var _loadResults = _.debounce(_loadResultsCallback, 250); | |
397 | ||
398 | function loadResults() { | |
399 | $scope.loading = true; | |
5fcd63f4 | 400 | aggregateGroupByColumns(); |
25523059 CW |
401 | _loadResults(); |
402 | } | |
403 | ||
404 | // What to tell the user when search returns an error from the server | |
405 | // Todo: parse error codes and give helpful feedback. | |
406 | function errorMsg(error) { | |
407 | return ts('Ensure all search critera are set correctly and try again.'); | |
408 | } | |
409 | ||
410 | this.changePage = function() { | |
411 | if (ctrl.stale || !ctrl.results[ctrl.page]) { | |
412 | lockTableHeight(); | |
413 | loadResults(); | |
414 | } | |
415 | }; | |
416 | ||
417 | this.refreshAll = function() { | |
418 | ctrl.stale = true; | |
419 | ctrl.selectedRows.length = 0; | |
420 | loadResults(); | |
421 | }; | |
422 | ||
423 | // Refresh results while staying on current page. | |
424 | this.refreshPage = function() { | |
425 | lockTableHeight(); | |
426 | ctrl.results = {}; | |
427 | loadResults(); | |
428 | }; | |
429 | ||
430 | $scope.onClickSearch = function() { | |
431 | if (ctrl.autoSearch) { | |
432 | ctrl.autoSearch = false; | |
433 | } else { | |
434 | ctrl.refreshAll(); | |
435 | } | |
436 | }; | |
437 | ||
438 | $scope.onClickAuto = function() { | |
439 | ctrl.autoSearch = !ctrl.autoSearch; | |
440 | if (ctrl.autoSearch && ctrl.stale) { | |
441 | ctrl.refreshAll(); | |
442 | } | |
0b1769c6 | 443 | $('.crm-search-auto-toggle').blur(); |
25523059 CW |
444 | }; |
445 | ||
446 | $scope.onChangeLimit = function() { | |
447 | // Refresh only if search has already been run | |
448 | if (ctrl.autoSearch || ctrl.results) { | |
449 | // Save page size in localStorage | |
450 | CRM.cache.set('searchPageSize', ctrl.limit); | |
451 | ctrl.refreshAll(); | |
452 | } | |
453 | }; | |
454 | ||
455 | function onChangeSelect(newSelect, oldSelect) { | |
7da9d079 | 456 | // When removing a column from SELECT, also remove from ORDER BY |
2894db84 CW |
457 | _.each(_.difference(_.keys(ctrl.savedSearch.api_params.orderBy), newSelect), function(col) { |
458 | delete ctrl.savedSearch.api_params.orderBy[col]; | |
7da9d079 | 459 | }); |
25523059 CW |
460 | // Re-arranging or removing columns doesn't merit a refresh, only adding columns does |
461 | if (!oldSelect || _.difference(newSelect, oldSelect).length) { | |
462 | if (ctrl.autoSearch) { | |
463 | ctrl.refreshPage(); | |
464 | } else { | |
465 | ctrl.stale = true; | |
466 | } | |
467 | } | |
2c7e2f4b | 468 | if (ctrl.load) { |
f9197b41 | 469 | ctrl.saved = false; |
2c7e2f4b | 470 | } |
25523059 CW |
471 | } |
472 | ||
25523059 CW |
473 | function onChangeFilters() { |
474 | ctrl.stale = true; | |
475 | ctrl.selectedRows.length = 0; | |
2c7e2f4b | 476 | if (ctrl.load) { |
f9197b41 | 477 | ctrl.saved = false; |
2c7e2f4b | 478 | } |
25523059 CW |
479 | if (ctrl.autoSearch) { |
480 | ctrl.refreshAll(); | |
481 | } | |
482 | } | |
483 | ||
484 | $scope.selectAllRows = function() { | |
485 | // Deselect all | |
486 | if (ctrl.allRowsSelected) { | |
487 | ctrl.allRowsSelected = false; | |
488 | ctrl.selectedRows.length = 0; | |
489 | return; | |
490 | } | |
491 | // Select all | |
492 | ctrl.allRowsSelected = true; | |
493 | if (ctrl.page === 1 && ctrl.results[1].length < ctrl.limit) { | |
494 | ctrl.selectedRows = _.pluck(ctrl.results[1], 'id'); | |
495 | return; | |
496 | } | |
497 | // If more than one page of results, use ajax to fetch all ids | |
498 | $scope.loadingAllRows = true; | |
2894db84 | 499 | var params = _.cloneDeep(ctrl.savedSearch.api_params); |
25523059 | 500 | params.select = ['id']; |
2894db84 | 501 | crmApi4(ctrl.savedSearch.api_entity, 'get', params, ['id']).then(function(ids) { |
25523059 CW |
502 | $scope.loadingAllRows = false; |
503 | ctrl.selectedRows = _.toArray(ids); | |
504 | }); | |
505 | }; | |
506 | ||
507 | $scope.selectRow = function(row) { | |
508 | var index = ctrl.selectedRows.indexOf(row.id); | |
509 | if (index < 0) { | |
510 | ctrl.selectedRows.push(row.id); | |
511 | ctrl.allRowsSelected = (ctrl.rowCount === ctrl.selectedRows.length); | |
512 | } else { | |
513 | ctrl.allRowsSelected = false; | |
514 | ctrl.selectedRows.splice(index, 1); | |
515 | } | |
516 | }; | |
517 | ||
518 | $scope.isRowSelected = function(row) { | |
519 | return ctrl.allRowsSelected || _.includes(ctrl.selectedRows, row.id); | |
520 | }; | |
521 | ||
03b55607 | 522 | this.getFieldLabel = searchMeta.getDefaultLabel; |
25523059 CW |
523 | |
524 | // Is a column eligible to use an aggregate function? | |
525 | this.canAggregate = function(col) { | |
f9cf8797 CW |
526 | // If the query does not use grouping, never |
527 | if (!ctrl.savedSearch.api_params.groupBy.length) { | |
528 | return false; | |
529 | } | |
4f3a95d9 | 530 | var info = searchMeta.parseExpr(col); |
25523059 | 531 | // If the column is used for a groupBy, no |
2894db84 | 532 | if (ctrl.savedSearch.api_params.groupBy.indexOf(info.path) > -1) { |
25523059 CW |
533 | return false; |
534 | } | |
535 | // If the entity this column belongs to is being grouped by id, then also no | |
2894db84 | 536 | return ctrl.savedSearch.api_params.groupBy.indexOf(info.prefix + 'id') < 0; |
25523059 CW |
537 | }; |
538 | ||
f9cf8797 | 539 | $scope.formatResult = function(row, col) { |
25523059 CW |
540 | var info = searchMeta.parseExpr(col), |
541 | key = info.fn ? (info.fn.name + ':' + info.path + info.suffix) : col, | |
542 | value = row[key]; | |
7ce7b1cd | 543 | if (info.fn && info.fn.name === 'COUNT') { |
2d198bb4 CW |
544 | return value; |
545 | } | |
f9cf8797 CW |
546 | // Output user-facing name/label fields as a link, if possible |
547 | if (info.field && _.includes(['display_name', 'title', 'label', 'subject'], info.field.name) && !info.fn && typeof value === 'string') { | |
548 | var link = getEntityUrl(row, info); | |
549 | if (link) { | |
550 | return '<a href="' + _.escape(link.url) + '" title="' + _.escape(link.title) + '">' + formatFieldValue(info.field, value) + '</a>'; | |
551 | } | |
552 | } | |
25523059 CW |
553 | return formatFieldValue(info.field, value); |
554 | }; | |
555 | ||
f9cf8797 CW |
556 | // Attempts to construct a view url for a given entity |
557 | function getEntityUrl(row, info) { | |
558 | var entity = searchMeta.getEntity(info.field.entity), | |
559 | path = _.result(_.findWhere(entity.paths, {action: 'view'}), 'path'); | |
560 | // Only proceed if the path metadata exists for this entity | |
561 | if (path) { | |
562 | // Replace tokens in the path (e.g. [id]) | |
563 | var tokens = path.match(/\[\w*]/g) || [], | |
564 | replacements = _.transform(tokens, function(replacements, token) { | |
565 | var fieldName = info.prefix + token.slice(1, token.length - 1); | |
566 | if (row[fieldName]) { | |
567 | replacements.push(row[fieldName]); | |
568 | } | |
569 | }); | |
570 | // Only proceed if the row contains all the necessary data to resolve tokens | |
571 | if (tokens.length === replacements.length) { | |
572 | _.each(tokens, function(token, index) { | |
573 | path = path.replace(token, replacements[index]); | |
574 | }); | |
575 | return {url: CRM.url(path), title: path.title}; | |
576 | } | |
577 | } | |
578 | } | |
579 | ||
25523059 | 580 | function formatFieldValue(field, value) { |
f9cf8797 CW |
581 | var type = field.data_type, |
582 | result = value; | |
7ce7b1cd CW |
583 | if (_.isArray(value)) { |
584 | return _.map(value, function(val) { | |
585 | return formatFieldValue(field, val); | |
586 | }).join(', '); | |
587 | } | |
25523059 | 588 | if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) { |
f9cf8797 | 589 | result = CRM.utils.formatDate(value, null, type === 'Timestamp'); |
25523059 CW |
590 | } |
591 | else if (type === 'Boolean' && typeof value === 'boolean') { | |
f9cf8797 | 592 | result = value ? ts('Yes') : ts('No'); |
25523059 | 593 | } |
7ce7b1cd | 594 | else if (type === 'Money' && typeof value === 'number') { |
f9cf8797 | 595 | result = CRM.formatMoney(value); |
2d198bb4 | 596 | } |
f9cf8797 | 597 | return _.escape(result); |
25523059 CW |
598 | } |
599 | ||
25523059 CW |
600 | $scope.fieldsForGroupBy = function() { |
601 | return {results: getAllFields('', function(key) { | |
2894db84 | 602 | return _.contains(ctrl.savedSearch.api_params.groupBy, key); |
25523059 CW |
603 | }) |
604 | }; | |
605 | }; | |
606 | ||
607 | $scope.fieldsForSelect = function() { | |
608 | return {results: getAllFields(':label', function(key) { | |
2894db84 | 609 | return _.contains(ctrl.savedSearch.api_params.select, key); |
25523059 CW |
610 | }) |
611 | }; | |
612 | }; | |
613 | ||
614 | $scope.fieldsForWhere = function() { | |
615 | return {results: getAllFields(':name', _.noop)}; | |
616 | }; | |
617 | ||
618 | $scope.fieldsForHaving = function() { | |
2894db84 | 619 | return {results: _.transform(ctrl.savedSearch.api_params.select, function(fields, name) { |
25523059 CW |
620 | fields.push({id: name, text: ctrl.getFieldLabel(name)}); |
621 | })}; | |
622 | }; | |
623 | ||
4b01551f CW |
624 | $scope.sortableColumnOptions = { |
625 | axis: 'x', | |
44402a2e | 626 | handle: '.crm-draggable', |
4b01551f CW |
627 | update: function(e, ui) { |
628 | // Don't allow items to be moved to position 0 if locked | |
629 | if (!ui.item.sortable.dropindex && ctrl.groupExists) { | |
630 | ui.item.sortable.cancel(); | |
631 | } | |
632 | } | |
633 | }; | |
634 | ||
635 | // Sets the default select clause based on commonly-named fields | |
25523059 | 636 | function getDefaultSelect() { |
4b01551f CW |
637 | var whitelist = ['id', 'name', 'subject', 'display_name', 'label', 'title']; |
638 | return _.transform(searchMeta.getEntity(ctrl.savedSearch.api_entity).fields, function(select, field) { | |
639 | if (_.includes(whitelist, field.name) || _.includes(field.name, '_type_id')) { | |
640 | select.push(field.name + (field.options ? ':label' : '')); | |
641 | } | |
c419e6ed | 642 | }); |
25523059 CW |
643 | } |
644 | ||
645 | function getAllFields(suffix, disabledIf) { | |
646 | function formatFields(entityName, prefix) { | |
647 | return _.transform(searchMeta.getEntity(entityName).fields, function(result, field) { | |
648 | var item = { | |
649 | id: prefix + field.name + (field.options ? suffix : ''), | |
b6b6cb2d | 650 | text: field.label, |
25523059 CW |
651 | description: field.description |
652 | }; | |
653 | if (disabledIf(item.id)) { | |
654 | item.disabled = true; | |
655 | } | |
656 | result.push(item); | |
657 | }, []); | |
658 | } | |
659 | ||
2894db84 | 660 | var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), |
25523059 | 661 | result = [{ |
9813ae79 | 662 | text: mainEntity.title_plural, |
25523059 | 663 | icon: mainEntity.icon, |
2894db84 | 664 | children: formatFields(ctrl.savedSearch.api_entity, '') |
25523059 | 665 | }]; |
2894db84 | 666 | _.each(ctrl.savedSearch.api_params.join, function(join) { |
25523059 CW |
667 | var joinName = join[0].split(' AS '), |
668 | joinEntity = searchMeta.getEntity(joinName[0]); | |
669 | result.push({ | |
9813ae79 | 670 | text: joinEntity.title_plural + ' (' + joinName[1] + ')', |
25523059 CW |
671 | icon: joinEntity.icon, |
672 | children: formatFields(joinEntity.name, joinName[1] + '.') | |
673 | }); | |
674 | }); | |
675 | return result; | |
676 | } | |
677 | ||
678 | /** | |
679 | * Fetch pseudoconstants for main entity + joined entities | |
680 | * | |
681 | * Sets an optionsLoaded property on each entity to avoid duplicate requests | |
682 | */ | |
683 | function loadFieldOptions() { | |
2894db84 | 684 | var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), |
25523059 CW |
685 | entities = {}; |
686 | ||
687 | function enqueue(entity) { | |
688 | entity.optionsLoaded = false; | |
689 | entities[entity.name] = [entity.name, 'getFields', { | |
22601c92 | 690 | loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'], |
25523059 CW |
691 | where: [['options', '!=', false]], |
692 | select: ['options'] | |
693 | }, {name: 'options'}]; | |
694 | } | |
695 | ||
696 | if (typeof mainEntity.optionsLoaded === 'undefined') { | |
697 | enqueue(mainEntity); | |
698 | } | |
2894db84 | 699 | _.each(ctrl.savedSearch.api_params.join, function(join) { |
25523059 CW |
700 | var joinName = join[0].split(' AS '), |
701 | joinEntity = searchMeta.getEntity(joinName[0]); | |
702 | if (typeof joinEntity.optionsLoaded === 'undefined') { | |
703 | enqueue(joinEntity); | |
704 | } | |
705 | }); | |
706 | if (!_.isEmpty(entities)) { | |
707 | crmApi4(entities).then(function(results) { | |
708 | _.each(results, function(fields, entityName) { | |
709 | var entity = searchMeta.getEntity(entityName); | |
710 | _.each(fields, function(options, fieldName) { | |
711 | _.find(entity.fields, {name: fieldName}).options = options; | |
712 | }); | |
713 | entity.optionsLoaded = true; | |
714 | }); | |
715 | }); | |
716 | } | |
717 | } | |
718 | ||
25523059 CW |
719 | } |
720 | }); | |
721 | ||
722 | })(angular, CRM.$, CRM._); |