Skip to content

Commit

Permalink
Afform - add collapsible title as directive
Browse files Browse the repository at this point in the history
Before: A fieldset `<legend>` was treated as its own element. This was more flexible but more complex.
After: Augenerated `<legend> for fieldsets or `<h4>` for other containers based on new `af-title` directive.

This allows central control of titles for e.g. collapsible styles.
Fixes dev/core#3110
  • Loading branch information
colemanw committed Mar 20, 2022
1 parent 666a68e commit be12535
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 25 deletions.
13 changes: 2 additions & 11 deletions ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,8 @@ public static function getGuiSettings() {
'element' => [
'#tag' => 'fieldset',
'af-fieldset' => NULL,
'#children' => [
[
'#tag' => 'legend',
'class' => 'af-text',
'#children' => [
[
'#text' => E::ts('Enter title'),
],
],
],
],
'af-title' => E::ts('Enter title'),
'#children' => [],
],
],
];
Expand Down
18 changes: 14 additions & 4 deletions ext/afform/admin/ang/afGuiEditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@
font-family: "Courier New", Courier, monospace;
font-size: 12px;
}
#afGuiEditor-canvas:not(.af-gui-menu-open) .af-gui-bar {
#afGuiEditor-canvas:not(.af-gui-menu-open) .af-gui-bar,
#afGuiEditor-canvas:not(.af-gui-menu-open) .af-gui-container-title span:empty {
opacity: 0;
}
#afGuiEditor-canvas [ui-sortable] .af-gui-bar {
Expand All @@ -153,11 +154,13 @@
left: 0;
padding-left: 15px;
}
#afGuiEditor:not(.af-gui-dragging *) #afGuiEditor-canvas:hover .af-gui-bar {
#afGuiEditor:not(.af-gui-dragging *) #afGuiEditor-canvas:hover .af-gui-bar,
#afGuiEditor:not(.af-gui-dragging *) #afGuiEditor-canvas:hover .af-gui-container-title span:empty {
opacity: 1;
transition: opacity .2s;
}
#afGuiEditor #afGuiEditor-canvas .af-gui-dragtarget > .af-gui-bar {
#afGuiEditor #afGuiEditor-canvas .af-gui-dragtarget > .af-gui-bar,
#afGuiEditor #afGuiEditor-canvas .af-gui-dragtarget > .af-gui-container-title span:empty {
background-color: #d7e6ff;
opacity: 1;
transition: opacity .1s;
Expand Down Expand Up @@ -263,7 +266,8 @@ body.af-gui-dragging {
}
/* Fix button colors when bar is highlighted */
#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-bar > .form-inline > .btn-group > .btn-group > button > span,
#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-bar > .form-inline > span {
#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-bar > .form-inline > span,
#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-node-title {
color: white;
}
#afGuiEditor #afGuiEditor-canvas .af-entity-selected > .af-gui-bar > .form-inline > .btn-group > .btn-group > button:hover > span,
Expand Down Expand Up @@ -381,6 +385,12 @@ body.af-gui-dragging {
margin-right: 20px;
position: relative;
}
#afGuiEditor .af-gui-container-title {
top: -21px;
}
#afGuiEditor .af-gui-container-title span:empty {
font-weight: lighter;
}

#afGuiEditor .af-gui-field-required:after {
content: '*';
Expand Down
17 changes: 16 additions & 1 deletion ext/afform/admin/ang/afGuiEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@
return str ? _.unique(_.trim(str).split(/\s+/g)) : [];
}

// Check if a node has class(es)
function hasClass(node, className) {
if (!node['class']) {
return false;
}
var classes = splitClass(node['class']),
classNames = className.split(' ');
return _.intersection(classes, classNames).length === classNames.length;
}

function modifyClasses(node, toRemove, toAdd) {
var classes = splitClass(node['class']);
if (toRemove) {
Expand All @@ -64,7 +74,11 @@
if (toAdd) {
classes = _.unique(classes.concat(splitClass(toAdd)));
}
node['class'] = classes.join(' ');
if (classes.length) {
node['class'] = classes.join(' ');
} else if ('class' in node) {
delete node['class'];
}
}

return {
Expand Down Expand Up @@ -202,6 +216,7 @@
},

splitClass: splitClass,
hasClass: hasClass,
modifyClasses: modifyClasses,
getStyles: getStyles,
setStyle: setStyle,
Expand Down
3 changes: 2 additions & 1 deletion ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
// Create a new af-fieldset container for the entity
var fieldset = _.cloneDeep(afGui.meta.elements.fieldset.element);
fieldset['af-fieldset'] = type + num;
fieldset['#children'][0]['#children'][0]['#text'] = meta.label + ' ' + num;
fieldset['af-title'] = meta.label + ' ' + num;
// Add boilerplate contents
_.each(meta.boilerplate, function (tag) {
fieldset['#children'].push(tag);
Expand Down Expand Up @@ -274,6 +274,7 @@
var fieldset = {
'#tag': 'div',
'af-fieldset': '',
'af-title': display.label,
'#children': [
{
'#tag': display.tag,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// https://civicrm.org/licensing
(function(angular, $, _) {
"use strict";

// Menu item to control the border property of a node
angular.module('afGuiEditor').component('afGuiMenuItemCollapsible', {
templateUrl: '~/afGuiEditor/afGuiMenuItemCollapsible.html',
bindings: {
node: '='
},
controller: function($scope, afGui) {
var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'),
ctrl = this;

this.isCollapsible = function() {
return afGui.hasClass(ctrl.node, 'af-collapsible');
};

this.isCollapsed = function() {
return afGui.hasClass(ctrl.node, 'af-collapsible af-collapsed');
};

this.toggleCollapsible = function() {
// Node must have a title to be collapsible
if (ctrl.isCollapsible() || !ctrl.node['af-title']) {
afGui.modifyClasses(ctrl.node, 'af-collapsible af-collapsed');
} else {
afGui.modifyClasses(ctrl.node, null, 'af-collapsible');
}
};

this.toggleCollapsed = function() {
if (ctrl.isCollapsed()) {
afGui.modifyClasses(ctrl.node, 'af-collapsed');
} else {
afGui.modifyClasses(ctrl.node, null, 'af-collapsed');
}
};

}
});

})(angular, CRM.$, CRM._);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<label ng-class="{disabled: !$ctrl.node['af-title']}" ng-click="$ctrl.toggleCollapsible(); $event.stopPropagation();" title="{{ $ctrl.node['af-title'] ? ts('Allow user to collapse this to only show title') : ts('Must have a title to be collapsible') }}">
<i class="crm-i fa-{{ $ctrl.isCollapsible() ? 'check-' : '' }}square-o"></i>
{{:: ts('Collapsible') }}
</label>
<a href ng-click="$ctrl.toggleCollapsed(); $event.stopPropagation();" class="btn btn-sm btn-default" ng-class="{invisible: !$ctrl.isCollapsible()}">
<i class="crm-i fa-caret-{{ $ctrl.isCollapsed() ? 'right' : 'down' }}"></i>
{{ $ctrl.isCollapsed() ? ts('Closed') : ts('Open') }}
</a>
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
</div>
</div>
</li>
<li><af-gui-menu-item-collapsible ng-if="!block" node="$ctrl.node" class="af-gui-field-select-in-dropdown form-inline"></af-gui-menu-item-collapsible></li>
<li><af-gui-menu-item-border node="$ctrl.node"></af-gui-menu-item-border></li>
<li><af-gui-menu-item-background node="$ctrl.node"></af-gui-menu-item-background></li>
<li role="separator" class="divider"></li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@
}
};

this.getCollapsibleIcon = function() {
if (afGui.hasClass(ctrl.node, 'af-collapsible')) {
return afGui.hasClass(ctrl.node, 'af-collapsed') ? 'fa-caret-right' : 'fa-caret-down';
}
};

// Sets min value for af-repeat as a string, returns it as an int
$scope.getSetMin = function(val) {
if (arguments.length) {
Expand Down Expand Up @@ -332,6 +338,33 @@
return type.length ? type[0].replace('af-', '') : null;
};

this.getSetTitle = function(value) {
if (arguments.length) {
if (value.length) {
ctrl.node['af-title'] = value;
} else {
delete ctrl.node['af-title'];
// With no title, cannot be collapsible
afGui.modifyClasses(ctrl.node, 'af-collapsible af-collapsed');
}
}
return ctrl.node['af-title'];
};

this.getToolTip = function() {
var text = '', nodeType;
if (!$scope.block) {
nodeType = ctrl.getNodeType(ctrl.node);
if (nodeType === 'fieldset') {
text = ctrl.editor.getEntity(ctrl.entityName).label;
} else if (nodeType === 'searchFieldset') {
text = ts('Search Display');
}
text += ' ' + $scope.tags[ctrl.node['#tag']];
}
return text;
};

this.removeElement = function(element) {
afGui.removeRecursive($scope.getSetChildren(), {$$hashKey: element.$$hashKey});
ctrl.editor.onRemoveElement();
Expand Down
17 changes: 9 additions & 8 deletions ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
<div class="af-gui-bar" ng-if="$ctrl.node['#tag']" ng-click="selectEntity()" >
<div ng-if="!$ctrl.loading" class="form-inline">
<span ng-if="$ctrl.getNodeType($ctrl.node) == 'fieldset'">{{ $ctrl.editor.getEntity($ctrl.entityName).label }}</span>
<span ng-if="$ctrl.getNodeType($ctrl.node) == 'searchFieldset'">{{:: ts('Search Display') }}</span>
<div class="af-gui-bar {{ block ? 'af-gui-block-bar' : '' }}" ng-if="$ctrl.node['#tag']" ng-click="selectEntity()" >
<div ng-if="!$ctrl.loading" class="form-inline" title="{{ $ctrl.getToolTip() }}">
<span ng-if="block">{{ $ctrl.join ? ts($ctrl.join) + ':' : ts('Block:') }}</span>
<span ng-if="!block">{{ tags[$ctrl.node['#tag']] }}</span>
<select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()">
<select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()" title="{{:: ts('Select block') }}">
<option value="">{{:: ts('Custom') }}</option>
<option ng-value="option.id" ng-repeat="option in block.options track by option.id">{{ option.text }}</option>
</select>
<button type="button" class="btn btn-default btn-xs" ng-if="block && !block.layout" ng-click="saveBlock()">{{:: ts('Save...') }}</button>
<div class="btn-group pull-right">
<button type="button" class="btn btn-default btn-xs" ng-if="block && !block.layout" ng-click="saveBlock()" title="{{:: ts('Save block') }}">{{:: ts('Save...') }}</button>
<div class="btn-group pull-right" title="">
<af-gui-container-multi-toggle ng-if="!ctrl.loading && ($ctrl.join || $ctrl.node['af-repeat'])" entity="$ctrl.getFieldEntityType()" class="btn-group"></af-gui-container-multi-toggle>
<div class="btn-group" af-gui-menu>
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
Expand All @@ -21,6 +18,10 @@
</div>
<div ng-if="$ctrl.loading"><i class="crm-i fa-spin fa-spinner"></i></div>
</div>
<label class="af-gui-node-title af-gui-container-title af-gui-text-h3" ng-if="$ctrl.node['#tag'] && !block" title="{{:: ts('Container title') }}">
<i class="crm-i {{ $ctrl.getCollapsibleIcon() }}"></i>
<span placeholder="{{:: ts('No title') }}" crm-ui-editable ng-model="$ctrl.getSetTitle" ng-model-options="{getterSetter: true}"></span>
</label>
<div ng-if="!$ctrl.loading" ui-sortable="$ctrl.sortableOptions" ui-sortable-update="$ctrl.editor.onDrop" ng-model="getSetChildren" ng-model-options="{getterSetter: true}" class="af-gui-layout {{ getLayout() }}">
<div ng-repeat="item in getSetChildren()" >
<div ng-switch="$ctrl.getNodeType(item)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</select>
</div>
</li>
<li><af-gui-menu-item-collapsible node="$ctrl.node" class="af-gui-field-select-in-dropdown form-inline"></af-gui-menu-item-collapsible></li>
<li><af-gui-menu-item-border node="$ctrl.node"></af-gui-menu-item-border></li>
<li><af-gui-menu-item-background node="$ctrl.node"></af-gui-menu-item-background></li>
<li role="separator" class="divider"></li>
Expand Down
28 changes: 28 additions & 0 deletions ext/afform/core/ang/af/afTitle.directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(function(angular, $, _) {
"use strict";
angular.module('af').directive('afTitle', function() {
return {
restrict: 'A',
bindToController: {
title: '@afTitle'
},
controller: function($scope, $element) {
var ctrl = this;

$scope.$watch(function() {return ctrl.title;}, function(text) {
var tag = $element.is('fieldset') ? 'legend' : 'h4',
$title = $element.children(tag + '.af-title');
if (!$title.length) {
$title = $('<' + tag + ' class="af-title" />').prependTo($element);
if ($element.hasClass('af-collapsible')) {
$title.click(function() {
$element.toggleClass('af-collapsed');
});
}
}
$title.text(text);
});
}
};
});
})(angular, CRM.$, CRM._);
17 changes: 17 additions & 0 deletions ext/afform/core/ang/afCore.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,20 @@ af-form {
top: 0;
right: 0;
}

/* Collapsible containers */
.af-collapsible > .af-title {
cursor: pointer;
}
.af-collapsible > .af-title:before {
font-family: "FontAwesome";
display: inline-block;
width: 1em;
content: "\f0d7";
}
.af-collapsible.af-collapsed > .af-title:before {
content: "\f0da";
}
.af-collapsible.af-collapsed > .af-title + * {
display: none;
}

0 comments on commit be12535

Please sign in to comment.