Fields drag n drop
authorColeman Watts <coleman@civicrm.org>
Fri, 1 Nov 2019 19:15:59 +0000 (15:15 -0400)
committerCiviCRM <info@civicrm.org>
Wed, 16 Sep 2020 02:13:19 +0000 (19:13 -0700)
ext/afform/core/afform.php
ext/afform/gui/afform_gui.php
ext/afform/gui/ang/afGuiEditor.css
ext/afform/gui/ang/afGuiEditor.js
ext/afform/gui/ang/afGuiEditor/block.html
ext/afform/gui/ang/afGuiEditor/config-entity.html
ext/afform/gui/ang/afGuiEditor/main.html

index e9decfee32f3ee2159f575748a9dec6083964a64..a54dd7fa7f2eabe8052d54b0f31671f8f909762c 100644 (file)
@@ -316,6 +316,7 @@ function afform_civicrm_alterAngular($angular) {
         }
         $entityType = $entities[$entityName]['type'];
         $getFields = civicrm_api4($entityType, 'getFields', [
+          'action' => 'create',
           'where' => [['name', '=', $fieldName]],
           'select' => ['title', 'input_type', 'input_attrs', 'options'],
           'loadOptions' => TRUE,
index f72cb7f014cb9c0ce35ac8769c6c0592c85988b6..5aef321d4002a4cd6b6984830f1b9e8301953e1c 100644 (file)
@@ -186,7 +186,8 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
     ->setCheckPermissions(FALSE)
     ->setIncludeCustom(TRUE)
     ->setLoadOptions(TRUE)
-    ->setAction('Create')
+    ->setAction('create')
+    ->setSelect(['name', 'title', 'input_type', 'input_attrs', 'options'])
     ->execute();
 
   $contactSettings = [
index 424e5aad54ee3fcd706bd5f4b516dad73afef13f..95df8383fdf403aec076094236410c6523773c07 100644 (file)
   width: 100%;
   padding-left:15px;
 }
-/* grip handle */
-#afGuiEditor .af-gui-bar:before {
-  background-size: cover;
-  background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjODg4Ij48L3JlY3Q+Cjwvc3ZnPg==");
-  width: 10px;
-  height: 15px;
-  content: ' ';
-  display: block;
-  position: absolute;
-  left: 4px;
-  top: 5px;
-}
 
 #afGuiEditor-canvas:hover .af-gui-bar {
   visibility: visible;
 #afGuiEditor .af-gui-block:hover {
   border: 2px dashed #757575;
 }
+
+#afGuiEditor [ui-sortable] {
+  min-height: 25px;
+}
+
+#afGuiEditor .af-gui-field-select-list {
+  max-height: 280px;
+  overflow-y: auto;
+}
+
+#afGuiEditor .af-gui-field-select-list > div {
+  cursor: move;
+  padding-left:15px;
+  position: relative;
+}
+#afGuiEditor .af-gui-field-select-list > div.disabled {
+  cursor: auto;
+}
+#afGuiEditor .af-gui-field-select-list > div:not(.disabled):hover {
+  background-color: #efefef;
+}
+/* grip handle */
+#afGuiEditor .af-gui-bar:before,
+#afGuiEditor .af-gui-field-select-list > div:not(.disabled):hover:before {
+  background-size: cover;
+  background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjODg4Ij48L3JlY3Q+Cjwvc3ZnPg==");
+  width: 10px;
+  height: 15px;
+  content: ' ';
+  display: block;
+  position: absolute;
+  left: 4px;
+  top: 5px;
+}
index 45ef072bf635743ff7751dea1f952580ffe9c035..95692554266f56f5a2ed9b5e53feea27c78d8625 100644 (file)
@@ -1,7 +1,7 @@
 (function(angular, $, _) {
   angular.module('afGuiEditor', CRM.angRequires('afGuiEditor'));
 
-  angular.module('afGuiEditor').directive('afGuiEditor', function(crmApi4, $parse) {
+  angular.module('afGuiEditor').directive('afGuiEditor', function(crmApi4, $parse, $timeout) {
     return {
       restrict: 'A',
       templateUrl: '~/afGuiEditor/main.html',
@@ -14,6 +14,7 @@
         $scope.selectedEntity = null;
         $scope.meta = CRM.afformAdminData;
         $scope.controls = {};
+        $scope.fieldList = {};
         $scope.editor = this;
         var newForm = {
           title: ts('Untitled Form'),
@@ -49,7 +50,8 @@
           $scope.layout = getTags($scope.afform.layout, 'af-form')[0];
           evaluate($scope.layout['#children']);
           $scope.entities = getTags($scope.layout['#children'], 'af-entity', 'name');
-          $scope.fields = getAllFields($scope.layout['#children']);
+          expandFields($scope.layout['#children']);
+          _.each(_.keys($scope.entities), buildFieldList);
         }
 
         this.addEntity = function(entityType) {
           return $scope.selectedEntity;
         };
 
+        $scope.rebuildFieldList = function() {
+          $timeout(function() {
+            $scope.$apply(function() {
+              buildFieldList($scope.selectedEntity);
+            });
+          });
+        };
+
+        function buildFieldList(entityName) {
+          $scope.fieldList[entityName] = $scope.fieldList[entityName] || [];
+          $scope.fieldList[entityName].length = 0;
+          _.each($scope.meta.fields[$scope.entities[entityName].type], function(field) {
+            $scope.fieldList[entityName].push({
+              "#tag": "af-field",
+              name: field.name,
+              defn: _.cloneDeep(_.pick(field, ['title', 'input_type', 'input_attrs']))
+            });
+          });
+        }
+
         $scope.valuesFields = function() {
           var fields = _.transform($scope.meta.fields[$scope.entities[$scope.selectedEntity].type], function(fields, field) {
-            var data = $scope.entities[$scope.selectedEntity].data || {};
-            fields.push({id: field.name, text: field.title, disabled: field.name in data});
+            fields.push({id: field.name, text: field.title, disabled: $scope.fieldInUse($scope.selectedEntity, field.name)});
           }, []);
           return {results: fields};
         };
           }
         });
 
+        // Checks if a field is on the form or set as a value
+        $scope.fieldInUse = function(entityName, fieldName) {
+          var data = $scope.entities[entityName].data || {},
+            found = false;
+          if (fieldName in data) {
+            return true;
+          }
+          return check($scope.layout['#children']);
+          function check(group) {
+            _.each(group, function(item) {
+              if (found) {
+                return false;
+              }
+              if (_.isPlainObject(item)) {
+                if ((!item['af-fieldset'] || (item['af-fieldset'] === entityName)) && item['#children']) {
+                  check(item['#children']);
+                }
+                if (item['#tag'] === 'af-field' && item.name === fieldName) {
+                  found = true;
+                }
+              }
+            });
+            return found;
+          }
+        };
+
+        // Parse strings of javascript that php couldn't interpret
         function evaluate(collection) {
           _.each(collection, function(item) {
             if (_.isPlainObject(item)) {
           });
         }
 
+        function expandFields(collection, entityType) {
+          _.each(collection, function (item) {
+            if (_.isPlainObject(item)) {
+              if (item['af-fieldset']) {
+                expandFields(item['#children'], $scope.editor.getEntity(item['af-fieldset']).type);
+              }
+              else if (item['#tag'] === 'af-field') {
+                item.defn = item.defn || {};
+                _.defaults(item.defn, _.cloneDeep(_.pick($scope.editor.getField(entityType, item.name), ['title', 'input_type', 'input_attrs'])));
+              } else {
+                expandFields(item['#children'], entityType);
+              }
+            }
+          });
+        }
+
       }
     };
   });
     return indexBy ? _.indexBy(items, indexBy) : items;
   }
 
