Breaks search displays into their own modules, one per display type.
This gives finer-grained dependency management per display type.
box-sizing: content-box;
}
+.crm-container .crm-inline-block {
+ display: inline-block;
+}
+
div.crm-container label {
font-weight: normal;
display: inline;
'ang/crmSearchAdmin',
],
'basePages' => ['civicrm/admin/search'],
- 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'crmSearchDisplay', 'crmSearchActions', 'crmSearchKit', 'crmRouteBinder'],
+ 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'crmSearchActions', 'crmSearchKit', 'crmRouteBinder'],
'settingsFactory' => ['\Civi\Search\Admin', 'getAdminSettings'],
];
return field;
}
}
+ function parseExpr(expr) {
+ var result = {fn: null, modifier: ''},
+ fieldName = expr,
+ bracketPos = expr.indexOf('(');
+ if (bracketPos >= 0) {
+ var parsed = expr.substr(bracketPos).match(/[ ]?([A-Z]+[ ]+)?([\w.:]+)/);
+ fieldName = parsed[2];
+ result.fn = _.find(CRM.crmSearchAdmin.functions, {name: expr.substring(0, bracketPos)});
+ result.modifier = _.trim(parsed[1]);
+ }
+ result.field = expr ? getField(fieldName, searchEntity) : undefined;
+ if (result.field) {
+ var split = fieldName.split(':'),
+ prefixPos = split[0].lastIndexOf(result.field.name);
+ result.path = split[0];
+ result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
+ result.suffix = !split[1] ? '' : ':' + split[1];
+ }
+ return result;
+ }
return {
getEntity: getEntity,
getField: getField,
- parseExpr: function(expr) {
- var result = {fn: null, modifier: ''},
- fieldName = expr,
- bracketPos = expr.indexOf('(');
- if (bracketPos >= 0) {
- var parsed = expr.substr(bracketPos).match(/[ ]?([A-Z]+[ ]+)?([\w.:]+)/);
- fieldName = parsed[2];
- result.fn = _.find(CRM.crmSearchAdmin.functions, {name: expr.substring(0, bracketPos)});
- result.modifier = _.trim(parsed[1]);
- }
- result.field = expr ? getField(fieldName, searchEntity) : undefined;
- if (result.field) {
- var split = fieldName.split(':'),
- prefixPos = split[0].lastIndexOf(result.field.name);
- result.path = split[0];
- result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
- result.suffix = !split[1] ? '' : ':' + split[1];
+ parseExpr: parseExpr,
+ getDefaultLabel: function(col) {
+ var info = parseExpr(col),
+ label = info.field.label;
+ if (info.fn) {
+ label = '(' + info.fn.title + ') ' + label;
}
- return result;
+ return label;
},
// Find all possible search columns that could serve as contact_id for a smart group
getSmartGroupColumns: function(api_entity, api_params) {
return ctrl.allRowsSelected || _.includes(ctrl.selectedRows, row.id);
};
- this.getFieldLabel = function(col) {
- var info = searchMeta.parseExpr(col),
- label = info.field.label;
- if (info.fn) {
- label = '(' + info.fn.title + ') ' + label;
- }
- return label;
- };
+ this.getFieldLabel = searchMeta.getDefaultLabel;
// Is a column eligible to use an aggregate function?
this.canAggregate = function(col) {
html += '</div>';
return html;
},
- controller: function($scope, $timeout) {
+ controller: function($scope, $timeout, searchMeta) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
+ function fieldToColumn(fieldExpr) {
+ var info = searchMeta.parseExpr(fieldExpr);
+ return {
+ expr: fieldExpr,
+ label: searchMeta.getDefaultLabel(fieldExpr),
+ dataType: (info.fn && info.fn.name === 'COUNT') ? 'Integer' : info.field.data_type
+ };
+ }
+
+ // Helper function to sort active from hidden columns and initialize each column with defaults
+ this.initColumns = function() {
+ if (!ctrl.display.settings.columns) {
+ ctrl.display.settings.columns = _.transform(ctrl.savedSearch.api_params.select, function(columns, fieldExpr) {
+ columns.push(fieldToColumn(fieldExpr));
+ });
+ return [];
+ } else {
+ var activeColumns = _.collect(ctrl.display.settings.columns, 'expr'),
+ hiddenColumns = _.transform(ctrl.savedSearch.api_params.select, function(hiddenColumns, fieldExpr) {
+ if (!_.includes(activeColumns, fieldExpr)) {
+ hiddenColumns.push(fieldToColumn(fieldExpr));
+ }
+ });
+ _.each(activeColumns, function(fieldExpr, index) {
+ if (!_.includes(ctrl.savedSearch.api_params.select, fieldExpr)) {
+ ctrl.display.settings.columns.splice(index, 1);
+ }
+ });
+ return hiddenColumns;
+ }
+ };
+
+ // Return all possible links to main entity or join entities
+ this.getLinks = function() {
+ var links = _.cloneDeep(searchMeta.getEntity(ctrl.savedSearch.api_entity).paths || []);
+ _.each(ctrl.savedSearch.api_params.join, function(join) {
+ var joinName = join[0].split(' AS '),
+ joinEntity = searchMeta.getEntity(joinName[0]);
+ _.each(joinEntity.paths, function(path) {
+ var link = _.cloneDeep(path);
+ link.path = link.path.replace(/\[/g, '[' + joinName[1] + '.');
+ links.push(link);
+ });
+ });
+ return links;
+ };
+
this.preview = this.stale = false;
this.previewDisplay = function() {
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('crmSearchAdmin').component('searchAdminDisplayList', {
+ bindings: {
+ display: '<',
+ apiEntity: '<',
+ apiParams: '<'
+ },
+ require: {
+ crmSearchAdminDisplay: '^crmSearchAdminDisplay'
+ },
+ templateUrl: '~/crmSearchAdmin/displays/searchAdminDisplayList.html',
+ controller: function($scope, searchMeta) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+ this.getFieldLabel = searchMeta.getDefaultLabel;
+
+ this.sortableOptions = {
+ connectWith: '.crm-search-admin-edit-columns',
+ containment: '.crm-search-admin-edit-columns-wrapper'
+ };
+
+ this.removeCol = function(index) {
+ ctrl.hiddenColumns.push(ctrl.display.settings.columns[index]);
+ ctrl.display.settings.columns.splice(index, 1);
+ };
+
+ this.restoreCol = function(index) {
+ ctrl.display.settings.columns.push(ctrl.hiddenColumns[index]);
+ ctrl.hiddenColumns.splice(index, 1);
+ };
+
+ this.symbols = {
+ ul: [
+ {char: '', label: ts('Default')},
+ {char: 'none', label: ts('None ( )')},
+ {char: 'circle', label: ts('Circle')},
+ {char: 'square', label: ts('Square')},
+ ],
+ ol: [
+ {char: '', label: ts('Numbered (1. 2. 3.)')},
+ {char: 'none', label: ts('None ( )')},
+ {char: 'lower-latin', label: ts('Lowercase (a. b. c.)')},
+ {char: 'upper-latin', label: ts('Uppercase (A. B. C.)')},
+ {char: 'upper-roman', label: ts('Roman (I. II. III.)')},
+ ]
+ };
+
+ this.$onInit = function () {
+ if (!ctrl.display.settings) {
+ ctrl.display.settings = {
+ style: 'ul',
+ limit: 20,
+ pager: true
+ };
+ }
+ ctrl.hiddenColumns = ctrl.crmSearchAdminDisplay.initColumns();
+ ctrl.links = ctrl.crmSearchAdminDisplay.getLinks();
+ };
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<fieldset>
+ <div class="form-inline">
+ <label for="crm-search-admin-display-style">{{:: ts('Style:') }}</label>
+ <select id="crm-search-admin-display-style" class="form-control" ng-model="$ctrl.display.settings.style" ng-change="$ctrl.display.settings.symbol = ''">
+ <option value="ul">{{:: ts('Bulleted') }}</option>
+ <option value="ol">{{:: ts('Numbered') }}</option>
+ </select>
+ <label for="crm-search-admin-display-symbol">{{:: ts('Symbol:') }}</label>
+ <select id="crm-search-admin-display-symbol" class="form-control" ng-model="$ctrl.display.settings.symbol">
+ <option ng-repeat="symbol in $ctrl.symbols[$ctrl.display.settings.style]" value="{{ symbol.char }}">
+ {{ symbol.label }}
+ </option>
+ </select>
+ <div class="form-inline">
+ </div>
+ <label for="crm-search-admin-display-limit">{{:: ts('Results to display (0 for no limit):') }}</label>
+ <input id="crm-search-admin-display-limit" type="number" min="0" step="1" class="form-control" ng-model="$ctrl.display.settings.limit">
+ <label><input type="checkbox" ng-model="$ctrl.display.settings.pager"> {{:: ts('Use Pager') }}</label>
+ </div>
+</fieldset>
+<div class="crm-flex-box crm-search-admin-edit-columns-wrapper">
+ <fieldset class="crm-search-admin-edit-columns" ng-model="$ctrl.display.settings.columns" ui-sortable="$ctrl.sortableOptions">
+ <legend>{{:: ts('Fields') }}</legend>
+ <fieldset ng-repeat="col in $ctrl.display.settings.columns" class="crm-draggable">
+ <legend>{{ $ctrl.getFieldLabel(col.expr) }}</legend>
+ <div class="form-inline">
+ <label>{{:: ts('Label:') }}</label> <input class="form-control" type="text" ng-model="col.label" >
+ <label ng-show="col.label.length" title="{{:: ts('Show label for every record even when this field is blank') }}"><input type="checkbox" ng-model="col.forceLabel"> {{:: ts('Always show') }}</label>
+ <button class="btn-xs pull-right" ng-click="$ctrl.removeCol($index)" title="{{:: ts('Hide') }}">
+ <i class="crm-i fa-ban"></i>
+ </button>
+ </div>
+ <div class="form-inline">
+ <label>{{:: ts('Prefix:') }}</label>
+ <input class="form-control" ng-model="col.prefix" size="4">
+ <label>{{:: ts('Suffix:') }}</label>
+ <input class="form-control" ng-model="col.suffix" size="4">
+ <label><input type="checkbox" ng-model="col.break"> {{:: ts('New line') }}</label>
+ </div>
+ <div class="form-inline">
+ <label>{{:: ts('Link:') }}</label>
+ <crm-search-admin-link-select column="col" links="$ctrl.links"></crm-search-admin-link-select>
+ </div>
+ <div class="form-inline">
+ <label>{{:: ts('Tooltip:') }}</label>
+ <input class="form-control" type="text" ng-model="col.title" />
+ </div>
+ </fieldset>
+ </fieldset>
+ <fieldset class="crm-search-admin-edit-columns" ng-model="$ctrl.hiddenColumns" ui-sortable="$ctrl.sortableOptions">
+ <legend>{{:: ts('Hidden Fields') }}</legend>
+ <fieldset ng-repeat="col in $ctrl.hiddenColumns" class="crm-draggable">
+ <legend>{{ $ctrl.getFieldLabel(col.expr) }}</legend>
+ <div class="form-inline">
+ <label>{{:: ts('Label:') }}</label> <input disabled class="form-control" type="text" ng-model="col.label" />
+ <button class="btn-xs pull-right" ng-click="$ctrl.restoreCol($index)" title="{{:: ts('Show') }}">
+ <i class="crm-i fa-undo"></i>
+ </button>
+ </div>
+ </fieldset>
+ </fieldset>
+</div>
apiParams: '<'
},
require: {
- crmSearchAdmin: '^crmSearchAdmin'
+ crmSearchAdminDisplay: '^crmSearchAdminDisplay'
},
templateUrl: '~/crmSearchAdmin/displays/searchAdminDisplayTable.html',
controller: function($scope, searchMeta) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
-
- function fieldToColumn(fieldExpr) {
- var info = searchMeta.parseExpr(fieldExpr);
- return {
- expr: fieldExpr,
- label: ctrl.getFieldLabel(fieldExpr),
- dataType: (info.fn && info.fn.name === 'COUNT') ? 'Integer' : info.field.data_type
- };
- }
+ this.getFieldLabel = searchMeta.getDefaultLabel;
this.sortableOptions = {
connectWith: '.crm-search-admin-edit-columns',
};
this.$onInit = function () {
- ctrl.getFieldLabel = ctrl.crmSearchAdmin.getFieldLabel;
if (!ctrl.display.settings) {
ctrl.display.settings = {
limit: 20,
pager: true
};
}
- if (!ctrl.display.settings.columns) {
- ctrl.display.settings.columns = _.transform(ctrl.apiParams.select, function(columns, fieldExpr) {
- columns.push(fieldToColumn(fieldExpr));
- });
- ctrl.hiddenColumns = [];
- } else {
- var activeColumns = _.collect(ctrl.display.settings.columns, 'expr');
- ctrl.hiddenColumns = _.transform(ctrl.apiParams.select, function(hiddenColumns, fieldExpr) {
- if (!_.includes(activeColumns, fieldExpr)) {
- hiddenColumns.push(fieldToColumn(fieldExpr));
- }
- });
- _.each(activeColumns, function(fieldExpr, index) {
- if (!_.includes(ctrl.apiParams.select, fieldExpr)) {
- ctrl.display.settings.columns.splice(index, 1);
- }
- });
- }
- ctrl.links = _.cloneDeep(searchMeta.getEntity(ctrl.apiEntity).paths || []);
- _.each(ctrl.apiParams.join, function(join) {
- var joinName = join[0].split(' AS '),
- joinEntity = searchMeta.getEntity(joinName[0]);
- _.each(joinEntity.paths, function(path) {
- var link = _.cloneDeep(path);
- link.path = link.path.replace(/\[/g, '[' + joinName[1] + '.');
- ctrl.links.push(link);
- });
- });
+ ctrl.hiddenColumns = ctrl.crmSearchAdminDisplay.initColumns();
+ ctrl.links = ctrl.crmSearchAdminDisplay.getLinks();
};
}
<fieldset>
<div class="form-inline">
- <label for="crm-search-admin-table-limit">{{ ts('Results to display (0 for no limit):') }}</label>
- <input id="crm-search-admin-table-limit" type="number" min="0" step="1" class="form-control" ng-model="$ctrl.display.settings.limit">
- <label><input type="checkbox" ng-model="$ctrl.display.settings.pager"> {{ ts('Use Pager') }}</label>
- <label><input type="checkbox" ng-model="$ctrl.display.settings.actions"> {{ ts('Enable Actions') }}</label>
+ <label for="crm-search-admin-display-limit">{{:: ts('Results to display (0 for no limit):') }}</label>
+ <input id="crm-search-admin-display-limit" type="number" min="0" step="1" class="form-control" ng-model="$ctrl.display.settings.limit">
+ <label><input type="checkbox" ng-model="$ctrl.display.settings.pager"> {{:: ts('Use Pager') }}</label>
+ <label><input type="checkbox" ng-model="$ctrl.display.settings.actions"> {{:: ts('Enable Actions') }}</label>
</div>
</fieldset>
<div class="crm-flex-box crm-search-admin-edit-columns-wrapper">
<fieldset ng-repeat="col in $ctrl.display.settings.columns" class="crm-draggable">
<legend>{{ $ctrl.getFieldLabel(col.expr) }}</legend>
<div class="form-inline">
- <label>{{ ts('Label:') }}</label> <input class="form-control" type="text" ng-model="col.label" />
+ <label>{{:: ts('Label:') }}</label> <input class="form-control" type="text" ng-model="col.label" />
<button class="btn-xs pull-right" ng-click="$ctrl.removeCol($index)" title="{{:: ts('Hide') }}">
<i class="crm-i fa-ban"></i>
</button>
</div>
<div class="form-inline">
- <label>{{ ts('Link:') }}</label>
+ <label>{{:: ts('Alignment:') }}</label>
+ <select ng-model="col.alignment" class="form-control">
+ <option value="">{{:: ts('Left') }}</option>
+ <option value="text-center">{{:: ts('Center') }}</option>
+ <option value="text-right">{{:: ts('Right') }}</option>
+ </select>
+ </div>
+ <div class="form-inline">
+ <label>{{:: ts('Link:') }}</label>
<crm-search-admin-link-select column="col" links="$ctrl.links"></crm-search-admin-link-select>
</div>
<div class="form-inline">
- <label>{{ ts('Tooltip:') }}</label>
+ <label>{{:: ts('Tooltip:') }}</label>
<input class="form-control" type="text" ng-model="col.title" />
</div>
</fieldset>
<fieldset ng-repeat="col in $ctrl.hiddenColumns" class="crm-draggable">
<legend>{{ $ctrl.getFieldLabel(col.expr) }}</legend>
<div class="form-inline">
- <label>{{ ts('Label:') }}</label> <input disabled class="form-control" type="text" ng-model="col.label" />
+ <label>{{:: ts('Label:') }}</label> <input disabled class="form-control" type="text" ng-model="col.label" />
<button class="btn-xs pull-right" ng-click="$ctrl.restoreCol($index)" title="{{:: ts('Show') }}">
<i class="crm-i fa-undo"></i>
</button>
<?php
-// Search Display module - for rendering search displays.
+// Search Display base module - provides services used commonly by search display implementations.
return [
'js' => [
'ang/crmSearchDisplay.module.js',
'ang/crmSearchDisplay',
],
'basePages' => [],
- 'requires' => ['ngSanitize', 'crmUi', 'api4', 'crmSearchActions', 'ui.bootstrap'],
+ 'requires' => ['api4', 'ngSanitize'],
'exports' => [
'crm-search-display-table' => 'E',
],
"use strict";
// Declare module
- angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'));
+ angular.module('crmSearchDisplay', CRM.angRequires('crmSearchDisplay'))
+
+ .factory('formatSearchValue', function() {
+ function getUrl(link, row) {
+ var url = replaceTokens(link, row);
+ if (url.slice(0, 1) !== '/' && url.slice(0, 4) !== 'http') {
+ url = CRM.url(url);
+ }
+ return _.escape(url);
+ }
+
+ function replaceTokens(str, data) {
+ _.each(data, function(value, key) {
+ str = str.replace('[' + key + ']', value);
+ });
+ return str;
+ }
+
+ return function formatSearchValue(row, col, value) {
+ var type = col.dataType,
+ result = value;
+ if (_.isArray(value)) {
+ return _.map(value, function(val) {
+ return formatSearchValue(col, val);
+ }).join(', ');
+ }
+ if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) {
+ result = CRM.utils.formatDate(value, null, type === 'Timestamp');
+ }
+ else if (type === 'Boolean' && typeof value === 'boolean') {
+ result = value ? ts('Yes') : ts('No');
+ }
+ else if (type === 'Money' && typeof value === 'number') {
+ result = CRM.formatMoney(value);
+ }
+ result = _.escape(result);
+ if (col.link) {
+ result = '<a href="' + getUrl(col.link, row) + '">' + result + '</a>';
+ }
+ return result;
+ };
+ })
+
+ .factory('searchDisplayFieldCanAggregate', function() {
+ return function searchDisplayFieldCanAggregate(fieldName, prefix, apiParams) {
+ // If the query does not use grouping, never
+ if (!apiParams.groupBy.length) {
+ return false;
+ }
+ // If the column is used for a groupBy, no
+ if (apiParams.groupBy.indexOf(prefix + fieldName) > -1) {
+ return false;
+ }
+ // If the entity this column belongs to is being grouped by id, then also no
+ return apiParams.groupBy.indexOf(prefix + 'id') < 0;
+ };
+ });
})(angular, CRM.$, CRM._);
--- /dev/null
+<div class="text-center" ng-if="$ctrl.rowCount && $ctrl.settings.pager">
+ <ul uib-pagination
+ class="pagination"
+ boundary-links="true"
+ total-items="$ctrl.rowCount"
+ ng-model="$ctrl.page"
+ ng-change="$ctrl.getResults()"
+ items-per-page="$ctrl.limit"
+ max-size="6"
+ force-ellipses="true"
+ previous-text="‹"
+ next-text="›"
+ first-text="«"
+ last-text="»"
+ ></ul>
+</div>
--- /dev/null
+<?php
+// Module for rendering List Search Displays.
+return [
+ 'js' => [
+ 'ang/crmSearchDisplayList.module.js',
+ 'ang/crmSearchDisplayList/*.js',
+ ],
+ 'partials' => [
+ 'ang/crmSearchDisplayList',
+ ],
+ 'basePages' => ['civicrm/search', 'civicrm/admin/search'],
+ 'requires' => ['crmSearchDisplay', 'crmUi', 'ui.bootstrap'],
+ 'exports' => [
+ 'crm-search-display-list' => 'E',
+ ],
+];
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ // Declare module
+ angular.module('crmSearchDisplayList', CRM.angRequires('crmSearchDisplayList'));
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('crmSearchDisplayList').component('crmSearchDisplayList', {
+ bindings: {
+ apiEntity: '<',
+ apiParams: '<',
+ settings: '<',
+ filters: '<'
+ },
+ templateUrl: '~/crmSearchDisplayList/crmSearchDisplayList.html',
+ controller: function($scope, crmApi4, formatSearchValue, searchDisplayFieldCanAggregate) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+ this.page = 1;
+
+ this.$onInit = function() {
+ this.limit = parseInt(ctrl.settings.limit || 0, 10);
+ ctrl.columns = _.cloneDeep(ctrl.settings.columns);
+ _.each(ctrl.columns, function(col, num) {
+ var index = ctrl.apiParams.select.indexOf(col.expr);
+ if (_.includes(col.expr, '(') && !_.includes(col.expr, ' AS ')) {
+ col.expr += ' AS column_' + num;
+ ctrl.apiParams.select[index] += ' AS column_' + num;
+ }
+ col.key = _.last(col.expr.split(' AS '));
+ });
+ };
+
+ this.getResults = function() {
+ var params = _.merge(_.cloneDeep(ctrl.apiParams), {limit: ctrl.limit, offset: (ctrl.page - 1) * ctrl.limit});
+ if (_.isEmpty(params.where)) {
+ params.where = [];
+ }
+ // Select the ids of joined entities (helps with displaying links)
+ _.each(params.join, function(join) {
+ var joinEntity = join[0].split(' AS ')[1],
+ idField = joinEntity + '.id';
+ if (!_.includes(params.select, idField) && !searchDisplayFieldCanAggregate('id', joinEntity + '.', params)) {
+ params.select.push(idField);
+ }
+ });
+ _.each(ctrl.filters, function(value, key) {
+ if (value) {
+ params.where.push([key, 'CONTAINS', value]);
+ }
+ });
+ if (ctrl.settings.pager) {
+ params.select.push('row_count');
+ }
+ crmApi4(ctrl.apiEntity, 'get', params).then(function(results) {
+ ctrl.results = results;
+ ctrl.rowCount = results.count;
+ });
+ };
+
+ $scope.$watch('$ctrl.filters', ctrl.getResults, true);
+
+ $scope.formatResult = function(row, col) {
+ var value = row[col.key],
+ formatted = formatSearchValue(row, col, value),
+ output = '';
+ if (formatted.length || (col.label && col.forceLabel)) {
+ if (col.label && (formatted.length || col.forceLabel)) {
+ output += '<label>' + _.escape(col.label) + '</label> ';
+ }
+ if (formatted.length) {
+ output += (col.prefix || '') + formatted + (col.suffix || '');
+ }
+ }
+ return output;
+ };
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<ol ng-if=":: $ctrl.settings.style === 'ol'" ng-include="'~/crmSearchDisplayList/crmSearchDisplayListItems.html'" ng-style="{'list-style': $ctrl.settings.symbol}"></ol>
+<ul ng-if=":: $ctrl.settings.style !== 'ol'" ng-include="'~/crmSearchDisplayList/crmSearchDisplayListItems.html'" ng-style="{'list-style': $ctrl.settings.symbol}"></ul>
+<div ng-include="'~/crmSearchDisplay/Pager.html'"></div>
--- /dev/null
+<li ng-repeat="row in $ctrl.results">
+ <div ng-repeat="col in $ctrl.columns" ng-bind-html="formatResult(row, col)" title="{{:: col.title }}" class="{{:: col.break ? '' : 'crm-inline-block' }}">
+ </div>
+</li>
--- /dev/null
+<?php
+// Module for rendering Table Search Displays.
+return [
+ 'js' => [
+ 'ang/crmSearchDisplayTable.module.js',
+ 'ang/crmSearchDisplayTable/*.js',
+ ],
+ 'partials' => [
+ 'ang/crmSearchDisplayTable',
+ ],
+ 'basePages' => ['civicrm/search', 'civicrm/admin/search'],
+ 'requires' => ['crmSearchDisplay', 'crmUi', 'crmSearchActions', 'ui.bootstrap'],
+ 'exports' => [
+ 'crm-search-display-table' => 'E',
+ ],
+];
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ // Declare module
+ angular.module('crmSearchDisplayTable', CRM.angRequires('crmSearchDisplayTable'));
+
+})(angular, CRM.$, CRM._);
(function(angular, $, _) {
"use strict";
- angular.module('crmSearchDisplay').component('crmSearchDisplayTable', {
+ angular.module('crmSearchDisplayTable').component('crmSearchDisplayTable', {
bindings: {
apiEntity: '<',
apiParams: '<',
settings: '<',
filters: '<'
},
- templateUrl: '~/crmSearchDisplay/crmSearchDisplayTable.html',
- controller: function($scope, crmApi4) {
+ templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayTable.html',
+ controller: function($scope, crmApi4, formatSearchValue, searchDisplayFieldCanAggregate) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
_.each(params.join, function(join) {
var joinEntity = join[0].split(' AS ')[1],
idField = joinEntity + '.id';
- if (!_.includes(params.select, idField) && !canAggregate('id', joinEntity + '.')) {
+ if (!_.includes(params.select, idField) && !searchDisplayFieldCanAggregate('id', joinEntity + '.', params)) {
params.select.push(idField);
}
});
$scope.formatResult = function(row, col) {
var value = row[col.key];
- return formatFieldValue(row, col, value);
+ return formatSearchValue(row, col, value);
};
- function formatFieldValue(row, col, value) {
- var type = col.dataType,
- result = value;
- if (_.isArray(value)) {
- return _.map(value, function(val) {
- return formatFieldValue(col, val);
- }).join(', ');
- }
- if (value && (type === 'Date' || type === 'Timestamp') && /^\d{4}-\d{2}-\d{2}/.test(value)) {
- result = CRM.utils.formatDate(value, null, type === 'Timestamp');
- }
- else if (type === 'Boolean' && typeof value === 'boolean') {
- result = value ? ts('Yes') : ts('No');
- }
- else if (type === 'Money' && typeof value === 'number') {
- result = CRM.formatMoney(value);
- }
- result = _.escape(result);
- if (col.link) {
- result = '<a href="' + getUrl(col.link, row) + '">' + result + '</a>';
- }
- return result;
- }
-
- function getUrl(link, row) {
- var url = replaceTokens(link, row);
- if (url.slice(0, 1) !== '/' && url.slice(0, 4) !== 'http') {
- url = CRM.url(url);
- }
- return _.escape(url);
- }
-
- function replaceTokens(str, data) {
- _.each(data, function(value, key) {
- str = str.replace('[' + key + ']', value);
- });
- return str;
- }
-
- function canAggregate(fieldName, prefix) {
- // If the query does not use grouping, never
- if (!ctrl.apiParams.groupBy.length) {
- return false;
- }
- // If the column is used for a groupBy, no
- if (ctrl.apiParams.groupBy.indexOf(prefix + fieldName) > -1) {
- return false;
- }
- // If the entity this column belongs to is being grouped by id, then also no
- return ctrl.apiParams.groupBy.indexOf(prefix + 'id') < 0;
- }
-
$scope.selectAllRows = function() {
// Deselect all
if (ctrl.allRowsSelected) {
<td ng-if="$ctrl.settings.actions">
<input type="checkbox" ng-checked="isRowSelected(row)" ng-click="selectRow(row)" ng-disabled="!(!loadingAllRows && row.id)">
</td>
- <td ng-repeat="col in $ctrl.columns" ng-bind-html="formatResult(row, col)" title="{{:: col.title }}">
+ <td ng-repeat="col in $ctrl.columns" ng-bind-html="formatResult(row, col)" title="{{:: col.title }}" class="{{:: col.alignment }}">
</td>
<td></td>
</tr>
</tbody>
</table>
-<div class="text-center" ng-if="$ctrl.rowCount && $ctrl.settings.pager">
- <ul uib-pagination
- class="pagination"
- boundary-links="true"
- total-items="$ctrl.rowCount"
- ng-model="$ctrl.page"
- ng-change="$ctrl.getResults()"
- items-per-page="$ctrl.limit"
- max-size="6"
- force-ellipses="true"
- previous-text="‹"
- next-text="›"
- first-text="«"
- last-text="»"
- ></ul>
-</div>
+<div ng-include="'~/crmSearchDisplay/Pager.html'"></div>
'ang/crmSearchKit',
],
'basePages' => [],
- 'requires' => ['api4'],
+ 'requires' => [],
];
'ang/crmSearchPage',
],
'basePages' => ['civicrm/search'],
- 'requires' => ['ngRoute', 'api4', 'crmUi', 'crmSearchDisplay'],
+ 'requires' => ['ngRoute', 'api4', 'crmUi'],
'settingsFactory' => ['\Civi\Search\Display', 'getPageSettings'],
];
'icon' => 'fa-table',
],
],
+ [
+ 'name' => 'SearchDisplayType:list',
+ 'entity' => 'OptionValue',
+ 'params' => [
+ 'option_group_id' => 'search_display_type',
+ 'name' => 'list',
+ 'value' => 'list',
+ 'label' => 'List',
+ 'icon' => 'fa-list',
+ ],
+ ],
];