GUI: Config fields
authorColeman Watts <coleman@civicrm.org>
Tue, 12 Nov 2019 21:58:55 +0000 (16:58 -0500)
committerCiviCRM <info@civicrm.org>
Wed, 16 Sep 2020 02:13:20 +0000 (19:13 -0700)
21 files changed:
ext/afform/core/ang/afField/afField.html
ext/afform/core/ang/afField/widgets/Number.html
ext/afform/core/ang/afField/widgets/Select.html
ext/afform/core/ang/afField/widgets/Text.html
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/button.html
ext/afform/gui/ang/afGuiEditor/field.html
ext/afform/gui/ang/afGuiEditor/main.html
ext/afform/gui/ang/afGuiEditor/text.html
ext/afform/gui/ang/afGuiEditor/widgets/CheckBox.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/Date.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/Number.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/Radio.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/RichTextEditor.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/Select.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/Text.html [new file with mode: 0644]
ext/afform/gui/ang/afGuiEditor/widgets/TextArea.html [new file with mode: 0644]
ext/afform/gui/images/number.png [new file with mode: 0644]

index 30fd1ed6a79e8e8635805b966a2efb8297b4a19c..79a0703b9301fdd2acf6fd445466e98f6f727be4 100644 (file)
@@ -1,4 +1,6 @@
 <label class="crm-af-field-label" ng-if="defn.title" for="{{ fieldId }}">
   {{ defn.title }}
 </label>
+<p class="crm-af-field-help-pre" ng-if="defn.help_pre">{{ defn.help_pre }}</p>
 <div class="crm-af-field" ng-include="'~/afField/widgets/' + defn.input_type + '.html'"></div>
+<p class="crm-af-field-help-post" ng-if="defn.help_post">{{ defn.help_post }}</p>
index d04da0659556afdf753f71e34d918c9166888aa9..2028c402c56b8a0acc22ae3cee48688a45f6efee 100644 (file)
@@ -1 +1 @@
-<input class="crm-form-text" type="number" id="{{ fieldId }}" ng-model="getData()[fieldName]" />
+<input class="crm-form-text" type="number" id="{{ fieldId }}" ng-model="getData()[fieldName]" placeholder="{{ defn.input_attrs.placeholder }}" />
index 01ae74fbe8b613ff27d3938378557439924c97e3..c0812ab4e4cabd6a568adcc7c5df10a4870d9018 100644 (file)
@@ -1 +1 @@
-<input crm-ui-select="{data: getOptions, multiple: defn.input_attrs.multiple}" id="{{ fieldId }}" ng-model="getData()[fieldName]" />
+<input crm-ui-select="{data: getOptions, multiple: defn.input_attrs.multiple, placeholder: defn.input_attrs.placeholder}" id="{{ fieldId }}" ng-model="getData()[fieldName]" />
index 6f94d24a59fbdf8f357afe74a25a5ad6dbea8127..619196a073242555578127f60d0589a736fab2f2 100644 (file)
@@ -1 +1 @@
-<input class="crm-form-text" type="text" id="{{ fieldId }}" ng-model="getData()[fieldName]" />
+<input class="crm-form-text" type="text" id="{{ fieldId }}" ng-model="getData()[fieldName]" placeholder="{{ defn.input_attrs.placeholder }}" />
index 1994e9be980ee3ee771618f322b564c83ddf8ee3..e63047898f8e12606fa3cdb5c15880860e1bb8fc 100644 (file)
@@ -185,7 +185,7 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
       ->setIncludeCustom(TRUE)
       ->setLoadOptions(TRUE)
       ->setAction('create')
