Afform - move contentEditable directive into crmUI library for reusability
authorColeman Watts <coleman@civicrm.org>
Thu, 25 Feb 2021 15:50:49 +0000 (10:50 -0500)
committerColeman Watts <coleman@civicrm.org>
Thu, 25 Feb 2021 16:05:11 +0000 (11:05 -0500)
ang/crmUi.js
ext/afform/admin/ang/afGuiEditor.css
ext/afform/admin/ang/afGuiEditor/afGuiEditOptions.html
ext/afform/admin/ang/afGuiEditor/afGuiEditable.directive.js [deleted file]
ext/afform/admin/ang/afGuiEditor/afGuiEditorPalette.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiButton.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiField.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiText.html

index 87d15f7f6070d875dd053fb0f838836e5a7a4348..20ee8b1a8f10af7da538e118becc24499a43b7ee 100644 (file)
       };
     })
 
+    // Editable text using ngModel & html5 contenteditable
+    // Usage: <span crm-ui-editable ng-model="my.data">{{ my.data }}</span>
+    .directive("crmUiEditable", function() {
+      return {
+        restrict: "A",
+        require: "ngModel",
+        scope: {
+          defaultValue: '='
+        },
+        link: function(scope, element, attrs, ngModel) {
+          var ts = CRM.ts();
+
+          function read() {
+            var htmlVal = element.html();
+            if (!htmlVal) {
+              htmlVal = scope.defaultValue || '';
+              element.text(htmlVal);
+            }
+            ngModel.$setViewValue(htmlVal);
+          }
+
+          ngModel.$render = function() {
+            element.text(ngModel.$viewValue || scope.defaultValue || '');
+          };
+
+          // Special handling for enter and escape keys
+          element.on('keydown', function(e) {
+            // Enter: prevent line break and save
+            if (e.which === 13) {
+              e.preventDefault();
+              element.blur();
+            }
+            // Escape: undo
+            if (e.which === 27) {
+              element.text(ngModel.$viewValue || scope.defaultValue || '');
+              element.blur();
+            }
+          });
+
+          element.on("blur change", function() {
+            scope.$apply(read);
+          });
+
+          element.attr('contenteditable', 'true').addClass('crm-editable-enabled');
+        }
+      };
+    })
+
     .run(function($rootScope, $location) {
       /// Example: <button ng-click="goto('home')">Go home!</button>
       $rootScope.goto = function(path) {
         $location.path(path);
       };
       // useful for debugging: $rootScope.log = console.log || function() {};
-    })
-  ;
+    });
 
 })(angular, CRM.$, CRM._);
index d38dfc3ac5d915106b8b96b58b649094f4722433..330b3b4e465d23e5efbb59ff852dbde5a0e1d91b 100644 (file)
@@ -53,6 +53,7 @@
   display: inline-block;
   padding: 0 4px !important;
   border: 2px solid transparent !important;
+  min-width: 21px;
 }
 #afGuiEditor .crm-editable-enabled:hover:not(:focus) {
   border: 2px dashed grey !important;
index cb9677ac12a1b9ea7cf9a4c6a8fa3aad9b7a5fb0..1553eb90b2045af9194745a548817fada06bb0b8 100644 (file)
@@ -13,7 +13,7 @@
 </div>
 <ul ui-sortable="{connectWith: '.af-gui-edit-options-deleted', cancel: 'input,textarea,button,select,option,a,[contenteditable]'}" ng-model="options" class="af-gui-edit-options-enabled">
   <li ng-repeat="option in options">
-    <div af-gui-editable ng-model="option.label" default-value="originalLabels[option.id]" >{{ option.label }}</div>
+    <div crm-ui-editable ng-model="option.label" default-value="originalLabels[option.id]" >{{ option.label }}</div>
     <button type="button" class="btn btn-danger-outline btn-xs pull-right" ng-click="deleteOption(option, $index)" title="{{:: ts('Remove option') }}">
       <i class="crm-i fa-trash"></i>
     </button>
diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiEditable.directive.js b/ext/afform/admin/ang/afGuiEditor/afGuiEditable.directive.js
deleted file mode 100644 (file)
index c15fb09..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-// https://civicrm.org/licensing
-(function(angular, $, _) {
-  "use strict";
-
-  // Editable titles using ngModel & html5 contenteditable
-  // Cribbed from ContactLayoutEditor
-  angular.module('afGuiEditor').directive("afGuiEditable", function() {
-    return {
-      restrict: "A",
-      require: "ngModel",
-      scope: {
-        defaultValue: '='
-      },
-      link: function(scope, element, attrs, ngModel) {
-        var ts = CRM.ts();
-
-        function read() {
-          var htmlVal = element.html();
-          if (!htmlVal) {
-            htmlVal = scope.defaultValue;
-            element.text(htmlVal);
-          }
-          ngModel.$setViewValue(htmlVal);
-        }
-
-        ngModel.$render = function() {
-          element.text(ngModel.$viewValue || scope.defaultValue);
-        };
-
-        // Special handling for enter and escape keys
-        element.on('keydown', function(e) {
-          // Enter: prevent line break and save
-          if (e.which === 13) {
-            e.preventDefault();
-            element.blur();
-          }
-          // Escape: undo
-          if (e.which === 27) {
-            element.html(ngModel.$viewValue || scope.defaultValue);
-            element.blur();
-          }
-        });
-
-        element.on("blur change", function() {
-          scope.$apply(read);
-        });
-
-        element.attr('contenteditable', 'true').addClass('crm-editable-enabled');
-      }
-    };
-  });
-
-})(angular, CRM.$, CRM._);
index 1db7dd30436f48135a01dd49c4790ef6e3464c96..02d2b40686389eb49d3aa96be8be2892df3cd90e 100644 (file)
@@ -7,7 +7,7 @@
       </li>
       <li role="presentation" ng-repeat="entity in entities" ng-class="{active: selectedEntityName === entity.name}">
         <a href ng-click="editor.selectEntity(entity.name)">
