flex-wrap: wrap;
box-sizing: border-box;
}
-.crm-container .crm-flex-box > * {
+.crm-flex-box > * {
flex: 1;
box-sizing: border-box;
min-width: 0; /* prevents getting squashed by whitespace:nowrap content */
}
-.crm-container .crm-flex-box > .crm-flex-2 {
+.crm-container .crm-flex-1 {
+ flex: 1;
+}
+.crm-container .crm-flex-2 {
flex: 2;
}
-.crm-container .crm-flex-box > .crm-flex-3 {
+.crm-container .crm-flex-3 {
flex: 3;
}
-.crm-container .crm-flex-box > .crm-flex-4 {
+.crm-container .crm-flex-4 {
flex: 4;
}
-.crm-container .crm-flex-box > .crm-flex-5 {
+.crm-container .crm-flex-5 {
flex: 5;
}
return TRUE;
}
+ /**
+ * Upgrade 1002 - embellish search display link data
+ * @return bool
+ */
+ public function upgrade_1002() {
+ $this->ctx->log->info('Applying update 1002 - embellish search display link data.');
+ $displays = \Civi\Api4\SearchDisplay::get(FALSE)
+ ->setSelect(['id', 'settings'])
+ ->execute();
+ foreach ($displays as $display) {
+ $update = FALSE;
+ foreach ($display['settings']['columns'] ?? [] as $c => $column) {
+ if (!empty($column['link'])) {
+ $display['settings']['columns'][$c]['link'] = ['path' => $column['link']];
+ $update = TRUE;
+ }
+ }
+ if ($update) {
+ \Civi\Api4\SearchDisplay::update(FALSE)
+ ->setValues($display)
+ ->execute();
+ }
+ }
+ return TRUE;
+ }
+
}
return values;
}
+ this.toggleLink = function(column) {
+ if (column.link) {
+ ctrl.onChangeLink(column, column.link.path, '');
+ } else {
+ var defaultLink = ctrl.getLinks()[0];
+ column.link = {path: defaultLink ? defaultLink.path : 'civicrm/'};
+ ctrl.onChangeLink(column, null, column.link.path);
+ }
+ };
+
+ this.onChangeLink = function(column, before, after) {
+ var beforeLink = before && _.findWhere(ctrl.getLinks(), {path: before}),
+ afterLink = after && _.findWhere(ctrl.getLinks(), {path: after});
+ if (!after) {
+ if (beforeLink && column.title === beforeLink.title) {
+ delete column.title;
+ }
+ delete column.link;
+ } else if (afterLink && ((!column.title && !before) || (beforeLink && beforeLink.title === column.title))) {
+ column.title = afterLink.title;
+ } else if (!afterLink && (beforeLink && beforeLink.title === column.title)) {
+ delete column.title;
+ }
+ };
+
this.getLinks = function() {
if (!ctrl.links) {
ctrl.links = buildLinks();
this.styles = CRM.crmSearchAdmin.styles;
- this.setValue = function(val, index) {
- var link = ctrl.getLink(val),
- item = ctrl.group[index];
- if (item.path === val) {
- return;
- }
- item.path = val;
- item.icon = link ? defaultIcons[link.action] : 'fa-external-link';
- if (val === 'civicrm/') {
- $timeout(function () {
- $('tr:eq(' + index + ') input[type=text]', $element).focus();
- });
- }
+ this.getStyle = function(item) {
+ return _.findWhere(this.styles, {key: item.style});
};
this.sortableOptions = {
ctrl.addItem('civicrm/');
}
}
- $element.on('change', 'select.crm-search-admin-select-path', function() {
+ $element.on('change', 'select.crm-search-admin-add-link', function() {
var $select = $(this);
$scope.$apply(function() {
- if ($select.closest('tfoot').length) {
- ctrl.addItem($select.val());
- $select.val('');
- } else {
- ctrl.setValue($select.val(), $select.closest('tr').index());
- }
+ ctrl.addItem($select.val());
+ $select.val('');
});
});
};
<tr>
<th class="crm-search-admin-icon-col"></th>
<th class="crm-search-admin-icon-col">{{:: ts('Icon') }}</th>
+ <th>{{:: ts('Open') }}</th>
<th>{{:: ts('Text') }}</th>
<th>{{:: ts('Link') }}</th>
<th>{{:: ts('Style') }}</th>
</tr>
</thead>
<tbody ui-sortable="$ctrl.sortableOptions" ng-model="$ctrl.group">
- <tr ng-repeat="item in $ctrl.group" class="bg-{{ item.style }}">
+ <tr ng-repeat="item in $ctrl.group">
<td class="crm-search-admin-icon-col">
<i class="crm-i fa-arrows disabled"></i>
</td>
<td class="crm-search-admin-icon-col">
<span class="crm-editable-enabled" ng-click="pickIcon($index)">
- <i class="{{ item.icon ? 'crm-i ' + item.icon : '' }}"></i>
+ <i class="{{ item.icon ? 'crm-i ' + item.icon : '' }}" style="opacity: 1"></i>
</span>
</td>
+ <td>
+ <select class="form-control" ng-model="item.target">
+ <option value>{{:: ts('Normal') }}</option>
+ <option value="_blank">{{:: ts('New tab') }}</option>
+ <option value="crm-popup">{{:: ts('Popup dialog') }}</option>
+ </select>
+ </td>
<td>
<input type="text" class="form-control" ng-model="item.text">
</td>
<td class="form-inline">
- <select class="form-control crm-search-admin-select-path" ng-show="$ctrl.links.length">
- <option ng-repeat="link in $ctrl.links" value="{{ link.path }}" ng-selected="item.path === link.path">
- {{ link.title }}
- </option>
- <option value="civicrm/" ng-selected="item.path && !$ctrl.getLink(item.path)">
- {{ ts('Other...') }}
- </option>
- </select>
- <input class="form-control" type="text" ng-if="item.path && !$ctrl.getLink(item.path)" ng-model="item.path" ng-model-options="{updateOn: 'blur'}" />
- <crm-search-admin-token-select ng-if="item.path && !$ctrl.getLink(item.path)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="item" field="path"></crm-search-admin-token-select>
+ <crm-search-admin-link-select api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" link="item" links="$ctrl.links"></crm-search-admin-link-select>
</td>
- <td class="form-inline">
- <select class="form-control" ng-model="item.style">
- <option ng-repeat="opt in $ctrl.styles" value="{{ opt.key }}">{{ opt.value }}</option>
- </select>
+ <td>
+ <div class="btn-group">
+ <button type="button" style="min-width: 85px" class="btn btn-{{ item.style }} dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ $ctrl.getStyle(item).value }} <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li ng-repeat="opt in $ctrl.styles">
+ <a href class="bg-{{:: opt.key }}" ng-click="item.style = opt.key">{{:: opt.value }}</a>
+ </li>
+ </ul>
+ </div>
</td>
<td class="crm-search-admin-icon-col">
<button ng-if="$ctrl.group.length > 1" type="button" class="btn btn-xs btn-danger-outline" ng-click="$ctrl.group.splice($index, 1)">
<tfoot>
<tr>
<td colspan="6" class="form-inline">
- <select class="form-control crm-search-admin-select-path" ng-show="$ctrl.links.length">
+ <select class="form-control crm-search-admin-add-link" ng-show="$ctrl.links.length">
<option value="">
+ {{:: ts('Add...') }}
</option>
angular.module('crmSearchAdmin').component('crmSearchAdminLinkSelect', {
bindings: {
- column: '<',
+ link: '<',
apiEntity: '<',
apiParams: '<',
- links: '<'
+ links: '<',
+ onChange: '&'
},
templateUrl: '~/crmSearchAdmin/crmSearchAdminLinkSelect.html',
controller: function ($scope, $element, $timeout) {
ctrl = this;
this.setValue = function(val) {
+ ctrl.link = ctrl.link || {};
var link = ctrl.getLink(val),
- oldLink = ctrl.getLink(ctrl.column.link);
- if (link) {
- ctrl.column.link = link.path;
- ctrl.column.title = link.title;
- delete ctrl.column.editable;
- } else {
- if (val === 'civicrm/') {
- ctrl.column.link = val;
- $timeout(function () {
- $('input[type=text]', $element).focus();
- });
- } else {
- ctrl.column.link = '';
- }
- if (oldLink && ctrl.column.title === oldLink.title) {
- ctrl.column.title = '';
- }
+ oldVal = ctrl.link.path;
+ ctrl.link.path = val;
+ if (!link) {
+ $timeout(function () {
+ $('input[type=text]', $element).focus();
+ });
}
- };
-
- function onChange() {
- var val = $('select', $element).val();
- if (val !== ctrl.column.link) {
- ctrl.setValue(val);
- }
- }
-
- this.$onInit = function() {
- $('select', $element).on('change', function() {
- $scope.$apply(onChange);
- });
+ ctrl.onChange({before: oldVal, after: val});
};
this.getLink = function(path) {
-<label title="{{ ts('Display as clickable link') }}" >
- <input type="checkbox" ng-checked="$ctrl.column.link" ng-click="$ctrl.setValue($ctrl.column.link ? '' : ($ctrl.links[0] && $ctrl.links[0].path || 'civicrm/'))" >
- {{ $ctrl.column.link ? ts('Link:') : ts('Link') }}
-</label>
-<select class="form-control" ng-show="$ctrl.links.length && $ctrl.column.link">
- <option ng-repeat="link in $ctrl.links" value="{{ link.path }}" ng-selected="$ctrl.column.link === link.path">
- {{ link.title }}
- </option>
- <option value="civicrm/" ng-selected="$ctrl.column.link && !$ctrl.getLink($ctrl.column.link)">
- {{ ts('Other...') }}
- </option>
-</select>
-<input class="form-control" type="text" ng-if="$ctrl.column.link && !$ctrl.getLink($ctrl.column.link)" ng-model="$ctrl.column.link" ng-model-options="{updateOn: 'blur'}" />
-<crm-search-admin-token-select ng-if="$ctrl.column.link && !$ctrl.getLink($ctrl.column.link)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="$ctrl.column" field="link"></crm-search-admin-token-select>
+<div class="crm-flex-1 input-group" >
+ <input type="text" class="form-control" ng-if="!$ctrl.getLink($ctrl.link.path)" ng-model="$ctrl.link.path" ng-model-options="{updateOn: 'blur'}" ng-change="$ctrl.onChange({before: 'civicrm/', after: $ctrl.link.path})">
+ <div class="input-group-btn" style="{{ $ctrl.getLink($ctrl.link.path) ? '' : 'width:27px' }}">
+ <button type="button" class="btn btn-default-outline dropdown-toggle" style="min-width: 200px; text-align: left;" ng-if="$ctrl.getLink($ctrl.link.path)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ $ctrl.getLink($ctrl.link.path).title }}
+ </button>
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu {{ $ctrl.getLink($ctrl.link.path) ? '' : 'dropdown-menu-right' }}" style="min-width: 223px;">
+ <li ng-repeat="link in $ctrl.links" ng-class="{disabled: $ctrl.link.path === link.path}">
+ <a href ng-click="$ctrl.setValue(link.path)">{{:: link.title }}</a>
+ </li>
+ <li ng-class="{disabled: !$ctrl.getLink($ctrl.link.path)}">
+ <a href ng-click="$ctrl.setValue('civicrm/')">{{:: ts('Other...') }}</a>
+ </li>
+ </ul>
+ </div>
+</div>
+<crm-search-admin-token-select ng-if="!$ctrl.getLink($ctrl.link.path)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="$ctrl.link" field="path"></crm-search-admin-token-select>
-<crm-search-admin-link-select class="form-inline crm-search-admin-flex-row" column="col" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" links=":: $ctrl.parent.getLinks()"></crm-search-admin-link-select>
+<div class="form-inline crm-search-admin-flex-row">
+ <label title="{{ ts('Display as clickable link') }}" >
+ <input type="checkbox" ng-checked="col.link" ng-click="$ctrl.parent.toggleLink(col)" >
+ {{ col.link ? ts('Link:') : ts('Link') }}
+ </label>
+ <select class="form-control" ng-model="$ctrl.link.target" ng-if="col.link">
+ <option value>{{:: ts('Open normally') }}</option>
+ <option value="_blank">{{:: ts('New tab') }}</option>
+ <option value="crm-popup">{{:: ts('Popup dialog') }}</option>
+ </select>
+ <crm-search-admin-link-select ng-if="col.link" link="col.link" on-change="$ctrl.parent.onChangeLink(col, before, after)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" links=":: $ctrl.parent.getLinks()">
+ </crm-search-admin-link-select>
+</div>
<div class="form-inline crm-search-admin-flex-row">
<label>
<input type="checkbox" ng-checked="col.title" ng-click="col.title = col.title ? null : $ctrl.parent.getFieldLabel(col.key)" >
{{ col.title ? ts('Tooltip:') : ts('Tooltip') }}
</label>
- <input class="form-control" type="text" ng-model="col.title" ng-if="col.title" ng-model-options="{updateOn: 'blur'}" />
+ <input class="form-control crm-flex-1" type="text" ng-model="col.title" ng-if="col.title" ng-model-options="{updateOn: 'blur'}" />
<crm-search-admin-token-select ng-if="col.title" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="col" field="title"></crm-search-admin-token-select>
</div>
<div class="form-inline crm-search-admin-flex-row">
<input type="checkbox" ng-checked="col.rewrite" ng-click="$ctrl.parent.toggleRewrite(col)" >
{{ col.rewrite ? ts('Rewrite:') : ts('Rewrite') }}
</label>
- <input type="text" class="form-control" ng-if="col.rewrite" ng-model="col.rewrite" ng-model-options="{updateOn: 'blur'}">
+ <input type="text" class="form-control crm-flex-1" ng-if="col.rewrite" ng-model="col.rewrite" ng-model-options="{updateOn: 'blur'}">
<crm-search-admin-token-select ng-if="col.rewrite" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="col" field="rewrite"></crm-search-admin-token-select>
</div>
<div class="form-inline">
<input type="checkbox" ng-checked="col.label" ng-click="col.label = col.label ? null : $ctrl.parent.getColLabel(col)" >
{{ col.label ? ts('Label:') : ts('Label') }}
</label>
- <input ng-if="col.label" class="form-control" type="text" ng-model="col.label" ng-model-options="{updateOn: 'blur'}">
+ <input ng-if="col.label" class="form-control crm-flex-1" type="text" ng-model="col.label" ng-model-options="{updateOn: 'blur'}">
<crm-search-admin-token-select ng-if="col.label" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" model="col" field="label"></crm-search-admin-token-select>
</div>
<div class="form-inline" ng-if="col.label">
<legend>{{ $ctrl.parent.getColLabel(col) }}</legend>
<div class="form-inline crm-search-admin-flex-row">
<label for="crm-search-admin-edit-col-{{ $index }}">{{:: ts('Header:') }}</label>
- <input id="crm-search-admin-edit-col-{{ $index }}" class="form-control" type="text" ng-model="col.label" >
+ <input id="crm-search-admin-edit-col-{{ $index }}" class="form-control crm-flex-1" type="text" ng-model="col.label" >
<button type="button" class="btn-xs" ng-click="$ctrl.parent.removeCol($index)" title="{{:: ts('Remove') }}">
<i class="crm-i fa-ban"></i>
</button>
displayValue = column.rewrite ? replaceTokens(column.rewrite, rowData, rowMeta) : formatRawValue(column, rowData[key]),
result = _.escape(displayValue);
if (column.link) {
- result = '<a href="' + _.escape(getUrl(column.link, rowData)) + '">' + result + '</a>';
+ var target = '';
+ if (column.link.target) {
+ target = column.link.target === 'crm-popup' ? 'class="crm-popup" ' : 'target="' + column.link.target + '" ';
+ }
+ result = '<a ' + target + 'href="' + _.escape(getUrl(column.link.path, rowData)) + '">' + result + '</a>';
}
return result;
}
<span ng-repeat="item in col.links">
- <a class="btn {{:: col.size }} btn-{{:: item.style }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
+ <a class="btn {{:: col.size }} btn-{{:: item.style }} {{:: item.target }}" target="{{:: item.target }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
<i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
{{:: item.text }}
</a>
<span ng-repeat="item in col.links">
- <a class="text-{{:: item.style }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
+ <a class="text-{{:: item.style }} {{:: item.target }}" target="{{:: item.target }}" href="{{:: displayUtils.getUrl(item.path, row) }}">
<i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
{{:: item.text }}
</a>
</button>
<ul class="dropdown-menu {{ col.alignment === 'text-right' ? 'dropdown-menu-right' : '' }}" ng-if=":: col.open">
<li ng-repeat="item in col.links" class="bg-{{:: item.style }}">
- <a href="{{:: displayUtils.getUrl(item.path, row) }}">
+ <a href="{{:: displayUtils.getUrl(item.path, row) }}" class="{{:: item.target }}" target="{{:: item.target }}">
<i ng-if=":: item.icon" class="crm-i {{:: item.icon }}"></i>
{{:: item.text }}
</a>
background-color: rgba(255, 255, 255, .8);
}
+/* A flex-row arranges elements inline, with the 'crm-flex-*' item(s) taking up all remaining space */
#bootstrap-theme .crm-search-admin-flex-row {
display: flex;
align-items: center;
#bootstrap-theme .crm-search-admin-flex-row > *:not(:first-child) {
margin-left: 6px;
}
-#bootstrap-theme .crm-search-admin-flex-row > input[type=text] {
- flex: 1;
-}
#bootstrap-theme .crm-search-admin-unused-columns fieldset {
min-height: 60px;