*
* @param CRM_Case_Form_Search $form
*/
- public static function buildSearchForm(&$form) {
+ public static function buildSearchForm(&$form): void {
+ $form->addOptionalQuickFormElement('upcoming');
//validate case configuration.
$configured = CRM_Case_BAO_Case::isCaseConfigured();
$form->assign('notConfigured', !$configured['configured']);
$expectedProperties = ['options_per_line', 'help_pre', 'help_post'];
// add field information
foreach ($value['fields'] as $k => $properties) {
- $properties = array_merge(array_fill_keys($expectedProperties, 'NULL'), $properties);
+ $properties = array_merge(array_fill_keys($expectedProperties, NULL), $properties);
$properties['element_name'] = "custom_{$k}_-{$groupCount}";
if (isset($properties['customValue']) &&
!CRM_Utils_System::isNull($properties['customValue']) &&
* Set up variables to build the form.
*/
public function preProcess() {
+ $this->addOptionalQuickFormElement('parents');
+ $this->addExpectedSmartyVariable('parent_groups');
$this->_id = $this->get('id');
if ($this->_id) {
$breadCrumb = array(
* @return array
*/
protected function getOrderByFromSort() {
+ // Drag-sortable tables have a forced order
+ if (!empty($this->display['settings']['draggable'])) {
+ return [$this->display['settings']['draggable'] => 'ASC'];
+ }
+
$defaultSort = $this->display['settings']['sort'] ?? [];
$currentSort = $this->sort;
}, $apiParams['select']);
$additions = [];
// Add primary key field if actions are enabled
- if (!empty($this->display['settings']['actions'])) {
+ if (!empty($this->display['settings']['actions']) || !empty($this->display['settings']['draggable'])) {
$additions = CoreUtil::getInfoItem($this->savedSearch['api_entity'], 'primary_key');
}
+ // Add draggable column (typically "weight")
+ if (!empty($this->display['settings']['draggable'])) {
+ $additions[] = $this->display['settings']['draggable'];
+ }
// Add style conditions for the display
foreach ($this->getCssRulesSelect($this->display['settings']['cssRules'] ?? []) as $addition) {
$additions[] = $addition;
},
controller: function($scope, $timeout, searchMeta) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
- ctrl = this,
- afforms;
+ ctrl = this;
this.isSuperAdmin = CRM.checkPerm('all CiviCRM permissions and ACLs');
this.aclBypassHelp = ts('Only users with "all CiviCRM permissions and ACLs" can disable permission checks.');
// Checks if a column contains a sortable value
// Must be a real sql expression (not a pseudo-field like `result_row_num`)
this.canBeSortable = function(col) {
+ // Column-header sorting is incompatible with draggable sorting
+ if (ctrl.display.settings.draggable) {
+ return false;
+ }
var expr = ctrl.getExprFromSelect(col.key),
info = searchMeta.parseExpr(expr),
arg = (info && info.args && _.findWhere(info.args, {type: 'field'})) || {};
}
};
+ this.toggleDraggable = function() {
+ if (ctrl.display.settings.draggable) {
+ delete ctrl.display.settings.draggable;
+ } else {
+ ctrl.display.settings.sort = [];
+ ctrl.display.settings.draggable = searchMeta.getEntity(ctrl.apiEntity).order_by;
+ }
+ };
+
this.$onInit = function () {
if (!ctrl.display.settings) {
ctrl.display.settings = _.extend({}, _.cloneDeep(CRM.crmSearchAdmin.defaultDisplay.settings), {columns: null});
}
// Displays created prior to 5.43 may not have this property
ctrl.display.settings.classes = ctrl.display.settings.classes || [];
+ // Table can be draggable if the main entity is a SortableEntity.
+ ctrl.canBeDraggable = _.includes(searchMeta.getEntity(ctrl.apiEntity).type, 'SortableEntity');
ctrl.parent.initColumns({label: true, sortable: true});
};
-<fieldset ng-include="'~/crmSearchAdmin/crmSearchAdminDisplaySort.html'"></fieldset>
+<fieldset ng-if="!$ctrl.display.settings.draggable" ng-include="'~/crmSearchAdmin/crmSearchAdminDisplaySort.html'"></fieldset>
<fieldset>
+ <div ng-if="$ctrl.canBeDraggable" class="form-inline">
+ <div class="checkbox-inline form-control">
+ <label>
+ <input type="checkbox" ng-checked="!!$ctrl.display.settings.draggable" ng-click="$ctrl.toggleDraggable()">
+ <span>{{:: ts('Drag and drop sorting') }}</span>
+ </label>
+ </div>
+ </div>
<div class="form-inline">
<div class="checkbox-inline form-control">
<label>
<option value="text-right">{{:: ts('Right') }}</option>
</select>
</div>
- <div class="form-inline" ng-if=":: $ctrl.parent.canBeSortable(col)">
+ <div class="form-inline" ng-if="$ctrl.parent.canBeSortable(col)">
<label title="{{:: ts('Allow user to click on header to sort table by this column') }}">
<input type="checkbox" ng-checked="col.sortable !== false" ng-click="col.sortable = col.sortable === false" >
{{:: ts('Sortable Header') }}
sort: [],
isSortable: function(col) {
- return col.type === 'field' && col.sortable !== false;
+ return !this.settings.draggable && col.type === 'field' && col.sortable !== false;
},
getSort: function(col) {
'ang/crmSearchDisplayTable',
],
'basePages' => ['civicrm/search', 'civicrm/admin/search'],
- 'requires' => ['crmSearchDisplay', 'crmUi', 'crmSearchTasks', 'ui.bootstrap'],
+ 'requires' => ['crmSearchDisplay', 'crmUi', 'crmSearchTasks', 'ui.bootstrap', 'ui.sortable'],
'bundles' => ['bootstrap3'],
'exports' => [
'crm-search-display-table' => 'E',
afFieldset: '?^^afFieldset'
},
templateUrl: '~/crmSearchDisplayTable/crmSearchDisplayTable.html',
- controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplaySortableTrait) {
+ controller: function($scope, $element, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplaySortableTrait, crmApi4, crmStatus) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
// Mix in traits to this controller
ctrl = angular.extend(this, searchDisplayBaseTrait, searchDisplayTasksTrait, searchDisplaySortableTrait);
+
this.$onInit = function() {
this.initializeDisplay($scope, $element);
+
+ if (ctrl.settings.draggable) {
+ ctrl.draggableOptions = {
+ containment: 'table',
+ direction: 'vertical',
+ handle: '.crm-draggable',
+ forcePlaceholderSize: true,
+ helper: function(e, ui) {
+ // Prevent table row width from changing during drag
+ ui.children().each(function() {
+ $(this).width($(this).width());
+ });
+ return ui;
+ },
+ stop: function(e, ui) {
+ $scope.$apply(function() {
+ var movedItem = ui.item.sortable.model,
+ oldPosition = ui.item.sortable.index,
+ newPosition = ctrl.results.indexOf(movedItem),
+ displacement = newPosition < oldPosition ? -1 : 1,
+ displacedItem = ctrl.results[newPosition - displacement],
+ weightColumn = ctrl.settings.draggable,
+ 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);
+ }
+ });
+ }
+ };
+ }
};
}
<table class="{{:: $ctrl.settings.classes.join(' ') }}">
<thead>
<tr>
- <th class="crm-search-result-select" ng-if=":: $ctrl.settings.actions">
- <input type="checkbox" ng-disabled="$ctrl.loading || !$ctrl.results.length" ng-checked="$ctrl.allRowsSelected" ng-click="$ctrl.selectAllRows()" >
+ <th class="crm-search-result-select" ng-if=":: $ctrl.settings.actions || $ctrl.settings.draggable">
+ <i ng-if=":: $ctrl.settings.draggable" class="crm-i fa-sort-amount-asc" title="{{:: ts('Drag columns to reposition') }}"></i>
+ <input type="checkbox" ng-if=":: $ctrl.settings.actions" ng-disabled="$ctrl.loading || !$ctrl.results.length" ng-checked="$ctrl.allRowsSelected" ng-click="$ctrl.selectAllRows()" >
</th>
- <th ng-repeat="col in $ctrl.settings.columns" ng-click="$ctrl.setSort(col, $event)" title="{{:: $ctrl.isSortable(col) ? ts('Click to sort results (shift-click to sort by multiple).') : '' }}">
+ <th ng-repeat="col in $ctrl.settings.columns" ng-click="$ctrl.setSort(col, $event)" class="{{:: $ctrl.isSortable(col) ? 'crm-sortable-col' : ''}}" title="{{:: $ctrl.isSortable(col) ? ts('Click to sort results (shift-click to sort by multiple).') : '' }}">
<i ng-if=":: $ctrl.isSortable(col)" class="crm-i {{ $ctrl.getSort(col) }}"></i>
<span>{{:: col.label }}</span>
</th>
</tr>
</thead>
- <tbody ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTable' + ($ctrl.loading ? 'Loading' : 'Body') + '.html'"></tbody>
+ <tbody ng-if="$ctrl.loading" ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTableLoading.html'"></tbody>
+ <tbody ng-if="!$ctrl.loading && !$ctrl.settings.draggable" ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTableBody.html'"></tbody>
+ <tbody ng-if="!$ctrl.loading && $ctrl.settings.draggable" ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTableBody.html'" ui-sortable="$ctrl.draggableOptions" ng-model="$ctrl.results"></tbody>
</table>
<div ng-include="'~/crmSearchDisplay/Pager.html'"></div>
</div>
<tr ng-repeat="(rowIndex, row) in $ctrl.results">
- <td ng-if=":: $ctrl.settings.actions" class="{{:: row.cssClass }}">
- <input type="checkbox" ng-checked="$ctrl.isRowSelected(row)" ng-click="$ctrl.selectRow(row)" ng-disabled="!!$ctrl.loadingAllRows">
+ <td ng-if=":: $ctrl.settings.actions || $ctrl.settings.draggable" class="{{:: row.cssClass }}">
+ <span ng-if=":: $ctrl.settings.draggable" class="crm-draggable" title="{{:: ts('Drag to reposition') }}">
+ <i class="crm-i fa-arrows-v"></i>
+ </span>
+ <input ng-if=":: $ctrl.settings.actions" type="checkbox" ng-checked="$ctrl.isRowSelected(row)" ng-click="$ctrl.selectRow(row)" ng-disabled="!!$ctrl.loadingAllRows">
</td>
<td ng-repeat="(colIndex, colData) in row.columns" ng-include="'~/crmSearchDisplay/colType/' + $ctrl.settings.columns[colIndex].type + '.html'" title="{{:: colData.title }}" class="{{:: row.cssClass }} {{:: colData.cssClass }}">
</td>
<!-- Placeholder table rows shown during ajax loading -->
<tr ng-repeat="num in [1,2,3,4,5] track by $index">
- <td ng-if=":: $ctrl.settings.actions">
- <input type="checkbox" disabled>
+ <td ng-if=":: $ctrl.settings.actions || $ctrl.settings.draggable">
+ <input ng-if=":: $ctrl.settings.actions" type="checkbox" disabled>
</td>
<td ng-repeat="col in $ctrl.settings.columns">
<div class="crm-search-loading-placeholder"></div>
/* Sortable headers */
-#bootstrap-theme .crm-search-display th[ng-click] {
+#bootstrap-theme .crm-search-display th.crm-sortable-col {
cursor: pointer;
}
#bootstrap-theme .crm-search-display th i.fa-sort-desc,
{/if}
{foreach from=$columnHeaders item=header}
<th scope="col">
- {if isset($header.sort)}
+ {if $header.sort}
{assign var='key' value=$header.sort}
{if !empty($sort)}
{$sort->_response.$key.link}
{/if}
- {elseif isset($header.name)}
+ {elseif $header.name}
{$header.name}
{/if}
</th>
{/if}
</td>
- <td>{if isset($row.activity_subject)}{$row.activity_subject|purify}{/if}</td>
+ <td>{$row.activity_subject|purify}</td>
<td>
{if !$row.source_contact_id}
<td>
{if $row.mailingId}
<a href="{$row.mailingId}" title="{ts}View Mailing Report{/ts}">{$row.recipients}</a>
- {elseif isset($row.recipients)}
+ {elseif $row.recipients}
{$row.recipients}
{elseif !$row.target_contact_name}
<em>n/a</em>
*}
{*this template is used for activity accordion*}
{assign var=caseid value=$caseID}
-{if isset($isForm) and $isForm}
+{if $isForm}
<div class="crm-accordion-wrapper crm-case_activities-accordion crm-case-activities-block">
<div class="crm-accordion-header">
{ts}Activities{/ts}
{/foreach}
</style>
-{if isset($isForm) and $isForm}
+{if $isForm}
</div><!-- /.crm-accordion-body -->
</div><!-- /.crm-accordion-wrapper -->
{/if}
<td class="crm-contact-form-block-case_status_id crm-inline-edit-field">
{$form.case_status_id.label}<br /> {$form.case_status_id.html}
</td>
- {if $accessAllCases && isset($form.upcoming)}
+ {if $accessAllCases && $form.upcoming}
<td class="crm-case-dashboard-switch-view-buttons">
<br/>
{$form.upcoming.html} {$form.upcoming.label}
</div>
<div class="crm-accordion-body">
<div>{$article.description|smarty:nodefaults|purify}</div>
- <p class="crm-news-feed-item-link"><a target="_blank" href="{$article.link|smarty:nodefaults|purify}" title="{$article.title|escape}"><i class="crm-i fa-external-link" aria-hidden="true"></i> {ts}read more{/ts}…</a></p>
+ <p class="crm-news-feed-item-link"><a target="_blank" href="{$article.link|smarty:nodefaults|purify}" title="{$article.title|smarty:nodefaults|escape}"><i class="crm-i fa-external-link" aria-hidden="true"></i> {ts}read more{/ts}…</a></p>
</div>
</div>
{/foreach}
+--------------------------------------------------------------------+
*}
{*CRM-14190*}
-{if (isset($parent_groups) and $parent_groups|@count > 0) or !empty($form.parents.html)}
+{if $parent_groups|@count > 0 || !empty($form.parents.html)}
<h3>{ts}Parent Groups{/ts} {help id="id-group-parent" file="CRM/Group/Page/Group.hlp"}</h3>
- {if isset($parent_groups) and $parent_groups|@count > 0}
+ {if $parent_groups|@count > 0}
<table class="form-layout-compressed">
<tr>
<td><label>{ts}Remove Parent?{/ts}</label></td>
<td class="label">{$form.used_for.label}</td>
<td>{$form.used_for.html} <br />
<span class="description">
- {* @TODO: I don't think is_parent is ever true because this form is never used for editing a tag itself, and you can't nest tagsets. And when used to add a new child tag, the used_for element doesn't exist. *}
- {if !empty($is_parent)}{ts}You can change the types of records which this tag can be used for by editing the 'Parent' tag.{/ts}
- {else}{ts}What types of record(s) can this tag be used for?{/ts}
- {/if}
+ {ts}What types of record(s) can this tag be used for?{/ts}
</span>
</td>
</tr>