-  // Lists fields by entity. Note that fields for an entity can be spread across several fieldsets.
-  function getAllFields(layout) {
-    var allFields = {};
-    _.each(getTags(layout, 'af-fieldset'), function(fieldset) {
-      if (!allFields[fieldset.model]) {
-        allFields[fieldset.model] = {};
-      }
-      _.assign(allFields[fieldset.model], getTags(fieldset['#children'], 'af-field', 'name'));
-    });
-    return allFields;
-  }
-
   // Turns a space-separated list (e.g. css classes) into an array
   function splitClass(str) {
     if (_.isArray(str)) {
index 6aca722e1dce8674dfa64b4ecd83a702c5556979..e45853423d555dfe3fc422bee1ce491c7eff6a48 100644 (file)
@@ -5,10 +5,10 @@
     <option ng-repeat="(opt, label) in tags" value="{{ opt }}">{{ label }}</option>
   </select>
 </div>
-<div ui-sortable="{handle: '.af-gui-bar'}" ng-model="node['#children']">
+<div ui-sortable="{handle: '.af-gui-bar', connectWith: '[ui-sortable]'}" ng-model="node['#children']">
   <div ng-repeat="item in node['#children']">
     <div ng-switch="block.getNodeType(item)">
-      <div ng-switch-when="fieldset" af-gui-block="item" class="af-gui-block af-gui-fieldset" ng-class="{'af-entity-selected': isSelectedFieldset(item['af-fieldset'])}" entity-name="item['af-fieldset']" />
+      <div ng-switch-when="fieldset" af-gui-block="item" class="af-gui-block af-gui-fieldset" ng-class="{'af-entity-selected': isSelectedFieldset(item['af-fieldset'])}" entity-name="item['af-fieldset']" data-entity="{{ item['af-fieldset'] }}" />
       <div ng-switch-when="block" af-gui-block="item" class="af-gui-block" entity-name="entityName" />
       <div ng-switch-when="field" af-gui-field="item" class="af-gui-field" entity-name="entityName" />
       <div ng-switch-when="text" af-gui-text="item" class="af-gui-text" />
index 495319c9075524fc4b374e4f0c5b322ca63ab9bc..cf32fadf031173e9724eae4d0033d6e3cb44cf4d 100644 (file)
 
   <fieldset>
     <legend>{{ ts('Fields') }}</legend>
-    <ul class="crm-checkbox-list">
-      <li ng-repeat="(key, spec) in meta.fields[entity.type]" >
-        <input type="checkbox" id="field-{{ selectedEntity + '-' + key }}" />
-        <label for="field-{{ selectedEntity + '-' + key }}">{{ spec.title }}</label>
-      </li>
-    </ul>
+    <div class="af-gui-field-select-list" ui-sortable="{update: rebuildFieldList, items: '> div:not(.disabled)', connectWith: '[data-entity=' + selectedEntity + '] [ui-sortable]'}" ng-model="fieldList[selectedEntity]">
+      <div ng-repeat="field in fieldList[selectedEntity]" ng-class="{disabled: fieldInUse(selectedEntity, field.name)}">
+        {{ field.defn.title }}
+      </div>
+    </div>
   </fieldset>
 
   <fieldset>
index bf677ca448e99482b4b641afe4711d19fd938702..bd8b471e1bb34936dd5544cf6e7da4ed04c23775 100644 (file)
@@ -1,7 +1,7 @@
 <div crm-ui-debug="afform"></div>
 <div crm-ui-debug="layout"></div>
 <div crm-ui-debug="entities"></div>
-<div crm-ui-debug="fields"></div>
+<div crm-ui-debug="fieldList"></div>
 <div crm-ui-debug="meta"></div>
 <div id="bootstrap-theme">
   <div id="afGuiEditor">