-      ->setSelect(['name', 'title', 'input_type', 'input_attrs', 'options'])
+      ->setSelect(['name', 'title', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize'])
       ->addWhere('input_type', 'IS NOT NULL')
       ->execute()
       ->indexBy('name');
@@ -195,6 +195,14 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
   // FIXME: This should probably be a callback event or something to allow extensions to tweak the metadata for their entities
   $data['fields']['Contact']['contact_type']['required_data'] = TRUE;
 
+  // Scan for input types
+  // FIXME: Need a way to load this from other extensions too
+  foreach (glob(__DIR__ . '/ang/afGuiEditor/widgets/*.html') as $file) {
+    $matches = [];
+    preg_match('/([-a-z_A-Z0-9]*).html/', $file, $matches);
+    $data['widgets'][$matches[1]] = $matches[1];
+  }
+
   $mimeType = 'text/javascript';
   $content = "CRM.afformAdminData=" . json_encode($data, JSON_UNESCAPED_SLASHES) . ';';
 }
index 9a3be933292699e989e0688af00737b8c2c8337f..6ed42502d08b4473d436d9b00031291fda5da034 100644 (file)
   opacity: 0;
   transition: opacity 1s 2s;
   position:relative;
+  font-family: "Courier New", Courier, monospace;
+  font-size: 12px;
 }
 #afGuiEditor [ui-sortable] .af-gui-bar {
   cursor: move;
-  background-color: #efefef;
+  background-color: #f2f2f2;
   position: absolute;
   top: 0;
   left: 0;
 
 #afGuiEditor .af-gui-element {
   position: relative;
-  padding: 22px 3px 3px;
-  min-height: 40px;
+  padding: 0 3px 3px;
 }
 
 #afGuiEditor .af-gui-block {
   border: 2px dashed transparent;
+  padding-top: 22px;
+  min-height: 40px;
+}
+
+#afGuiEditor .af-gui-block-type-fieldset {
+  box-shadow: 0 0 5px #bbbbbb;
 }
 
 #afGuiEditor .af-gui-block:hover {
   top: -8px;
 }
 
+#afGuiEditor .af-gui-button {
+  padding-left: 15px;
+}
+
 #afGuiEditor .af-gui-button > .btn.disabled {
   cursor: default !important;
   color: white !important;
 }
+#afGuiEditor .af-gui-button > .btn.disabled .crm-editable-enabled:hover:not(:focus) {
+  border-color: #f4f4f4 !important;
+}
 
 #afGuiEditor .af-gui-button > .btn-default.disabled {
   background-color: lightgrey !important;
 }
 
+#afGuiEditor .af-gui-node-title {
+  margin-left: 5px;
+  margin-right: 20px;
+  position: relative;
+}
+
+#afGuiEditor .af-gui-field-required:after {
+  content: '*';
+  color: #cf3458;
+  position: relative;
+  left: -4px;
+}
+
+#afGuiEditor li a.af-gui-field-select-in-dropdown {
+  padding-top: 2px;
+  padding-bottom: 2px;
+  background-color: white !important;
+  cursor: default;
+}
+
+#afGuiEditor .af-gui-field-widget input[type=text].form-control {
+  color: #9a9a9a;
+}
+
+#afGuiEditor .af-gui-field-widget-number input[type=text].form-control {
+  background-image: url('../images/number.png');
+  background-repeat: no-repeat;
+  background-position: center right 6px;
+}
+
+#afGuiEditor .af-gui-field-widget input.crm-form-date {
+  width: 140px;
+  margin-right: -2px;
+}
+#afGuiEditor .af-gui-field-widget input.crm-form-time {
+  width: 80px;
+}
+
+#afGuiEditor .af-gui-text-h1 {
+  font-weight: bolder;
+  font-size: 16px;
+}
+
+#afGuiEditor .af-gui-text-h2 {
+  font-weight: bold;
+  font-size: 15px;
+}
+
+#afGuiEditor .af-gui-text-legend,
+#afGuiEditor .af-gui-text-h3 {
+  font-weight: bold;
+  font-size: 14px;
+}
+
+#afGuiEditor .af-gui-text-legend {
+  text-decoration: underline;
+}
+
+#afGuiEditor .af-gui-text-h4 {
+  font-weight: bold;
+}
+
+#afGuiEditor .af-gui-text-h5 {
+  font-weight: 600;
+}
+
+#afGuiEditor .af-gui-text-h6 {
+  font-weight: 500;
+}
+
+#afGuiEditor .af-gui-field-help {
+  font-style: italic;
+}
index 7322a8367d7759903959424fd29f201a906bac7f..542473f94bb7d584d6b74e27372e3984907d03f9 100644 (file)
         node: '=afGuiField',
         entityName: '='
       },
