Skip to content

Commit

Permalink
Merge pull request #22975 from colemanw/afformLayout2
Browse files Browse the repository at this point in the history
Afform - Easier layout creation with predefined container styles and built-in title
  • Loading branch information
eileenmcnaughton authored Mar 20, 2022
2 parents 1097f93 + 082da25 commit 7ae979d
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 47 deletions.
24 changes: 24 additions & 0 deletions ang/crmUI.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* CSS rules for Angular module "crmUI" */

/* In-place edit */
.crm-container [crm-ui-editable] {
padding-left: 2px;
border: 2px dashed transparent;
}
.crm-container [crm-ui-editable]:hover,
.crm-container [crm-ui-editable]:focus {
border: 2px dashed #d1d1d1;
cursor: pointer;
background-color: white !important;
color: initial !important;
}
.crm-container span[crm-ui-editable] {
display: inline-block !important;
padding-right: 2px;
min-height: 1em;
min-width: 3em;
}
.crm-container [crm-ui-editable]:empty:before {
content: attr(placeholder);
color: #9a9a9a;
}
1 change: 1 addition & 0 deletions ang/crmUi.ang.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'ext' => 'civicrm',
'js' => ['ang/crmUi.js'],
'partials' => ['ang/crmUi'],
'css' => ['ang/crmUI.css'],
'requires' => array_merge(
[
'crmResource',
Expand Down
8 changes: 5 additions & 3 deletions ang/crmUi.js
Original file line number Diff line number Diff line change
Expand Up @@ -1107,8 +1107,10 @@
};
})

// Editable text using ngModel & html5 contenteditable
// Usage: <span crm-ui-editable ng-model="my.data">{{ my.data }}</span>
// Single-line editable text using ngModel & html5 contenteditable
// Supports a `placeholder` attribute which shows up if empty and no `default-value`.
// The `default-value` attribute will force a value if empty (mutually-exclusive with `placeholder`).
// Usage: <span crm-ui-editable ng-model="model.text" placeholder="Enter text"></span>
.directive("crmUiEditable", function() {
return {
restrict: "A",
Expand Down Expand Up @@ -1150,7 +1152,7 @@
scope.$apply(read);
});

element.attr('contenteditable', 'true').addClass('crm-editable-enabled');
element.attr('contenteditable', 'true');
}
};
})
Expand Down
14 changes: 3 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,9 @@ public static function getGuiSettings() {
'element' => [
'#tag' => 'fieldset',
'af-fieldset' => NULL,
'#children' => [
[
'#tag' => 'legend',
'class' => 'af-text',
'#children' => [
[
'#text' => E::ts('Enter title'),
],
],
],
],
'class' => 'af-container',
'af-title' => E::ts('Enter title'),
'#children' => [],
],
],
];
Expand Down
26 changes: 22 additions & 4 deletions ext/afform/admin/ang/afGuiEditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
#afGuiEditor .crm-editable-enabled:hover:not(:focus) {
border: 2px dashed grey !important;
}
/* Undo Shoreditch add-ons */
#afGuiEditor .crm-editable-enabled:before,
#afGuiEditor .crm-editable-enabled:after {
content: '';
Expand Down Expand Up @@ -141,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 @@ -152,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 @@ -200,6 +204,13 @@ body.af-gui-dragging {
margin-top: 10px;
}

/* Card style */
#afGuiEditor .af-gui-container.af-container-style-pane {
box-shadow: 1px 2px 8px 1px rgb(0, 0, 0, .3);
background: linear-gradient(to bottom, #f2f2f2 0 22px, transparent 22px 100%) no-repeat;
border-radius: 4px;
}

#afGuiEditor af-gui-container,
#afGuiEditor af-gui-markup,
#afGuiEditor af-gui-field,
Expand Down Expand Up @@ -262,7 +273,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 @@ -380,6 +392,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>
31 changes: 31 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/afGuiMenuItemStyle.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// https://civicrm.org/licensing
(function(angular, $, _) {
"use strict";

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

// Todo: Make this an option group so other extensions can add to it
this.styles = [
{name: 'af-container-style-pane', label: ts('Panel Pane')}
];

$scope.getSetStyle = function(style) {
var options = _.map(ctrl.styles, 'name');
if (arguments.length) {
afGui.modifyClasses(ctrl.node, options, style);
}
return _.intersection(afGui.splitClass(ctrl.node['class']), options)[0] || '';
};

}
});

})(angular, CRM.$, CRM._);
7 changes: 7 additions & 0 deletions ext/afform/admin/ang/afGuiEditor/afGuiMenuItemStyle.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="af-gui-field-select-in-dropdown form-inline" ng-click="$event.stopPropagation()">
<label>{{:: ts('Style:') }}</label>
<select class="form-control" ng-model="getSetStyle" ng-model-options="{getterSetter: true}">
<option value="">{{:: ts('None') }}</option>
<option ng-repeat="style in $ctrl.styles" value="{{:: style.name }}">{{:: style.label }}</option>
</select>
</div>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<li ng-if="!$ctrl.node['af-fieldset'] && !block.layout"><a href ng-click="saveBlock()">{{:: ts('Save as block') }}</a></li>
<li ng-if="!$ctrl.node['af-fieldset'] && !block.layout" role="separator" class="divider"></li>
<li ng-if="$ctrl.canSaveAsBlock()"><a href ng-click="saveBlock()">{{:: ts('Save as block') }}</a></li>
<li ng-if="$ctrl.canSaveAsBlock()" role="separator" class="divider"></li>
<li ng-if="tags[$ctrl.node['#tag']]">
<div class="af-gui-field-select-in-dropdown form-inline" ng-click="$event.stopPropagation()">
{{:: ts('Element:') }}
Expand Down Expand Up @@ -30,6 +30,8 @@
</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-style node="$ctrl.node"></af-gui-menu-item-style></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
Loading

0 comments on commit 7ae979d

Please sign in to comment.