Commit 1e1e198c authored by colemanw's avatar colemanw
Browse files

GUI - Implement Save Block feature

parent 60cdbdaf
......@@ -206,22 +206,19 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
}
}
// Load blocks
// Load fields from entity joins
$blockData = \Civi\Api4\Afform::get()
->setCheckPermissions(FALSE)
->addWhere('block', 'IS NOT NULL')
->setSelect(['name', 'title', 'block', 'join', 'layout', 'repeat'])
->setFormatWhitespace(TRUE)
->setLayoutFormat('shallow')
->addWhere('join', 'IS NOT NULL')
->setSelect(['join'])
->execute();
foreach ($blockData as $block) {
if (!empty($block['join']) && !isset($data['entities'][$block['join']]['fields'])) {
if (!isset($data['entities'][$block['join']]['fields'])) {
$data['entities'][$block['join']]['entity'] = $block['join'];
// Normally you shouldn't pass variables to ts() but very common strings like "Email" should already exist
$data['entities'][$block['join']]['label'] = ts($block['join']);
$data['entities'][$block['join']]['fields'] = (array) civicrm_api4($block['join'], 'getFields', $getFieldParams, 'name');
}
$data['blocks'][_afform_angular_module_name($block['name'], 'dash')] = $block;
}
// Todo: scan for other elements
......@@ -252,15 +249,15 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
'#markup' => FALSE,
],
],
'button' => [
'title' => ts('Button'),
'submit' => [
'title' => ts('Submit Button'),
'element' => [
'#tag' => 'button',
'class' => 'af-button btn-primary',
'crm-icon' => 'fa-check',
'ng-click' => 'afform.submit()',
'#children' => [
['#text' => ts('Enter text')],
['#text' => ts('Submit')],
],
],
],
......
......@@ -7,7 +7,7 @@ return [
],
'css' => ['ang/afGuiEditor.css'],
'partials' => ['ang/afGuiEditor'],
'requires' => ['crmUi', 'crmUtil'],
'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4'],
'settings' => [],
'basePages' => [],
'exports' => [
......
......@@ -43,36 +43,37 @@
'#children': []
}]
};
if ($scope.afGuiEditor.name && $scope.afGuiEditor.name != '0') {
// Todo - show error msg if form is not found
crmApi4('Afform', 'get', {where: [['name', '=', $scope.afGuiEditor.name]], layoutFormat: 'shallow', formatWhitespace: true}, 0)
.then(initialize);
}
else {
$timeout(function() {
initialize(_.cloneDeep(newForm));
editor.addEntity('Individual');
$scope.layout['#children'].push({
"#tag": "button",
"class": 'af-button btn btn-primary',
"crm-icon": 'fa-check',
"ng-click": "afform.submit()",
"#children": [
{
"#text": "Submit"
}
]
});
// Fetch the current form plus all blocks
crmApi4('Afform', 'get', {where: [["OR", [["name", "=", $scope.afGuiEditor.name], ["block", "IS NOT NULL"]]]], layoutFormat: 'shallow', formatWhitespace: true})
.then(initialize);
// Initialize the current form + list of blocks
function initialize(afforms) {
$scope.meta.blocks = {};
_.each(afforms, function(form) {
evaluate(form.layout);
if (form.block) {
$scope.meta.blocks[_.kebabCase(form.name)] = form;
}
if (form.name === $scope.afGuiEditor.name) {
$scope.afform = form;
}
});
}
function initialize(afform) {
$scope.afform = afform;
if (!$scope.afform) {
$scope.afform = _.cloneDeep(newForm);
if ($scope.afGuiEditor.name != '0') {
alert('Error: could not find form ' + $scope.afGuiEditor.name);
}
}
$scope.changesSaved = 1;
$scope.layout = findRecursive($scope.afform.layout, {'#tag': 'af-form'})[0];
evaluate($scope.layout['#children']);
$scope.entities = findRecursive($scope.layout['#children'], {'#tag': 'af-entity'}, 'name');
if ($scope.afGuiEditor.name == '0') {
editor.addEntity('Individual');
$scope.layout['#children'].push($scope.meta.elements.submit.element);
}
// Set changesSaved to true on initial load, false thereafter whenever changes are made to the model
$scope.$watch('afform', function () {
$scope.changesSaved = $scope.changesSaved === 1;
......@@ -394,7 +395,7 @@
};
});
angular.module('afGuiEditor').directive('afGuiContainer', function() {
angular.module('afGuiEditor').directive('afGuiContainer', function(crmApi4, dialogService) {
return {
restrict: 'A',
templateUrl: '~/afGuiEditor/container.html',
......@@ -548,8 +549,7 @@
$scope.node['#tag'] = 'div';
$scope.node['class'] = 'af-container';
}
block.override = true;
block.layout = null;
block.layout = block.directive = null;
}
$scope.layouts = {
......@@ -573,13 +573,34 @@
modifyClasses($scope.node, _.keys($scope.layouts), classes);
};
$scope.selectBlockDirective = function() {
if (block.directive) {
block.layout = _.cloneDeep($scope.editor.meta.blocks[block.directive].layout);
block.original = block.directive;
setBlockDirective(block.directive);
}
else {
overrideBlockContents(block.layout);
}
};
if (($scope.node['#tag'] in $scope.editor.meta.blocks) || $scope.join) {
initializeBlockContainer();
}
function initializeBlockContainer() {
// Cancel the below $watch expressions if already set
_.each(block.listeners, function(deregister) {
deregister();
});
block = $scope.block = {
directive: null,
layout: null,
override: false,
original: null,
options: [],
listeners: []
};
_.each($scope.editor.meta.blocks, function(blockInfo, directive) {
......@@ -591,35 +612,49 @@
}
});
$scope.$watch('block.directive', function (directive, oldVal) {
if (directive && directive !== oldVal) {
block.layout = _.cloneDeep($scope.editor.meta.blocks[directive].layout);
setBlockDirective(directive);
block.override = false;
}
else if (!directive && oldVal) {
overrideBlockContents(block.layout);
block.override = false;
}
});
if (getBlockNode() && getBlockNode()['#tag'] in $scope.editor.meta.blocks) {
block.directive = block.original = getBlockNode()['#tag'];
block.layout = _.cloneDeep($scope.editor.meta.blocks[block.directive].layout);
}
$scope.$watch('block.layout', function (layout, oldVal) {
if (block.directive && !block.override && layout && layout !== oldVal && !angular.equals(layout, $scope.editor.meta.blocks[block.directive].layout)) {
block.listeners.push($scope.$watch('block.layout', function (layout, oldVal) {
if (block.directive && layout && layout !== oldVal && !angular.equals(layout, $scope.editor.meta.blocks[block.directive].layout)) {
overrideBlockContents(block.layout);
}
}, true);
$scope.$watch("node['#children']", function (children) {
if (getBlockNode() && getBlockNode()['#tag'] in $scope.editor.meta.blocks) {
block.directive = getBlockNode()['#tag'];
block.override = false;
} else if (block.directive && block.override && !block.layout && children && angular.equals(children, $scope.editor.meta.blocks[block.directive].layout)) {
block.layout = _.cloneDeep($scope.editor.meta.blocks[block.directive].layout);
$scope.node['#children'] = [{'#tag': block.directive}];
block.override = false;
}
}, true);
}, true));
}
$scope.saveBlock = function() {
var options = CRM.utils.adjustDialogDefaults({
width: '500px',
height: '300px',
autoOpen: false,
title: ts('Save block')
});
var model = {
title: '',
name: null,
layout: $scope.node['#children']
};
if ($scope.join) {
model.join = $scope.join;
}
if ($scope.block && $scope.block.original) {
model.title = $scope.editor.meta.blocks[$scope.block.original].title;
model.name = $scope.editor.meta.blocks[$scope.block.original].name;
model.block = $scope.editor.meta.blocks[$scope.block.original].block;
}
else {
model.block = $scope.container.getFieldEntityType() || '*';
}
dialogService.open('saveBlockDialog', '~/afGuiEditor/saveBlock.html', model, options)
.then(function(block) {
$scope.editor.meta.blocks[_.kebabCase(block.name)] = block;
setBlockDirective(_.kebabCase(block.name));
initializeBlockContainer();
});
};
},
controller: function($scope) {
var container = $scope.container = this;
......@@ -670,6 +705,35 @@
};
});
angular.module('afGuiEditor').controller('afGuiSaveBlock', function($scope, crmApi4, dialogService) {
var ts = $scope.ts = CRM.ts(),
model = $scope.model,
original = $scope.original = {
title: model.title,
name: model.name
};
if (model.name) {
$scope.$watch('model.name', function(val, oldVal) {
if (!val && model.title === original.title) {
model.title += ' ' + ts('(copy)');
}
else if (val === original.name && val !== oldVal) {
model.title = original.title;
}
});
}
$scope.cancel = function() {
dialogService.cancel('saveBlockDialog');
};
$scope.save = function() {
$('.ui-dialog:visible').block();
crmApi4('Afform', 'save', {formatWhitespace: true, records: [JSON.parse(angular.toJson(model))]})
.then(function(result) {
dialogService.close('saveBlockDialog', result[0]);
});
};
});
angular.module('afGuiEditor').directive('afGuiField', function() {
return {
restrict: 'A',
......
......@@ -3,6 +3,8 @@
<li><a href ng-click="addElement('div.af-markup', {'#markup': false})">{{ ts('Add rich content') }}</a></li>
<li><a href ng-click="addElement('button.af-button.btn-primary', {'crm-icon': 'fa-check', 'ng-click': 'afform.submit()'})">{{ ts('Add button') }}</a></li>
<li role="separator" class="divider"></li>
<li ng-if="!node['af-fieldset'] && !block.layout"><a href ng-click="saveBlock()">{{ ts('Save as block') }}</a></li>
<li ng-if="!node['af-fieldset'] && !block.layout" role="separator" class="divider"></li>
<li ng-if="tags[node['#tag']]">
<div class="af-gui-field-select-in-dropdown form-inline" ng-click="$event.stopPropagation()">
{{ ts('Element:') }}
......
......@@ -3,10 +3,11 @@
<span ng-if="container.getNodeType(node) == 'fieldset'">{{ editor.getEntity(entityName).label }}</span>
<span ng-if="block">{{ join ? ts(join) + ':' : ts('Block:') }}</span>
<span ng-if="!block">{{ tags[node['#tag']].toLowerCase() }}</span>
<select ng-if="block" ng-model="block.directive">
<select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()">
<option value="">{{ ts('Custom') }}</option>
<option value="{{ option.id }}" ng-repeat="option in block.options track by option.id">{{ option.text }}</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>
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button pull-right" data-toggle="dropdown" title="{{ ts('Add Element') }}">
<span><i class="crm-i fa-plus"></i></span>
</button>
......
<form id="bootstrap-theme">
<div ng-controller="afGuiSaveBlock">
<div class="alert alert-info">
{{ ts('Saving a container as a block allows it to be reused across forms.') }}
</div>
<div>
<label for="af-gui-save-block-title">{{ ts('Title:') }} <span class="crm-marker">*</span></label>
<input id="af-gui-save-block-title" class="form-control" ng-model="model.title" required>
</div>
<div class="form-inline" ng-if="original.name">
<label>
<input type="radio" name="save-as" ng-value="original.name" ng-model="model.name" />
{{ ts('Update %1', {1: original.title}) }}
</label>
&nbsp;
<label>
<input type="radio" name="save-as" ng-value="null" ng-model="model.name" />
{{ ts('Save new block') }}
</label>
</div>
<div class="alert alert-warning" ng-show="model.name">
{{ ts('Affects every form that uses this block, system-wide.') }}
</div>
<hr />
<div class="buttons pull-right">
<button ng-click="save()" class="btn btn-primary" ng-disabled="!model.title">{{ ts('Save') }}</button>
<button ng-click="cancel()" class="btn btn-danger">{{ ts('Cancel') }}</button>
</div>
</div>
</form>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment