Afform Gui - Improve speed & accuracy of rendering fields/blocks palette
authorColeman Watts <coleman@civicrm.org>
Thu, 4 Feb 2021 17:10:38 +0000 (12:10 -0500)
committerColeman Watts <coleman@civicrm.org>
Thu, 4 Feb 2021 17:19:39 +0000 (12:19 -0500)
Ensure the palette gets refreshed when saving a new block, and use ng-change instead of $watch

ext/afform/admin/ang/afGuiEditor/afGuiEntity.component.js
ext/afform/admin/ang/afGuiEditor/afGuiEntity.html
ext/afform/admin/ang/afGuiEditor/afGuiSearch.component.js
ext/afform/admin/ang/afGuiEditor/afGuiSearch.html

index 6c231a5914539554d54e8a82e8a95b915687b42b..b4f67341bbcb0dbe8881442b290f0b17bdd46149 100644 (file)
         delete entity.data[fieldName];
       };
 
-      function buildPaletteLists() {
+      this.buildPaletteLists = function() {
         var search = $scope.controls.fieldSearch ? $scope.controls.fieldSearch.toLowerCase() : null;
         buildFieldList(search);
         buildBlockList(search);
         buildElementList(search);
-      }
+      };
 
       function buildFieldList(search) {
         $scope.fieldList.length = 0;
         });
       }
 
-      $scope.clearSearch = function() {
-        $scope.controls.fieldSearch = '';
-      };
-
       // This gets called from jquery-ui so we have to manually apply changes to scope
       $scope.buildPaletteLists = function() {
         $timeout(function() {
           $scope.$apply(function() {
-            buildPaletteLists();
+            ctrl.buildPaletteLists();
           });
         });
       };
         return check(ctrl.editor.layout['#children'], {'#tag': 'af-field', name: fieldName});
       };
 
+      // Checks if fields in a block are already in use on the form.
+      // Note that if a block contains no fields it can be used repeatedly, so this will always return false for those.
       $scope.blockInUse = function(block) {
         if (block['af-join']) {
           return check(ctrl.editor.layout['#children'], {'af-join': block['af-join']});
         return found.match;
       }
 
-      $scope.$watch('controls.addValue', function(fieldName) {
-        if (fieldName) {
-          if (!ctrl.entity.data) {
-            ctrl.entity.data = {};
-          }
-          ctrl.entity.data[fieldName] = '';
-          $scope.controls.addValue = '';
-        }
-      });
+      this.$onInit = function() {
+        // When a new block is saved, update the list
+        this.meta = afGui.meta;
+        $scope.$watchCollection('$ctrl.meta.blocks', function() {
+          $scope.controls.fieldSearch = '';
+          ctrl.buildPaletteLists();
+        });
 
-      $scope.$watch('controls.fieldSearch', buildPaletteLists);
+        $scope.$watch('controls.addValue', function(fieldName) {
+          if (fieldName) {
+            if (!ctrl.entity.data) {
+              ctrl.entity.data = {};
+            }
+            ctrl.entity.data[fieldName] = '';
+            $scope.controls.addValue = '';
+          }
+        });
+      };
     }
   });
 
index ee017c9ee1cb1e1c46b6611d93a00643c5507ed0..adbfaf60b9c204e89c4b0399a6787cbdefc6678e 100644 (file)
   <fieldset class="af-gui-entity-palette">
     <legend class="form-inline">
       {{:: ts('Add:') }}
-      <input ng-model="controls.fieldSearch" class="form-control" type="search" placeholder="&#xf002" title="{{:: ts('Search fields') }}" />
+      <input ng-model="controls.fieldSearch" ng-change="$ctrl.buildPaletteLists()" class="form-control" type="search" placeholder="&#xf002" title="{{:: ts('Search fields') }}" />
     </legend>
     <div class="af-gui-entity-palette-select-list">
       <div ng-if="elementList.length">
         <label>{{:: ts('Elements') }}</label>
         <div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="elementList">
           <div ng-repeat="element in elementList" >
-            {{ elementTitles[$index] }}
+            {{:: elementTitles[$index] }}
           </div>
         </div>
       </div>
@@ -32,7 +32,7 @@
         <label>{{:: ts('Blocks') }}</label>
         <div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[data-entity=' + $ctrl.entity.name + '] &gt; [ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="blockList">
           <div ng-repeat="block in blockList" ng-class="{disabled: blockInUse(block)}">
-            {{ blockTitles[$index] }}
+            {{:: blockTitles[$index] }}
           </div>
         </div>
       </div>
@@ -41,7 +41,7 @@
           <label>{{ fieldGroup.label }}</label>
           <div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[data-entity=' + fieldGroup.entityName + '] &gt; [ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="fieldGroup.fields">
             <div ng-repeat="field in fieldGroup.fields" ng-class="{disabled: fieldInUse(field.name)}">
-              {{ getField(fieldGroup.entityType, field.name).label }}
+              {{:: getField(fieldGroup.entityType, field.name).label }}
             </div>
           </div>
         </div>
@@ -56,5 +56,5 @@
 
 <fieldset ng-if="!$ctrl.entity.loading && $ctrl.editor.allowEntityConfig">
   <legend>{{:: ts('Options') }}</legend>
-  <div ng-include="'~/afGuiEditor/entityConfig/' + $ctrl.entity.type + '.html'"></div>
+  <div ng-include="::'~/afGuiEditor/entityConfig/' + $ctrl.entity.type + '.html'"></div>
 </fieldset>
index e8d8be05818d39a2a0be1eda7b41889069752bfa..c195a5e321f546a478231e552d3cca52c083e1e6 100644 (file)
 
       $scope.getField = afGui.getField;
 
-      function buildPaletteLists() {
+      this.buildPaletteLists = function() {
         var search = $scope.controls.fieldSearch ? $scope.controls.fieldSearch.toLowerCase() : null;
         buildFieldList(search);
         buildBlockList(search);
         buildElementList(search);
-      }
+      };
 
       function buildBlockList(search) {
         $scope.blockList.length = 0;
         });
       }
 
-      $scope.clearSearch = function() {
-        $scope.controls.fieldSearch = '';
-      };
-
       // This gets called from jquery-ui so we have to manually apply changes to scope
       $scope.buildPaletteLists = function() {
         $timeout(function() {
           $scope.$apply(function() {
-            buildPaletteLists();
+            ctrl.buildPaletteLists();
           });
         });
       };
         return check(ctrl.editor.layout['#children'], {'#tag': 'af-field', name: fieldName});
       };
 
+      // Checks if fields in a block are already in use on the form.
+      // Note that if a block contains no fields it can be used repeatedly, so this will always return false for those.
+      $scope.blockInUse = function(block) {
+        if (block['af-join']) {
+          return check(ctrl.editor.layout['#children'], {'af-join': block['af-join']});
+        }
+        var fieldsInBlock = _.pluck(afGui.findRecursive(afGui.meta.blocks[block['#tag']].layout, {'#tag': 'af-field'}), 'name');
+        return check(ctrl.editor.layout['#children'], function(item) {
+          return item['#tag'] === 'af-field' && _.includes(fieldsInBlock, item.name);
+        });
+      };
+
       // Check for a matching item for this entity
       // Recursively checks the form layout, including block directives
       function check(group, criteria, found) {
         return found.match;
       }
 
-      $scope.$watch('controls.fieldSearch', buildPaletteLists);
+      this.$onInit = function() {
+        // When a new block is saved, update the list
+        this.meta = afGui.meta;
+        $scope.$watchCollection('$ctrl.meta.blocks', function() {
+          $scope.controls.fieldSearch = '';
+          ctrl.buildPaletteLists();
+        });
+      };
     }
   });
 
index b61deac99adf9b2b9b6b7188924e94887cfd1a3a..1d9def8349cac6fa92e9a8e271955d2945164154 100644 (file)
@@ -2,7 +2,7 @@
   <fieldset class="af-gui-entity-palette">
     <legend class="form-inline">
       {{:: ts('Add:') }}
-      <input ng-model="controls.fieldSearch" class="form-control" type="search" placeholder="&#xf002" title="{{:: ts('Search fields') }}" />
+      <input ng-model="controls.fieldSearch" ng-change="$ctrl.buildPaletteLists()" class="form-control" type="search" placeholder="&#xf002" title="{{:: ts('Search fields') }}" />
     </legend>
     <div class="af-gui-entity-palette-select-list">
       <div ng-if="elementList.length">
@@ -17,7 +17,7 @@
         <label>{{:: ts('Blocks') }}</label>
         <div ui-sortable="{update: buildPaletteLists, items: '&gt; div:not(.disabled)', connectWith: '[ui-sortable]', placeholder: 'af-gui-dropzone'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="blockList">
           <div ng-repeat="block in blockList" ng-class="{disabled: blockInUse(block)}">
-            {{ blockTitles[$index] }}
+            {{:: blockTitles[$index] }}
           </div>
         </div>
       </div>