Merge pull request #21069 from colemanw/searchKitAdminResultsTable
[civicrm-core.git] / ext / search_kit / ang / crmSearchDisplay / traits / searchDisplayBaseTrait.service.js
CommitLineData
ab4e6f8a
CW
1(function(angular, $, _) {
2 "use strict";
3
4 // Trait provides base methods and properties common to all search display types
5 angular.module('crmSearchDisplay').factory('searchDisplayBaseTrait', function(crmApi4) {
6 var ts = CRM.ts('org.civicrm.search_kit');
7
8 // Replace tokens keyed to rowData.
9 // If rowMeta is provided, values will be formatted; if omitted, raw values will be provided.
10 function replaceTokens(str, rowData, rowMeta, index) {
11 if (!str) {
12 return '';
13 }
14 _.each(rowData, function(value, key) {
15 if (str.indexOf('[' + key + ']') >= 0) {
16 var column = rowMeta && _.findWhere(rowMeta, {key: key}),
17 val = column ? formatRawValue(column, value) : value,
18 replacement = angular.isArray(val) ? val[index || 0] : val;
19 str = str.replace(new RegExp(_.escapeRegExp('[' + key + ']', 'g')), replacement);
20 }
21 });
22 return str;
23 }
24
25 function getUrl(link, rowData, index) {
26 var url = replaceTokens(link, rowData, null, index);
27 if (url.slice(0, 1) !== '/' && url.slice(0, 4) !== 'http') {
28 url = CRM.url(url);
29 }
30 return url;
31 }
32
33 // Returns display value for a single column in a row
34 function formatDisplayValue(rowData, key, columns) {
35 var column = _.findWhere(columns, {key: key}),
36 displayValue = column.rewrite ? replaceTokens(column.rewrite, rowData, columns) : formatRawValue(column, rowData[key]);
37 return angular.isArray(displayValue) ? displayValue.join(', ') : displayValue;
38 }
39
40 // Returns value and url for a column formatted as link(s)
41 function formatLinks(rowData, key, columns) {
42 var column = _.findWhere(columns, {key: key}),
43 value = formatRawValue(column, rowData[key]),
44 values = angular.isArray(value) ? value : [value],
45 links = [];
46 _.each(values, function(value, index) {
47 links.push({
48 value: value,
49 url: getUrl(column.link.path, rowData, index)
50 });
51 });
52 return links;
53 }
54
55 // Formats raw field value according to data type
56 function formatRawValue(column, value) {
57 var type = column && column.dataType,
58 result = value;
59 if (_.isArray(value)) {
60 return _.map(value, function(val) {
61 return formatRawValue(column, val);
62 });
63 }
64 if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) {
65 result = CRM.utils.formatDate(value, null, type === 'Timestamp');
66 }
67 else if (type === 'Boolean' && typeof value === 'boolean') {
68 result = value ? ts('Yes') : ts('No');
69 }
70 else if (type === 'Money' && typeof value === 'number') {
71 result = CRM.formatMoney(value);
72 }
73 return result;
74 }
75
76 // Return a base trait shared by all search display controllers
77 // Gets mixed in using angular.extend()
78 return {
79 page: 1,
80 rowCount: null,
81 getUrl: getUrl,
957358aa
CW
82 // Arrays may contain callback functions for various events
83 onChangeFilters: [],
84 onPreRun: [],
85 onPostRun: [],
ab4e6f8a
CW
86
87 // Called by the controller's $onInit function
88 initializeDisplay: function($scope, $element) {
89 var ctrl = this;
10cd9d37 90 this.limit = this.settings.limit;
ab4e6f8a
CW
91 this.sort = this.settings.sort ? _.cloneDeep(this.settings.sort) : [];
92
b7fd7a81 93 this.getResults = _.debounce(function() {
e362bd2f
CW
94 $scope.$apply(function() {
95 ctrl.runSearch();
96 });
ab4e6f8a
CW
97 }, 100);
98
99 // If search is embedded in contact summary tab, display count in tab-header
100 var contactTab = $element.closest('.crm-contact-page .ui-tabs-panel').attr('id');
101 if (contactTab) {
102 var unwatchCount = $scope.$watch('$ctrl.rowCount', function(rowCount) {
103 if (typeof rowCount === 'number') {
104 unwatchCount();
105 CRM.tabHeader.updateCount(contactTab.replace('contact-', '#tab_'), rowCount);
106 }
107 });
108 }
109
110 function onChangeFilters() {
111 ctrl.page = 1;
112 ctrl.rowCount = null;
957358aa
CW
113 _.each(ctrl.onChangeFilters, function(callback) {
114 callback.call(ctrl);
115 });
e362bd2f
CW
116 if (!ctrl.settings.button) {
117 ctrl.getResults();
118 }
ab4e6f8a
CW
119 }
120
10cd9d37
CW
121 function onChangePageSize() {
122 ctrl.page = 1;
e362bd2f
CW
123 // Only refresh if search has already been run
124 if (ctrl.results) {
125 ctrl.getResults();
126 }
10cd9d37
CW
127 }
128
ab4e6f8a
CW
129 if (this.afFieldset) {
130 $scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
131 }
10cd9d37
CW
132 if (this.settings.pager && this.settings.pager.expose_limit) {
133 $scope.$watch('$ctrl.limit', onChangePageSize);
134 }
ab4e6f8a
CW
135 $scope.$watch('$ctrl.filters', onChangeFilters, true);
136 },
137
138 // Generate params for the SearchDisplay.run api
139 getApiParams: function(mode) {
140 return {
141 return: mode || 'page:' + this.page,
142 savedSearch: this.search,
143 display: this.display,
144 sort: this.sort,
10cd9d37 145 limit: this.limit,
ab4e6f8a
CW
146 filters: _.assign({}, (this.afFieldset ? this.afFieldset.getFieldData() : {}), this.filters),
147 afform: this.afFieldset ? this.afFieldset.getFormName() : null
148 };
149 },
150
151 // Call SearchDisplay.run and update ctrl.results and ctrl.rowCount
b7fd7a81 152 runSearch: function() {
e362bd2f
CW
153 var ctrl = this,
154 apiParams = this.getApiParams();
155 this.loading = true;
957358aa
CW
156 _.each(ctrl.onPreRun, function(callback) {
157 callback.call(ctrl, apiParams);
158 });
e362bd2f 159 return crmApi4('SearchDisplay', 'run', apiParams).then(function(results) {
ab4e6f8a 160 ctrl.results = results;
e362bd2f 161 ctrl.editing = ctrl.loading = false;
ab4e6f8a 162 if (!ctrl.rowCount) {
10cd9d37 163 if (!ctrl.limit || results.length < ctrl.limit) {
ab4e6f8a
CW
164 ctrl.rowCount = results.length;
165 } else if (ctrl.settings.pager) {
166 var params = ctrl.getApiParams('row_count');
167 crmApi4('SearchDisplay', 'run', params).then(function(result) {
168 ctrl.rowCount = result.count;
169 });
170 }
171 }
957358aa
CW
172 _.each(ctrl.onPostRun, function(callback) {
173 callback.call(ctrl, results, 'success');
174 });
e362bd2f
CW
175 }, function(error) {
176 ctrl.results = [];
177 ctrl.editing = ctrl.loading = false;
957358aa
CW
178 _.each(ctrl.onPostRun, function(callback) {
179 callback.call(ctrl, error, 'error');
180 });
ab4e6f8a
CW
181 });
182 },
183 replaceTokens: function(value, row) {
184 return replaceTokens(value, row, this.settings.columns);
185 },
186 getLinks: function(rowData, col) {
187 rowData._links = rowData._links || {};
188 if (!(col.key in rowData._links)) {
189 rowData._links[col.key] = formatLinks(rowData, col.key, this.settings.columns);
190 }
191 return rowData._links[col.key];
192 },
193 formatFieldValue: function(rowData, col) {
194 return formatDisplayValue(rowData, col.key, this.settings.columns);
195 }
196 };
197 });
198
199})(angular, CRM.$, CRM._);