angular.module('searchActions').controller('SaveSmartGroup', function ($scope, $element, $timeout, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
- model = $scope.model,
- joins = _.pluck((model.api_params.join || []), 0),
- entityCount = {};
+ model = $scope.model;
$scope.groupEntityRefParams = {
entity: 'Group',
api: {
placeholder: ts('Select existing group')
}
};
- // Find all possible search columns that could serve as contact_id for the smart group
- $scope.columns = _.transform([model.api_entity].concat(joins), function(columns, joinExpr) {
- var joinName = joinExpr.split(' AS '),
- entityName = joinName[0],
- entity = searchMeta.getEntity(entityName),
- prefix = joinName[1] ? joinName[1] + '.' : '';
- _.each(entity.fields, function(field) {
- if ((entityName === 'Contact' && field.name === 'id') || field.fk_entity === 'Contact') {
- columns.push({
- id: prefix + field.name,
- text: entity.title_plural + (entityCount[entityName] ? ' ' + entityCount[entityName] : '') + ': ' + field.label,
- icon: entity.icon
- });
- }
- });
- entityCount[entityName] = 1 + (entityCount[entityName] || 1);
- });
+ $scope.columns = searchMeta.getSmartGroupColumns(model.api_entity, model.api_params);
if (!$scope.columns.length) {
CRM.alert(ts('Cannot create smart group; search does not include any contacts.'), ts('Error'));
'ang/searchAdmin',
],
'basePages' => [],
- 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'dialogService', 'api4', 'searchActions'],
+ 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'searchActions'],
'settingsFactory' => ['\Civi\Search\Admin', 'getAdminSettings'],
];
result.suffix = !split[1] ? '' : ':' + split[1];
}
return result;
+ },
+ // Find all possible search columns that could serve as contact_id for a smart group
+ getSmartGroupColumns: function(api_entity, api_params) {
+ var joins = _.pluck((api_params.join || []), 0),
+ entityCount = {};
+ return _.transform([api_entity].concat(joins), function(columns, joinExpr) {
+ var joinName = joinExpr.split(' AS '),
+ entityName = joinName[0],
+ entity = getEntity(entityName),
+ prefix = joinName[1] ? joinName[1] + '.' : '';
+ _.each(entity.fields, function(field) {
+ if ((entityName === 'Contact' && field.name === 'id') || field.fk_entity === 'Contact') {
+ columns.push({
+ id: prefix + field.name,
+ text: entity.titlePlural + (entityCount[entityName] ? ' ' + entityCount[entityName] : '') + ': ' + field.label,
+ icon: entity.icon
+ });
+ }
+ });
+ entityCount[entityName] = 1 + (entityCount[entityName] || 1);
+ });
}
};
})
<table>
<thead>
- <tr ng-model="$ctrl.savedSearch.api_params.select" ui-sortable="{axis: 'x'}">
+ <tr ng-model="$ctrl.savedSearch.api_params.select" ui-sortable="sortableColumnOptions">
<th class="crm-search-result-select">
<input type="checkbox" ng-checked="$ctrl.allRowsSelected" ng-click="selectAllRows()" ng-disabled="!(loading === false && !loadingAllRows && $ctrl.results[$ctrl.page] && $ctrl.results[$ctrl.page][0].id)">
</th>
<th ng-repeat="col in $ctrl.savedSearch.api_params.select" ng-click="setOrderBy(col, $event)" title="{{:: ts('Drag to reorder columns, click to sort results (shift-click to sort by multiple).')}}">
<i class="crm-i {{ getOrderBy(col) }}"></i>
- <span>{{ $ctrl.getFieldLabel(col) }}</span>
- <a href class="crm-hover-button" title="{{:: ts('Clear') }}" ng-click="$ctrl.clearParam('select', $index)"><i class="crm-i fa-times" aria-hidden="true"></i></a>
+ <span ng-class="{'crm-sortable': $index || !$ctrl.groupExists}">{{ $ctrl.getFieldLabel(col) }}</span>
+ <span ng-switch="$index || !$ctrl.groupExists ? 'sortable' : 'locked'">
+ <i ng-switch-when="locked" class="crm-i fa-lock" aria-hidden="true"></i>
+ <a href ng-switch-default class="crm-hover-button" title="{{:: ts('Clear') }}" ng-click="$ctrl.clearParam('select', $index)"><i class="crm-i fa-times" aria-hidden="true"></i></a>
+ </span>
</th>
<th class="form-inline">
<input class="form-control crm-action-menu fa-plus" ng-model="controls.select" crm-ui-select="::{data: fieldsForSelect, placeholder: ts('Add')}" ng-change="addParam('select')">
$scope.controls = {tab: 'compose'};
$scope.joinTypes = [{k: false, v: ts('Optional')}, {k: true, v: ts('Required')}];
+ $scope.groupOptions = CRM.searchActions.groupOptions;
$scope.entities = formatForSelect2(CRM.vars.search.schema, 'name', 'title_plural', ['description', 'icon']);
this.perm = {
editGroups: CRM.checkPerm('edit groups')
this.entityTitle = searchMeta.getEntity(this.savedSearch.api_entity).title_plural;
this.savedSearch.displays = this.savedSearch.displays || [];
+ this.groupExists = !!this.savedSearch.group;
+
+ this.original = _.indexBy(_.cloneDeep(this.savedSearch.displays), 'id');
+ if (this.savedSearch.group) {
+ this.original.group = _.cloneDeep(this.savedSearch.group);
+ }
if (!this.savedSearch.api_params) {
this.savedSearch.api_params = {
};
this.addDisplay = function(type) {
- $scope.controls.tab = 'display_' + ctrl.savedSearch.displays.length;
ctrl.savedSearch.displays.push({
type: type
});
+ $scope.selectTab('display_' + (ctrl.savedSearch.displays.length - 1));
+ };
+
+ this.removeDisplay = function(index) {
+ var display = ctrl.savedSearch.displays[index];
+ if (display.id) {
+ display.trashed = !display.trashed;
+ } else {
+ $scope.selectTab('compose');
+ ctrl.savedSearch.displays.splice(index, 1);
+ }
+ };
+
+ this.addGroup = function() {
+ ctrl.savedSearch.group = {
+ title: '',
+ description: '',
+ visibility: 'User and User Admin Only',
+ group_type: []
+ };
+ ctrl.groupExists = true;
+ $scope.selectTab('group');
+ };
+
+ $scope.selectTab = function(tab) {
+ if (tab === 'group') {
+ $scope.smartGroupColumns = searchMeta.getSmartGroupColumns(ctrl.savedSearch.api_entity, ctrl.savedSearch.api_params);
+ var smartGroupColumns = _.map($scope.smartGroupColumns, 'id');
+ if (smartGroupColumns.length && !_.includes(smartGroupColumns, ctrl.savedSearch.api_params.select[0])) {
+ ctrl.savedSearch.api_params.select.unshift(smartGroupColumns[0]);
+ }
+ }
+ ctrl.savedSearch.api_params.select = _.uniq(ctrl.savedSearch.api_params.select);
+ $scope.controls.tab = tab;
+ };
+
+ this.removeGroup = function() {
+ ctrl.groupExists = !ctrl.groupExists;
+ if (!ctrl.groupExists && (!ctrl.savedSearch.group || !ctrl.savedSearch.group.id)) {
+ ctrl.savedSearch.group = null;
+ }
+ $scope.selectTab('compose');
};
$scope.getJoinEntities = function() {
})};
};
+ $scope.sortableColumnOptions = {
+ axis: 'x',
+ handle: '.crm-sortable',
+ update: function(e, ui) {
+ // Don't allow items to be moved to position 0 if locked
+ if (!ui.item.sortable.dropindex && ctrl.groupExists) {
+ ui.item.sortable.cancel();
+ }
+ }
+ };
+
+ // Sets the default select clause based on commonly-named fields
function getDefaultSelect() {
- return _.filter(['id', 'display_name', 'label', 'title', 'location_type_id:label'], function(field) {
- return !!searchMeta.getField(field, ctrl.savedSearch.api_entity);
+ var whitelist = ['id', 'name', 'subject', 'display_name', 'label', 'title'];
+ return _.transform(searchMeta.getEntity(ctrl.savedSearch.api_entity).fields, function(select, field) {
+ if (_.includes(whitelist, field.name) || _.includes(field.name, '_type_id')) {
+ select.push(field.name + (field.options ? ':label' : ''));
+ }
});
}
<div id="bootstrap-theme" class="crm-search">
<h1 crm-page-title>{{:: ts('Search for %1', {1: $ctrl.entityTitle}) }}</h1>
+ <div crm-ui-debug="$ctrl.savedSearch"></div>
<!--This warning will show if bootstrap is unavailable. Normally it will be hidden by the bootstrap .collapse class.-->
<div class="messages warning no-popup collapse">
<div ng-include="'~/searchAdmin/compose/pager.html'"></div>
</div>
<div ng-switch-when="group">
+ <fieldset ng-include="'~/searchAdmin/group.html'"></fieldset>
</div>
<div ng-switch-default>
</div>
--- /dev/null
+<div ng-if="!$ctrl.groupExists">
+ <div class="alert alert-warning">
+ {{:: ts('Smart group "%1" will be deleted.', {1: $ctrl.original.group.title}) }}
+ </div>
+</div>
+<div ng-if="$ctrl.groupExists">
+ <div class="alert alert-warning" ng-show="!smartGroupColumns.length">
+ {{:: ts('Unable to create smart group because this search does not include any contacts.') }}
+ </div>
+ <input class="form-control" placeholder="{{:: ts('Group Title') }}" ng-model="$ctrl.savedSearch.group.title" ng-disabled="!smartGroupColumns.length">
+ <hr />
+ <div class="form-inline">
+ <label for="api-save-search-select-column">{{:: ts('Contact Column:') }} <span class="crm-marker">*</span></label>
+ <input id="api-save-search-select-column" ng-model="$ctrl.savedSearch.api_params.select[0]" class="form-control" crm-ui-select="{data: smartGroupColumns}"/>
+ </div>
+ <fieldset ng-show="smartGroupColumns.length">
+ <label>{{:: ts('Description:') }}</label>
+ <textarea class="form-control" ng-model="$ctrl.savedSearch.group.description"></textarea>
+ <div class="form-inline">
+ <label>{{:: ts('Group Type:') }} </label>
+ <div class="checkbox" ng-repeat="option in groupOptions.group_type track by option.id">
+ <label>
+ <input type="checkbox" checklist-model="$ctrl.savedSearch.group.group_type" checklist-value="option.id">
+ {{ option.label }}
+ </label>
+ </div>
+ </div>
+ <div class="form-inline">
+ <label>{{:: ts('Visibility:') }}</label>
+ <select class="form-control" ng-model="$ctrl.savedSearch.group.visibility" ng-options="item.id as item.label for item in groupOptions.visibility track by item.id" crm-ui-select></select>
+ </div>
+ </fieldset>
+</div>
<li role="presentation" ng-class="{active: controls.tab === 'compose'}">
- <a href ng-click="controls.tab = 'compose'">
+ <a href ng-click="selectTab('compose')">
<i class="crm-i fa-gears"></i>
{{ ts('Compose Search') }}
</a>
</li>
-<li role="presentation" ng-class="{active: controls.tab === 'group'}">
- <a href ng-click="controls.tab = 'group'">
+<li role="presentation" ng-class="{active: controls.tab === 'group'}" ng-if="$ctrl.savedSearch.group">
+ <a href ng-click="selectTab('group')" ng-class="{strikethrough: !$ctrl.groupExists}">
<i class="crm-i fa-users"></i>
- {{ ts('Smart Group: %1') }}
+ {{:: ts('Smart Group:') }} {{ $ctrl.savedSearch.group.title }}
</a>
+ <button class="btn-xs crm-search-delete-display" ng-click="$ctrl.removeGroup()">
+ <i class="crm-i fa-trash"></i>
+ </button>
</li>
<li role="presentation" ng-repeat="display in $ctrl.savedSearch.displays" ng-class="{active: controls.tab === ('display_' + $index)}">
- <a href ng-click="controls.tab = ('display_' + $index)">
+ <a href ng-click="selectTab('display_' + $index)">
<i class="crm-i {{ $ctrl.displayTypes[display.type].icon }}"></i>
{{ $ctrl.displayTypes[display.type].label }}
</a>
+ <button class="btn-xs crm-search-delete-display" ng-click="$ctrl.removeDisplay($index)">
+ <i class="crm-i fa-trash"></i>
+ </button>
</li>
<li role="presentation">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
- <i class="crm-i fa-plus"></i> {{ ts('Add Display') }} <span class="caret"></span>
+ <i class="crm-i fa-plus"></i> {{:: ts('Add...') }} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
- <li ng-repeat="type in $ctrl.displayTypes" >
- <a href ng-click="$ctrl.addDisplay(type.name)">{{ type.label }}</a>
+ <li ng-if="!$ctrl.savedSearch.group">
+ <a href ng-click="$ctrl.addGroup()">
+ <i class="crm-i fa-users"></i>
+ {{:: ts('Smart Group') }}
+ </a>
+ </li>
+ <li ng-repeat="type in ::$ctrl.displayTypes">
+ <a href ng-click="$ctrl.addDisplay(type.name)">
+ <i class="crm-i {{:: type.icon }}"></i>
+ {{:: type.label }}
+ </a>
</li>
</ul>
</li>
.crm-flex-box > .crm-flex-4 {
flex: 4;
}
+.crm-sortable {
+ cursor: move;
+}
+
#bootstrap-theme #crm-search-results-page-size {
width: 5em;
}
#bootstrap-theme .crm-search-results {
min-height: 200px;
}
-.crm-search-results thead th[ng-repeat] {
- cursor: pointer;
-}
-.crm-search-results thead th[ng-repeat] > span {
- cursor: move;
+
+#bootstrap-theme.crm-search ul.nav.nav-stacked {
+ margin-left: 0;
+ margin-right: 20px;
}
#bootstrap-theme.crm-search fieldset {
#bootstrap-theme.crm-search th.crm-search-result-select {
padding-right: 10px;
}
+
+#bootstrap-theme .crm-search-delete-display {
+ position: absolute;
+ right: 0;
+ top: 0;
+}