};
// Add callbacks for pre & post run
- this.onPreRun.push(function(apiParams) {
- apiParams.debug = true;
+ this.onPreRun.push(function(apiCalls) {
+ apiCalls.run.debug = true;
});
- this.onPostRun.push(function(result) {
- ctrl.debug = _.extend(_.pick(ctrl.debug, 'apiParams'), result.debug);
+ this.onPostRun.push(function(apiResults) {
+ ctrl.debug = _.extend(_.pick(ctrl.debug, 'apiParams'), apiResults.run.debug);
});
$scope.sortableColumnOptions = {
<div ng-include="'~/crmSearchAdmin/resultsTable/debug.html'"></div>
<div class="form-inline">
<div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'"></div>
- <crm-search-tasks entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" search="$ctrl.search" display="$ctrl.display" display-controller="$ctrl" refresh="$ctrl.refreshAfterTask()"></crm-search-tasks>
+ <crm-search-tasks-menu ids="$ctrl.selectedRows" task-manager="$ctrl.taskManager"></crm-search-tasks-menu>
<span ng-include="'~/crmSearchDisplay/ResultCount.html'"></span>
</div>
<table class="{{:: $ctrl.settings.classes.join(' ') }}">
}));
}
- this.onPostRun.push(function(result) {
- _.each(result, function(row) {
+ this.onPostRun.push(function(apiResults) {
+ _.each(apiResults.run, function(row) {
row.permissionToEdit = CRM.checkPerm('all CiviCRM permissions and ACLs') || !_.includes(row.data.display_acl_bypass, true);
// If main entity doesn't exist, no can edit
if (!row.data['api_entity:label']) {
this.deleteSearch = function(row) {
ctrl.runSearch(
- [['SavedSearch', 'delete', {where: [['id', '=', row.key]]}]],
+ {deleteSearch: ['SavedSearch', 'delete', {where: [['id', '=', row.key]]}]},
{start: ts('Deleting...'), success: ts('Search Deleted')},
row
);
this.revertSearch = function(row) {
ctrl.runSearch(
- [['SavedSearch', 'revert', {
+ {revertSearch: ['SavedSearch', 'revert', {
where: [['id', '=', row.key]],
chain: {
revertDisplays: ['SearchDisplay', 'revert', {'where': [['saved_search_id', '=', '$id'], ['has_base', '=', true]]}],
deleteDisplays: ['SearchDisplay', 'delete', {'where': [['saved_search_id', '=', '$id'], ['has_base', '=', false]]}]
}
- }]],
+ }]},
{start: ts('Reverting...'), success: ts('Search Reverted')},
row
);
this.deleteSegment = function(row) {
ctrl.runSearch(
- [['SearchSegment', 'delete', {where: [['id', '=', row.key]]}]],
+ {deleteSegment: ['SearchSegment', 'delete', {where: [['id', '=', row.key]]}]},
{start: ts('Deleting...'), success: ts('Segment Deleted')},
row
);
-<crm-search-display-editable row="row" col="colData" do-save="$ctrl.runSearch([apiCall], {}, row)" cancel="$ctrl.editing = null;" ng-if="colData.edit && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === colIndex"></crm-search-display-editable>
+<crm-search-display-editable row="row" col="colData" do-save="$ctrl.runSearch({inPlaceEdit: apiCall}, {}, row)" cancel="$ctrl.editing = null;" ng-if="colData.edit && $ctrl.editing && $ctrl.editing[0] === rowIndex && $ctrl.editing[1] === colIndex"></crm-search-display-editable>
<span ng-if="::!colData.links" ng-class="{'crm-editable-enabled': colData.edit && !$ctrl.editing, 'crm-editable-disabled': colData.edit && $ctrl.editing}" ng-click="colData.edit && !$ctrl.editing && ($ctrl.editing = [rowIndex, colIndex])">
<i ng-repeat="icon in colData.icons" ng-if="icon.side === 'left'" class="crm-i {{:: icon['class'] }}"></i>
{{:: $ctrl.formatFieldValue(colData) }}
page: 1,
rowCount: null,
// Arrays may contain callback functions for various events
+ onInitialize: [],
onChangeFilters: [],
onPreRun: [],
onPostRun: [],
for (var p=0; p < placeholderCount; ++p) {
this.placeholders.push({});
}
+ _.each(ctrl.onInitialize, function(callback) {
+ callback.call(ctrl, $scope, $element);
+ });
// _.debounce used here to trigger the initial search immediately but prevent subsequent launches within 300ms
this.getResultsPronto = _.debounce(ctrl.runSearch, 300, {leading: true, trailing: false});
if (this.afFieldset) {
$scope.$watch(this.afFieldset.getFieldData, onChangeFilters, true);
// Add filter title to Afform
- this.onPostRun.push(function(results) {
- if (results.labels && results.labels.length && $scope.$parent.addTitle) {
- $scope.$parent.addTitle(results.labels.join(' '));
+ this.onPostRun.push(function(apiResults) {
+ if (apiResults.run.labels && apiResults.run.labels.length && $scope.$parent.addTitle) {
+ $scope.$parent.addTitle(apiResults.run.labels.join(' '));
}
});
}
if (!statusParams) {
this.loading = true;
}
+ apiCalls = apiCalls || {};
+ apiCalls.run = ['SearchDisplay', 'run', apiParams];
_.each(ctrl.onPreRun, function(callback) {
- callback.call(ctrl, apiParams);
+ callback.call(ctrl, apiCalls);
});
- apiCalls = apiCalls || [];
- apiCalls.push(['SearchDisplay', 'run', apiParams]);
var apiRequest = crmApi4(apiCalls);
apiRequest.then(function(apiResults) {
if (requestId < ctrl._runCount) {
return; // Another request started after this one
}
- ctrl.results = _.last(apiResults);
+ ctrl.results = apiResults.run;
ctrl.editing = ctrl.loading = false;
// Update rowCount if running for the first time or during an update op
if (!ctrl.rowCount || editedRow) {
}
}
_.each(ctrl.onPostRun, function(callback) {
- callback.call(ctrl, ctrl.results, 'success', editedRow);
+ callback.call(ctrl, apiResults, 'success', editedRow);
});
}, function(error) {
if (requestId < ctrl._runCount) {
var tallyParams;
if (ctrl.settings.tally) {
- ctrl.onPreRun.push(function (apiParams) {
+ ctrl.onPreRun.push(function (apiCalls) {
ctrl.tally = null;
- tallyParams = _.cloneDeep(apiParams);
+ tallyParams = _.cloneDeep(apiCalls.run);
});
- ctrl.onPostRun.push(function (results, status) {
+ ctrl.onPostRun.push(function (apiResults, status) {
ctrl.tally = null;
if (status === 'success' && tallyParams) {
tallyParams.return = 'tally';
updateParams = {where: [['id', '=', movedItem.data.id]], values: {}};
if (newPosition > -1 && oldPosition !== newPosition) {
updateParams.values[weightColumn] = displacedItem.data[weightColumn];
- ctrl.runSearch([[ctrl.apiEntity, 'update', updateParams]], {}, movedItem);
+ ctrl.runSearch({updateWeight: [ctrl.apiEntity, 'update', updateParams]}, {}, movedItem);
}
});
}
<div class="alert alert-info crm-search-display-description" ng-if="$ctrl.settings.description">{{:: $ctrl.settings.description }}</div>
<div class="form-inline">
<div class="btn-group" ng-include="'~/crmSearchDisplay/SearchButton.html'" ng-if="$ctrl.settings.button"></div>
- <crm-search-tasks ng-if="$ctrl.settings.actions" entity="$ctrl.apiEntity" ids="$ctrl.selectedRows" search="$ctrl.search" display="$ctrl.display" display-controller="$ctrl" refresh="$ctrl.refreshAfterTask()"></crm-search-tasks>
+ <crm-search-tasks-menu ng-if="$ctrl.settings.actions && $ctrl.taskManager" ids="$ctrl.selectedRows" task-manager="$ctrl.taskManager"></crm-search-tasks-menu>
<span ng-if="$ctrl.settings.headerCount" ng-include="'~/crmSearchDisplay/ResultCount.html'"></span>
<div class="btn-group pull-right" ng-include="'~/crmSearchDisplay/AddButton.html'" ng-if="$ctrl.settings.addButton.path"></div>
</div>
ctrl.progress += 10;
}
}, 1000);
- var apiParams = ctrl.displayController.getApiParams();
+ var apiParams = ctrl.taskManager.getApiParams();
delete apiParams.return;
delete apiParams.limit;
apiParams.filters.id = ctrl.ids || null;
<form ng-controller="crmSearchTaskDownload as $ctrl">
<p>
<strong ng-if="$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.ids.length, 2: $ctrl.entityTitle}) }}</strong>
- <strong ng-if="!$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.displayController.rowCount, 2: $ctrl.entityTitle}) }}</strong>
+ <strong ng-if="!$ctrl.ids.length">{{:: ts('Download %1 %2', {1: $ctrl.taskManager.getRowCount(), 2: $ctrl.entityTitle}) }}</strong>
</p>
<div class="form-inline">
<label for="crmSearchTaskDownload-format">{{:: ts('Format') }}</label>
+++ /dev/null
-(function(angular, $, _) {
- "use strict";
-
- angular.module('crmSearchTasks').component('crmSearchTasks', {
- bindings: {
- entity: '<',
- refresh: '&',
- search: '<',
- display: '<',
- displayController: '<',
- ids: '<'
- },
- templateUrl: '~/crmSearchTasks/crmSearchTasks.html',
- controller: function($scope, crmApi4, dialogService, $window) {
- var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
- ctrl = this,
- initialized = false,
- unwatchIDs = $scope.$watch('$ctrl.ids.length', watchIDs);
-
- function watchIDs() {
- if (ctrl.ids && ctrl.ids.length) {
- unwatchIDs();
- ctrl.getTasks();
- }
- }
-
- this.getTasks = function() {
- if (initialized) {
- return;
- }
- initialized = true;
- crmApi4({
- entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural', 'primary_key'], where: [['name', '=', ctrl.entity]]}, 0],
- tasks: ['SearchDisplay', 'getSearchTasks', {entity: ctrl.entity, savedSearch: ctrl.search, display: ctrl.display}]
- }).then(function(result) {
- ctrl.entityInfo = result.entityInfo;
- ctrl.tasks = result.tasks;
- });
- };
-
- this.isActionAllowed = function(action) {
- return $scope.$eval('' + ctrl.ids.length + action.number);
- };
-
- this.updateActionData = function() {
- if (this.entity === 'RelationshipCache') {
- this.entity = 'Relationship';
- this.entityInfo.title = ts('Relationship');
- this.entityInfo.title_plural = ts('Relationships');
- }
- };
-
- this.getActionTitle = function(action) {
- if (ctrl.isActionAllowed(action)) {
- ctrl.updateActionData();
- return ctrl.ids.length ?
- ts('Perform action on %1 %2', {1: ctrl.ids.length, 2: ctrl.entityInfo[ctrl.ids.length === 1 ? 'title' : 'title_plural']}) :
- ts('Perform action on all %1', {1: ctrl.entityInfo.title_plural});
- }
- return ts('Selected number must be %1', {1: action.number.replace('===', '')});
- };
-
- this.doAction = function(action) {
- if (!ctrl.isActionAllowed(action)) {
- return;
- }
- // Update data specific to entity actions.
- ctrl.updateActionData();
-
- var data = {
- ids: ctrl.ids,
- entity: ctrl.entity,
- search: ctrl.search,
- display: ctrl.display,
- displayController: ctrl.displayController,
- entityInfo: ctrl.entityInfo,
- taskTitle: action.title,
- apiBatch: _.cloneDeep(action.apiBatch)
- };
- // If action uses a crmPopup form
- if (action.crmPopup) {
- var path = $scope.$eval(action.crmPopup.path, data),
- query = action.crmPopup.query && $scope.$eval(action.crmPopup.query, data);
- CRM.loadForm(CRM.url(path, query), {post: action.crmPopup.data && $scope.$eval(action.crmPopup.data, data)})
- .on('crmFormSuccess', ctrl.refresh);
- }
- else if (action.redirect) {
- var redirectPath = $scope.$eval(action.redirect.path, data),
- redirectQuery = action.redirect.query && $scope.$eval(action.redirect.query, data) && $scope.$eval(action.redirect.data, data);
- $window.open(CRM.url(redirectPath, redirectQuery), '_blank');
- }
- // If action uses dialogService
- else {
- var options = CRM.utils.adjustDialogDefaults({
- autoOpen: false,
- dialogClass: 'crm-search-task-dialog',
- title: action.title
- });
- dialogService.open('crmSearchTask', (action.uiDialog && action.uiDialog.templateUrl) || '~/crmSearchTasks/crmSearchTaskApiBatch.html', data, options)
- // Reload results on success, do nothing on cancel
- .then(ctrl.refresh, _.noop);
- }
- };
- }
- });
-
-})(angular, CRM.$, CRM._);
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('crmSearchTasks').component('crmSearchTasksMenu', {
+ bindings: {
+ taskManager: '<',
+ ids: '<'
+ },
+ templateUrl: '~/crmSearchTasks/crmSearchTasksMenu.html',
+ controller: function($scope) {
+ var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
+ ctrl = this;
+
+ this.$onInit = function() {
+ // When a row is selected for bulk actions, load the actions menu
+ var unwatchIDs = $scope.$watch('$ctrl.ids.length', function (idsLength) {
+ if (idsLength) {
+ unwatchIDs();
+ ctrl.taskManager.getMetadata();
+ }
+ });
+ };
+
+ this.isActionAllowed = function(action) {
+ return $scope.$eval('' + ctrl.ids.length + action.number);
+ };
+
+ this.getActionTitle = function(action) {
+ if (ctrl.isActionAllowed(action)) {
+ return ctrl.ids.length ?
+ ts('Perform action on %1 %2', {1: ctrl.ids.length, 2: ctrl.taskManager.entityInfo[ctrl.ids.length === 1 ? 'title' : 'title_plural']}) :
+ ts('Perform action on all %1', {1: ctrl.taskManager.entityInfo.title_plural});
+ }
+ return ts('Selected number must be %1', {1: action.number.replace('===', '')});
+ };
+
+ this.doAction = function(action) {
+ if (!ctrl.isActionAllowed(action)) {
+ return;
+ }
+ ctrl.taskManager.doTask(action, ctrl.ids);
+ };
+ }
+ });
+
+})(angular, CRM.$, CRM._);
<div class="btn-group">
<button type="button"
- ng-disabled="$ctrl.displayController.loading || !$ctrl.displayController.results.length"
- ng-click="$ctrl.getTasks()"
+ ng-disabled="!$ctrl.taskManager.isDisplayReady()"
+ ng-click="$ctrl.openMenu = true; $ctrl.taskManager.getMetadata();"
class="btn dropdown-toggle btn-default"
title="{{:: ts('Perform action on selected items.') }}"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
<i class="crm-i fa-pencil"></i>
{{:: ts('Action') }} <span class="caret"></span>
</button>
- <ul class="dropdown-menu">
- <li ng-class="{disabled: !$ctrl.isActionAllowed(action)}" ng-repeat="action in $ctrl.tasks" title="{{ $ctrl.getActionTitle(action) }}">
- <a href ng-click="$ctrl.doAction(action)"><i class="crm-i {{:: action.icon }}"></i> {{:: action.title }}</a>
+ <ul class="dropdown-menu" ng-if="$ctrl.openMenu">
+ <li ng-class="{disabled: !$ctrl.isActionAllowed(action)}" ng-repeat="action in $ctrl.taskManager.tasks" title="{{ $ctrl.getActionTitle(action) }}">
+ <a href ng-click="$ctrl.doAction(action); $ctrl.openMenu = false;"><i class="crm-i {{:: action.icon }}"></i> {{:: action.title }}</a>
</li>
- <li class="disabled" ng-if="!$ctrl.tasks">
+ <li class="disabled" ng-if="!$ctrl.taskManager.tasks">
<a href><i class="crm-i fa-spinner fa-spin"></i></a>
</li>
</ul>
-(function(angular, $, _) {
+ (function(angular, $, _) {
"use strict";
// Trait shared by any search display controllers which use tasks
- angular.module('crmSearchDisplay').factory('searchDisplayTasksTrait', function(crmApi4) {
+ angular.module('crmSearchTasks').factory('searchDisplayTasksTrait', function($rootScope, $window, crmApi4, dialogService) {
var ts = CRM.ts('org.civicrm.search_kit');
+ // TaskManager object is responsible for fetching task metadata for a SearchDispaly
+ // and handles the running of tasks.
+ function TaskManager(displayCtrl) {
+ var mngr = this;
+ var fetchedMetadata;
+ this.tasks = null;
+ this.entityInfo = null;
+
+ this.getMetadata = function() {
+ if (fetchedMetadata) {
+ return;
+ }
+ fetchedMetadata = crmApi4({
+ entityInfo: ['Entity', 'get', {select: ['name', 'title', 'title_plural', 'primary_key'], where: [['name', '=', mngr.getEntityName()]]}, 0],
+ tasks: ['SearchDisplay', 'getSearchTasks', {entity: displayCtrl.apiEntity, savedSearch: displayCtrl.search, display: displayCtrl.display}]
+ }).then(function(result) {
+ mngr.entityInfo = result.entityInfo;
+ mngr.tasks = result.tasks;
+ });
+ };
+
+ this.getEntityName = function() {
+ return displayCtrl.apiEntity === 'RelationshipCache' ? 'Relationship' : displayCtrl.apiEntity;
+ };
+ this.getApiParams = function() {
+ return displayCtrl.getApiParams();
+ };
+ this.getRowCount = function() {
+ return displayCtrl.rowCount;
+ };
+ this.isDisplayReady = function() {
+ return !displayCtrl.loading && displayCtrl.results && displayCtrl.results.length;
+ };
+
+ this.doTask = function(task, ids) {
+ var data = {
+ ids: ids,
+ entity: mngr.getEntityName(),
+ search: displayCtrl.search,
+ display: displayCtrl.display,
+ taskManager: mngr,
+ entityInfo: mngr.entityInfo,
+ taskTitle: task.title,
+ apiBatch: _.cloneDeep(task.apiBatch)
+ };
+ // If task uses a crmPopup form
+ if (task.crmPopup) {
+ var path = $rootScope.$eval(task.crmPopup.path, data),
+ query = task.crmPopup.query && $rootScope.$eval(task.crmPopup.query, data);
+ CRM.loadForm(CRM.url(path, query), {post: task.crmPopup.data && $rootScope.$eval(task.crmPopup.data, data)})
+ .on('crmFormSuccess', mngr.refreshAfterTask);
+ }
+ else if (task.redirect) {
+ var redirectPath = $rootScope.$eval(task.redirect.path, data),
+ redirectQuery = task.redirect.query && $rootScope.$eval(task.redirect.query, data) && $rootScope.$eval(task.redirect.data, data);
+ $window.open(CRM.url(redirectPath, redirectQuery), '_blank');
+ }
+ // If task uses dialogService
+ else {
+ var options = CRM.utils.adjustDialogDefaults({
+ autoOpen: false,
+ dialogClass: 'crm-search-task-dialog',
+ title: task.title
+ });
+ dialogService.open('crmSearchTask', (task.uiDialog && task.uiDialog.templateUrl) || '~/crmSearchTasks/crmSearchTaskApiBatch.html', data, options)
+ // Reload results on success, do nothing on cancel
+ .then(mngr.refreshAfterTask, _.noop);
+ }
+ };
+
+ this.refreshAfterTask = function() {
+ displayCtrl.selectedRows = [];
+ displayCtrl.allRowsSelected = false;
+ displayCtrl.rowCount = undefined;
+ displayCtrl.runSearch();
+ };
+ }
+
// Trait properties get mixed into display controller using angular.extend()
return {
(!this.allRowsSelected && this.selectedRows && this.selectedRows.length === this.results.length);
},
- refreshAfterTask: function() {
- this.selectedRows = [];
- this.allRowsSelected = false;
- this.rowCount = undefined;
- this.runSearch();
- },
+ // onInitialize callback
+ onInitialize: [function() {
+ // Instantiate task manager object
+ if (!this.taskManager) {
+ this.taskManager = new TaskManager(this);
+ }
+ }],
- // Add onChangeFilters callback (gets merged with others via angular.extend)
+ // onChangeFilters callback
onChangeFilters: [function() {
// Reset selection when filters are changed
this.selectedRows = [];
this.allRowsSelected = false;
}],
- // Add onPostRun callback (gets merged with others via angular.extend)
- onPostRun: [function(results, status, editedRow) {
+ // onPostRun callback (gets merged with others via angular.extend)
+ onPostRun: [function(apiResults, status, editedRow) {
if (editedRow && status === 'success' && this.selectedRows) {
// If edited row disappears (because edits cause it to not meet search criteria), deselect it
var index = this.selectedRows.indexOf(editedRow.key);
- if (index > -1 && !_.findWhere(results, {key: editedRow.key})) {
+ if (index > -1 && !_.findWhere(apiResults.run, {key: editedRow.key})) {
this.selectedRows.splice(index, 1);
}
}
"use strict";
// Trait shared by task controllers
- angular.module('crmSearchDisplay').factory('searchTaskBaseTrait', function(dialogService) {
+ angular.module('crmSearchTasks').factory('searchTaskBaseTrait', function(dialogService) {
var ts = CRM.ts('org.civicrm.search_kit');
// Trait properties get mixed into task controller using angular.extend()