Afform - Support editing new element types added by extensions
authorColeman Watts <coleman@civicrm.org>
Mon, 31 Oct 2022 11:54:33 +0000 (07:54 -0400)
committerColeman Watts <coleman@civicrm.org>
Mon, 31 Oct 2022 12:48:24 +0000 (08:48 -0400)
This allows extensions (e.g. ReCaptcha) to provide new element types. They are editable in the GUI
via a generic template, and exensions can provide their own templates for further
configurability.

ext/afform/admin/ang/afGuiEditor.css
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.component.js
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement-menu.html [new file with mode: 0644]
ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.html [new file with mode: 0644]
ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.js [new file with mode: 0644]
ext/afform/core/ang/af/afForm.component.js

index 75fff3e3d393720a3f8a84e783dd2d4edda0f0ab..3aca35836f6514835a8a414cabd391a7a91d886c 100644 (file)
@@ -194,6 +194,7 @@ body.af-gui-dragging {
   position: relative;
   padding: 0 3px 3px;
   display: block;
+  min-height: 21px;
 }
 
 #afGuiEditor .af-gui-container {
index a61c47e1447f144337d570d72723d432b43cd8a5..06c8648d1a13a2a233c11989d1f04ad5295a8a28 100644 (file)
@@ -17,6 +17,7 @@
     controller: function($scope, $element, crmApi4, dialogService, afGui) {
       var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'),
         ctrl = this;
+      var genericElements = [];
 
       this.$onInit = function() {
         if (ctrl.node['#tag'] && ((ctrl.node['#tag'] in afGui.meta.blocks) || ctrl.join)) {
           }
           initializeBlockContainer();
         }
+        _.each(afGui.meta.elements, function(element) {
+          if (element.directive) {
+            genericElements.push(element.directive);
+          }
+        });
       };
 
       this.sortableOptions = {
         if (node['#tag'] && (node['#tag'].slice(0, 19) === 'crm-search-display-')) {
           return 'searchDisplay';
         }
+        if (node['#tag'] && _.includes(genericElements, node['#tag'])) {
+          return 'generic';
+        }
         var classes = afGui.splitClass(node['class']),
           types = ['af-container', 'af-text', 'af-button', 'af-markup'],
           type = _.intersection(types, classes);
index c98ec2297e8a4b00df8bbb7b97a5ee3d40d76ffc..9d6431cc08eeaf31ee915fc1dad90519388359b5 100644 (file)
@@ -34,6 +34,7 @@
       <af-gui-button ng-switch-when="button" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-button" ></af-gui-button>
       <af-gui-container ng-switch-when="searchFieldset" node="item" delete-this="$ctrl.removeElement(item)" style="{{ item.style }}" class="af-gui-container af-gui-fieldset af-gui-container-type-{{ item['#tag'] + ' ' + item['class'] }}" ng-class="{'af-entity-selected': isSelectedSearchFieldset(item)}" data-entity="{{ getSearchKey(item) }}" ></af-gui-container>
       <af-gui-search-display ng-switch-when="searchDisplay" node="item" class="af-gui-element"></af-gui-search-display>
+      <af-gui-generic-element ng-switch-when="generic" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-generic" ></af-gui-generic-element>
     </div>
   </div>
 </div>
diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement-menu.html
new file mode 100644 (file)
index 0000000..c7319d5
--- /dev/null
@@ -0,0 +1 @@
+<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger"><i class="crm-i fa-trash"></i> {{:: ts('Remove %1', {1: $ctrl.getTitle()}) }}</span></a></li>
diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.html
new file mode 100644 (file)
index 0000000..f4d8445
--- /dev/null
@@ -0,0 +1,14 @@
+<div class="af-gui-element">
+  <div class="af-gui-bar">
+    <div class="form-inline">
+      <span>{{:: $ctrl.getTitle() }}</span>
+      <div class="btn-group pull-right" 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') }}">
+          <span><i class="crm-i fa-gear"></i></span>
+        </button>
+        <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiGenericElement-menu.html'"></ul>
+      </div>
+    </div>
+  </div>
+</div>
+
diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiGenericElement.js
new file mode 100644 (file)
index 0000000..d6bd7ea
--- /dev/null
@@ -0,0 +1,33 @@
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+  "use strict";
+
+  // Generic element handler for element types supplied by 3rd-party extensions
+  // If they have no configuration options they can use the generic template,
+  // or they can supply their own `admin_tpl` path.
+  angular.module('afGuiEditor').component('afGuiGenericElement', {
+    template: '<div ng-include="$ctrl.getTemplate()"></div>',
+    bindings: {
+      node: '=',
+      deleteThis: '&'
+    },
+    controller: function($scope, afGui) {
+      var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'),
+        ctrl = this,
+        elementType = {};
+
+      this.$onInit = function() {
+        elementType = _.findWhere(afGui.meta.elements, {directive: ctrl.node['#tag']});
+      };
+
+      this.getTemplate = function() {
+        return elementType.admin_tpl || '~/afGuiEditor/elements/afGuiGenericElement.html';
+      };
+
+      this.getTitle = function() {
+        return elementType.title;
+      };
+    }
+  });
+
+})(angular, CRM.$, CRM._);
index 097ebbb46e0c7c5488de485909c1b22e2f06e869..e13ea5dae67f854ce02c27d84d693edb78523952 100644 (file)
@@ -9,7 +9,7 @@
     },
     controller: function($scope, $element, $timeout, crmApi4, crmStatus, $window, $location, $parse, FileUploader) {
       var schema = {},
-        data = {},
+        data = {extra: {}},
         status,
         args,
         submissionResponse,