-          <span ng-if="!entity.loading && editor.allowEntityConfig" af-gui-editable ng-model="entity.label">{{ entity.label }}</span>
+          <span ng-if="!entity.loading && editor.allowEntityConfig" crm-ui-editable ng-model="entity.label">{{ entity.label }}</span>
           <span ng-if="!entity.loading && !editor.allowEntityConfig">{{ entity.label }}</span>
           <i ng-if="entity.loading" class="crm-i fa-spin fa-spinner"></i>
         </a>
index 21c168805c0ca061f3d52051c07683ca42b1727a..b2234776825a91c5d30f5b0bfc8e08c6e4facd23 100644 (file)
@@ -12,7 +12,7 @@
   <span class="crm-editable-enabled" ng-click="pickIcon()" >
     <i class="crm-i {{ $ctrl.node['crm-icon'] }}"></i>
   </span>
-  <span af-gui-editable ng-model="$ctrl.node['#children'][0]['#text']" >
+  <span crm-ui-editable ng-model="$ctrl.node['#children'][0]['#text']" >
     {{ $ctrl.node['#children'][0]['#text'] }}
   </span>
 </button>
index e191239c0f339152211187ab4411f559f10fe3a4..d205755ee55915a39e326c83c4db2f004eee2918 100644 (file)
@@ -34,6 +34,6 @@
     <span class="crm-editable-enabled" ng-click="pickAddIcon()" >
       <i class="crm-i {{ $ctrl.node['add-icon'] || 'fa-plus' }}"></i>
     </span>
-    <span af-gui-editable ng-model="$ctrl.node['af-repeat']">{{ $ctrl.node['af-repeat'] }}</span>
+    <span crm-ui-editable ng-model="$ctrl.node['af-repeat']">{{ $ctrl.node['af-repeat'] }}</span>
   </button>
 </div>
index a22ece82de0864b26e7a335793d7aabd314bb0f8..2d1c086a4083acf65173b2c35045b8e271787c98 100644 (file)
     </div>
   </div>
   <label ng-style="{visibility: $ctrl.node.defn.label === false ? 'hidden' : 'visible'}" ng-class="{'af-gui-field-required': getProp('required')}" class="af-gui-node-title">
-    <span af-gui-editable ng-model="getSet('label')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().label">{{ getProp('label') }}</span>
+    <span crm-ui-editable ng-model="getSet('label')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().label">{{ getProp('label') }}</span>
   </label>
   <div class="af-gui-field-help" ng-if="propIsset('help_pre')">
-    <span af-gui-editable ng-model="getSet('help_pre')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().help_pre">{{ getProp('help_pre') }}</span>
+    <span crm-ui-editable ng-model="getSet('help_pre')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().help_pre">{{ getProp('help_pre') }}</span>
   </div>
   <div class="af-gui-field-input af-gui-field-input-type-{{ getProp('input_type').toLowerCase() }}" ng-include="'~/afGuiEditor/inputType/' + getProp('input_type') + '.html'"></div>
   <div class="af-gui-field-help" ng-if="propIsset('help_post')">
-    <span af-gui-editable ng-model="getSet('help_post')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().help_post">{{ getProp('help_post') }}</span>
+    <span crm-ui-editable ng-model="getSet('help_post')" ng-model-options="{getterSetter: true}" default-value="$ctrl.getDefn().help_post">{{ getProp('help_post') }}</span>
   </div>
 </div>
index e7b5d8cd2731c7ad7d245556ab2301e1e1a69046..411b3e3901d2c6756b2368a97549fc5c92f087ea 100644 (file)
@@ -9,5 +9,5 @@
   </div>
 </div>
 <p class="af-gui-node-title {{ $ctrl.node['class'] + ' af-gui-text-' + $ctrl.node['#tag'] }}" >
-  <span af-gui-editable ng-model="$ctrl.node['#children'][0]['#text']">{{ $ctrl.node['#children'][0]['#text'] }}</span>
+  <span crm-ui-editable ng-model="$ctrl.node['#children'][0]['#text']">{{ $ctrl.node['#children'][0]['#text'] }}</span>
 </p>