-      require: '^^afGuiEditor',
-      link: function($scope, element, attrs, editor) {
-        $scope.editor = editor;
+      require: ['^^afGuiEditor', '^^afGuiBlock'],
+      link: function($scope, element, attrs, ctrls) {
+        $scope.editor = ctrls[0];
+        $scope.block = ctrls[1];
       },
       controller: function($scope) {
+        var ts = $scope.ts = CRM.ts();
 
         $scope.getEntity = function() {
           return $scope.editor ? $scope.editor.getEntity($scope.entityName) : {};
         $scope.getDefn = function() {
           return $scope.editor ? $scope.editor.getField($scope.getEntity().type, $scope.node.name) : {};
         };
+
+        $scope.getOptions = function() {
+          return {
+            results: _.transform($scope.getProp('options'), function(result, val, key) {
+              result.push({id: key, text: val});
+            }, [])
+          };
+        };
+
+        $scope.getProp = function(propName) {
+          var path = propName.split('.'),
+            item = path.pop(),
+            localDefn = drillDown($scope.node.defn || {}, path);
+          if (typeof localDefn[item] !== 'undefined') {
+            return localDefn[item];
+          }
+          return drillDown($scope.getDefn(), path)[item];
+        };
+
+        $scope.toggleRequired = function() {
+          getSet('required', !getSet('required'));
+          return false;
+        };
+
+        $scope.toggleHelp = function(position) {
+          getSet('help_' + position, getSet('help_' + position) === null ? ($scope.getDefn()['help_' + position] || ts('Enter text')) : null);
+          return false;
+        };
+
+        // Getter/setter for definition props
+        $scope.getSet = function(propName) {
+          return _.wrap(propName, getSet);
+        };
+
+        // Returns a reference to a path n-levels deep within an object
+        function drillDown(parent, path) {
+          var container = parent;
+          _.each(path, function(level) {
+            container[level] = container[level] || {};
+            container = container[level];
+          });
+          return container;
+        }
+
+        // Getter/setter callback
+        function getSet(propName, val) {
+          if (arguments.length > 1) {
+            var path = propName.split('.'),
+              item = path.pop(),
+              localDefn = drillDown($scope.node, ['defn'].concat(path)),
+              fieldDefn = drillDown($scope.getDefn(), path);
+            // Set the value if different than the field defn, otherwise unset it
+            if (typeof val !== 'undefined' && val !== fieldDefn[item]) {
+              localDefn[item] = val;
+            } else {
+              delete localDefn[item];
+            }
+            return val;
+          }
+          return $scope.getProp(propName);
+        }
+
       }
     };
   });
           'btn-danger': ts('Danger')
         };
 
-        $scope.getStyle = function() {
+        // Getter/setter for ng-model
+        $scope.getSetStyle = function(val) {
+          if (arguments.length) {
+            return $scope.block.modifyClasses($scope.node, _.keys($scope.styles), ['btn', val]);
+          }
           return _.intersection(splitClass($scope.node['class']), _.keys($scope.styles))[0] || '';
         };
 
-        $scope.setStyle = function(val) {
-          $scope.block.modifyClasses($scope.node, _.keys($scope.styles), ['btn', val]);
-        };
-
         $scope.pickIcon = function() {
           editingIcon = $scope.node;
           $('#af-gui-icon-picker ~ .crm-icon-picker-button').click();
index 00fad45b9bd24524da668e74aed6b7c0929dc1e6..11c28be13b15fc29b1be5fbb181697c5f67f8446 100644 (file)
@@ -40,8 +40,8 @@
 <div ui-sortable="{handle: '.af-gui-bar', connectWith: '[ui-sortable]', cancel: 'input,textarea,button,select,option,a'}" ng-model="node['#children']" class="af-gui-layout {{ getLayout() }}">
   <div ng-repeat="item in node['#children']" ng-show="block.getNodeType(item)">
     <div ng-switch="block.getNodeType(item)">
-      <div ng-switch-when="fieldset" af-gui-block="item" class="af-gui-element 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-element af-gui-block" entity-name="entityName" />
+      <div ng-switch-when="fieldset" af-gui-block="item" class="af-gui-element af-gui-block af-gui-fieldset af-gui-block-type-{{ item['#tag'] }}" 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-element af-gui-block af-gui-block-type-{{ item['#tag'] }}" entity-name="entityName" />
       <div ng-switch-when="field" af-gui-field="item" class="af-gui-element af-gui-field" entity-name="entityName" />
       <div ng-switch-when="text" af-gui-text="item" class="af-gui-element af-gui-text" />
       <div ng-switch-when="button" af-gui-button="item" class="af-gui-element af-gui-button" />
index 391130c45a4fcb19a1ceeab7921028d1d741941b..f566c9b1874eb1156f4377bf5ea306ca40599fbc 100644 (file)
@@ -1,28 +1,23 @@
 <div class="af-gui-bar">
-  {{ node['#tag'] }}
   <div class="form-inline pull-right">
     <div class="btn-group pull-right">
       <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-block-button" data-toggle="dropdown" title="{{ ts('Configure') }}">
         <span><i class="crm-i fa-gear"></i></span>
       </button>
       <ul class="dropdown-menu">
-        <li><a href ng-click="block.removeBlock(node)"><span class="text-danger">{{ ts('Delete this button') }}</span></a></li>
-      </ul>
-    </div>
-    <div class="btn-group pull-right">
-      <button type="button" class="btn btn-xs dropdown-toggle {{ getStyle() }}-outline" data-toggle="dropdown" title="{{ ts('Style') }}">
-        <span>{{ styles[getStyle()] }}</span>
-        <i class="crm-i fa-caret-down"></i>
-      </button>
-      <ul class="dropdown-menu">
-        <li ng-repeat="(style, label) in styles" class="{{ style.replace('btn', 'bg') }}">
-          <a href ng-click="setStyle(style)">{{ label }}</a>
+        <li title="{{ ts('Button style') }}">
+          <a href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown" >
+            <select class="form-control {{ getSetStyle().replace('btn', 'text') }}" ng-model="getSetStyle" ng-model-options="{getterSetter: true}">
+              <option ng-repeat="(style, label) in styles" class="{{ style.replace('btn', 'bg') }}" value="{{ style }}">{{ ts('Color: %1', {1: label}) }}</option>
+            </select>
+          </a>
         </li>
+        <li><a href ng-click="block.removeBlock(node)"><span class="text-danger">{{ ts('Delete this button') }}</span></a></li>
       </ul>
     </div>
   </div>
 </div>
-<button type="button" class="btn {{ getStyle() }} disabled">
+<button type="button" class="btn {{ getSetStyle() }} disabled">
   <span class="crm-editable-enabled" ng-click="pickIcon()" >
     <i class="crm-i {{ node['crm-icon'] }}"></i>
   </span>
index 01cc02d74acb8440d4889d6975b746e2de83f89a..0085c9f8d4565d73ed807e29fb27e0770d6cae8f 100644 (file)
@@ -1,7 +1,51 @@
-<div class="af-gui-bar">
-  <span>{{ getEntity().label + ': ' + getDefn().title }}</span>
+<div class="af-gui-bar" title="{{ getEntity().label + ': ' + getDefn().title }}">
+  <div class="form-inline pull-right">
+
+    <div class="btn-group">
+      <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-block-button" data-toggle="dropdown" title="{{ ts('Configure') }}">
+        <span><i class="crm-i fa-gear"></i></span>
+      </button>
+      <ul class="dropdown-menu dropdown-menu-right">
+        <li title="{{ ts('Field type') }}">
+          <a href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
+            <select class="form-control" ng-model="getSet('input_type')" ng-model-options="{getterSetter: true}">
+              <option ng-repeat="(opt, label) in editor.meta.widgets" value="{{ opt }}">{{ label }}</option>
+            </select>
+          </a>
+        </li>
+        <li title="{{ ts('Require this field') }}">
+          <a href ng-click="toggleRequired(); $event.stopPropagation();">
+            <i class="crm-i" ng-class="{'fa-square-o': !getProp('required'), 'fa-check-square-o': getProp('required')}"></i>
+            {{ ts('Required') }}
+          </a>
+        </li>
+        <li title="{{ ts('Show help text above this field') }}">
+          <a href ng-click="toggleHelp('pre'); $event.stopPropagation();">
+            <i class="crm-i" ng-class="{'fa-square-o': !getProp('help_pre'), 'fa-check-square-o': getProp('help_pre')}"></i>
+            {{ ts('Pre help text') }}
+          </a>
+        </li>
+        <li title="{{ ts('Show help text below this field') }}">
+          <a href ng-click="toggleHelp('post'); $event.stopPropagation();">
+            <i class="crm-i" ng-class="{'fa-square-o': !getProp('help_post'), 'fa-check-square-o': getProp('help_post')}"></i>
+            {{ ts('Post help text') }}
+          </a>
+        </li>
+        <li title="{{ ts('Remove field from form') }}">
+          <a href ng-click="block.removeBlock(node)"><span class="text-danger">{{ ts('Delete this field') }}</span></a>
+        </li>
+      </ul>
+    </div>
+  </div>
 </div>
-<div>
-  <label af-gui-editable ng-model="node.defn.title" default-value="getDefn().title">{{ node.defn.title || getDefn().title }}</label>
-  <input class="form-control" disabled placeholder="{{ node.name }}" />
+<label ng-class="{'af-gui-field-required': getProp('required')}" class="af-gui-node-title">
+  <span af-gui-editable ng-model="node.defn.title" default-value="getDefn().title">{{ getProp('title') }}</span>
+</label>
+<div class="af-gui-field-help" ng-if="getProp('help_pre') !== null">
+  <span af-gui-editable ng-model="node.defn.help_pre" default-value="getDefn().help_pre">{{ getProp('help_pre') }}</span>
 </div>
+<div class="af-gui-field-widget af-gui-field-widget-{{ getProp('input_type').toLowerCase() }}" ng-include="'~/afGuiEditor/widgets/' + getProp('input_type') + '.html'"></div>
+<div class="af-gui-field-help" ng-if="getProp('help_post') !== null">
+  <span af-gui-editable ng-model="node.defn.help_post" default-value="getDefn().help_post">{{ getProp('help_post') }}</span>
+</div>
+
index f7ff60ff4dfef9b6406227d6abd20cbc75ef1f9f..f89bbe1283f9f0a74daa19f90afb39bc7b84eaa1 100644 (file)
@@ -1,7 +1,6 @@
 <div crm-ui-debug="afform"></div>
 <div crm-ui-debug="layout"></div>
 <div crm-ui-debug="entities"></div>
-<div crm-ui-debug="fieldList"></div>
 <div crm-ui-debug="meta"></div>
 <div id="bootstrap-theme">
   <div id="afGuiEditor">
index f770446d8fe7861b79939d89c80e2077bdd0aa25..0b65461790030f844b53343f288fc42b530fd303 100644 (file)
@@ -1,24 +1,33 @@
 <div class="af-gui-bar">
-  {{ node['#tag'] }}
   <div class="form-inline pull-right">
     <div class="btn-group pull-right">
       <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-block-button" data-toggle="dropdown" title="{{ ts('Configure') }}">
         <span><i class="crm-i fa-gear"></i></span>
       </button>
       <ul class="dropdown-menu">
-        <li><a href ng-click="block.removeBlock(node)"><span class="text-danger">{{ ts('Delete this text') }}</span></a></li>
+        <li title="{{ ts('Text style') }}">
+          <a href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
+            <select class="form-control" ng-model="node['#tag']">
+              <option ng-repeat="(opt, label) in tags" value="{{ opt }}">{{ label }}</option>
+            </select>
+          </a>
+        </li>
+        <li>
+          <a href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
+            <div class="btn-group btn-group-sm" role="group">
+              <button type="button" class="btn btn-default" ng-class="{active: opt === getAlign()}" ng-repeat="(opt, label) in alignments" ng-click="setAlign(opt)" title="{{ label }}">
+                <i class="crm-i fa-{{ opt.replace('text', 'align') }}" ></i>
+              </button>
+            </div>
+          </a>
+        </li>
+        <li>
+          <a href ng-click="block.removeBlock(node)"><span class="text-danger">{{ ts('Delete this text') }}</span></a>
+        </li>
       </ul>
     </div>
-    <select class="pull-right" ng-model="node['#tag']" title="{{ ts('Text style') }}">
-      <option ng-repeat="(opt, label) in tags" value="{{ opt }}">{{ label }}</option>
-    </select>
-    <div class="btn-group btn-group-xs pull-right" role="group">
-      <button type="button" class="btn btn-default" ng-class="{active: opt === getAlign()}" ng-repeat="(opt, label) in alignments" ng-click="setAlign(opt)" title="{{ label }}">
-        <i class="crm-i fa-{{ opt.replace('text', 'align') }}" ></i>
-      </button>
-    </div>
   </div>
 </div>
-<p af-gui-editable ng-model="node['#children'][0]['#text']" class="{{ getAlign() + ' af-gui-text-' + node['#tag'] }}" >
-  {{ node['#children'][0]['#text'] }}
+<p class="af-gui-node-title {{ getAlign() + ' af-gui-text-' + node['#tag'] }}" >
+  <span af-gui-editable ng-model="node['#children'][0]['#text']">{{ node['#children'][0]['#text'] }}</span>
 </p>
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/CheckBox.html b/ext/afform/gui/ang/afGuiEditor/widgets/CheckBox.html
new file mode 100644 (file)
index 0000000..27086e1
--- /dev/null
@@ -0,0 +1,7 @@
+<ul class="crm-checkbox-list" id="{{ fieldId }}" ng-if="getProp('options')">
+  <li ng-repeat="(key, val) in getProp('options')" >
+    <input type="checkbox" disabled />
+    <label >{{ val }}</label>
+  </li>
+</ul>
+<input type="checkbox" disabled ng-if="!getProp('options')" />
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/Date.html b/ext/afform/gui/ang/afGuiEditor/widgets/Date.html
new file mode 100644 (file)
index 0000000..aebd73d
--- /dev/null
@@ -0,0 +1,5 @@
+<div class="form-inline">
+  <input class="form-control crm-form-date" ng-model="getSet('input_attrs.placeholder')" ng-model-options="{getterSetter: true}" type="text" />
+  <span class="addon fa fa-calendar"></span>
+  <input ng-if="getProp('input_attrs.time')" class="form-control crm-form-time" ng-model="getSet('input_attrs.timePlaceholder')" ng-model-options="{getterSetter: true}" type="text" />
+</div>
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/Number.html b/ext/afform/gui/ang/afGuiEditor/widgets/Number.html
new file mode 100644 (file)
index 0000000..bfb984a
--- /dev/null
@@ -0,0 +1 @@
+<input class="form-control" ng-model="getSet('input_attrs.placeholder')" ng-model-options="{getterSetter: true}" type="text" />
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/Radio.html b/ext/afform/gui/ang/afGuiEditor/widgets/Radio.html
new file mode 100644 (file)
index 0000000..4050077
--- /dev/null
@@ -0,0 +1,4 @@
+<label ng-repeat="(key, val) in getProp('options')" >
+  <input class="crm-form-radio" type="radio" disabled value="{{ key }}" />
+  {{ val }}
+</label>
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/RichTextEditor.html b/ext/afform/gui/ang/afGuiEditor/widgets/RichTextEditor.html
new file mode 100644 (file)
index 0000000..81acfcd
--- /dev/null
@@ -0,0 +1 @@
+<textarea class="crm-form-textarea" disabled ></textarea>
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/Select.html b/ext/afform/gui/ang/afGuiEditor/widgets/Select.html
new file mode 100644 (file)
index 0000000..d096b8a
--- /dev/null
@@ -0,0 +1 @@
+<input crm-ui-select="{data: getOptions, multiple: defn.input_attrs.multiple}" />
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/Text.html b/ext/afform/gui/ang/afGuiEditor/widgets/Text.html
new file mode 100644 (file)
index 0000000..bfb984a
--- /dev/null
@@ -0,0 +1 @@
+<input class="form-control" ng-model="getSet('input_attrs.placeholder')" ng-model-options="{getterSetter: true}" type="text" />
diff --git a/ext/afform/gui/ang/afGuiEditor/widgets/TextArea.html b/ext/afform/gui/ang/afGuiEditor/widgets/TextArea.html
new file mode 100644 (file)
index 0000000..81acfcd
--- /dev/null
@@ -0,0 +1 @@
+<textarea class="crm-form-textarea" disabled ></textarea>
diff --git a/ext/afform/gui/images/number.png b/ext/afform/gui/images/number.png
new file mode 100644 (file)
index 0000000..357b11c
Binary files /dev/null and b/ext/afform/gui/images/number.png differ