From 9633741a59a7f49f9a7a64e753a8e89f51423656 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Thu, 2 Jan 2020 22:01:19 -0500 Subject: [PATCH] GUI - Implement Save Block feature --- ext/afform/gui/afform_gui.php | 17 +- ext/afform/gui/ang/afGuiEditor.ang.php | 2 +- ext/afform/gui/ang/afGuiEditor.js | 172 ++++++++++++------ .../gui/ang/afGuiEditor/container-menu.html | 2 + ext/afform/gui/ang/afGuiEditor/container.html | 5 +- ext/afform/gui/ang/afGuiEditor/saveBlock.html | 30 +++ 6 files changed, 161 insertions(+), 67 deletions(-) create mode 100644 ext/afform/gui/ang/afGuiEditor/saveBlock.html diff --git a/ext/afform/gui/afform_gui.php b/ext/afform/gui/afform_gui.php index 040fc2e1a8..ff9db85758 100644 --- a/ext/afform/gui/afform_gui.php +++ b/ext/afform/gui/afform_gui.php @@ -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')], ], ], ], diff --git a/ext/afform/gui/ang/afGuiEditor.ang.php b/ext/afform/gui/ang/afGuiEditor.ang.php index 18459b2234..cea47a8d19 100644 --- a/ext/afform/gui/ang/afGuiEditor.ang.php +++ b/ext/afform/gui/ang/afGuiEditor.ang.php @@ -7,7 +7,7 @@ return [ ], 'css' => ['ang/afGuiEditor.css'], 'partials' => ['ang/afGuiEditor'], - 'requires' => ['crmUi', 'crmUtil'], + 'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4'], 'settings' => [], 'basePages' => [], 'exports' => [ diff --git a/ext/afform/gui/ang/afGuiEditor.js b/ext/afform/gui/ang/afGuiEditor.js index 39bdca95c2..1a2655227d 100644 --- a/ext/afform/gui/ang/afGuiEditor.js +++ b/ext/afform/gui/ang/afGuiEditor.js @@ -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', diff --git a/ext/afform/gui/ang/afGuiEditor/container-menu.html b/ext/afform/gui/ang/afGuiEditor/container-menu.html index 6c9b62176c..44d25ba239 100644 --- a/ext/afform/gui/ang/afGuiEditor/container-menu.html +++ b/ext/afform/gui/ang/afGuiEditor/container-menu.html @@ -3,6 +3,8 @@
  • {{ ts('Add rich content') }}
  • {{ ts('Add button') }}
  • +
  • {{ ts('Save as block') }}
  • +
  • {{ ts('Element:') }} diff --git a/ext/afform/gui/ang/afGuiEditor/container.html b/ext/afform/gui/ang/afGuiEditor/container.html index d620e89232..0f71f098d3 100644 --- a/ext/afform/gui/ang/afGuiEditor/container.html +++ b/ext/afform/gui/ang/afGuiEditor/container.html @@ -3,10 +3,11 @@ {{ editor.getEntity(entityName).label }} {{ join ? ts(join) + ':' : ts('Block:') }} {{ tags[node['#tag']].toLowerCase() }} - - + + diff --git a/ext/afform/gui/ang/afGuiEditor/saveBlock.html b/ext/afform/gui/ang/afGuiEditor/saveBlock.html new file mode 100644 index 0000000000..d652b2ea81 --- /dev/null +++ b/ext/afform/gui/ang/afGuiEditor/saveBlock.html @@ -0,0 +1,30 @@ +
    +
    +
    + {{ ts('Saving a container as a block allows it to be reused across forms.') }} +
    +
    + + +
    +
    + +   + +
    +
    + {{ ts('Affects every form that uses this block, system-wide.') }} +
    +
    +
    + + +
    +
    +
    -- 2.25.1