<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>
-<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 }}" />
-<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]" />
-<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 }}" />
->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');
// 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) . ';';
}
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;
+}
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();
<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" />
<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>
-<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>
+
<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">
<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>
--- /dev/null
+<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')" />
--- /dev/null
+<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>
--- /dev/null
+<input class="form-control" ng-model="getSet('input_attrs.placeholder')" ng-model-options="{getterSetter: true}" type="text" />
--- /dev/null
+<label ng-repeat="(key, val) in getProp('options')" >
+ <input class="crm-form-radio" type="radio" disabled value="{{ key }}" />
+ {{ val }}
+</label>
--- /dev/null
+<textarea class="crm-form-textarea" disabled ></textarea>
--- /dev/null
+<input crm-ui-select="{data: getOptions, multiple: defn.input_attrs.multiple}" />
--- /dev/null
+<input class="form-control" ng-model="getSet('input_attrs.placeholder')" ng-model-options="{getterSetter: true}" type="text" />
--- /dev/null
+<textarea class="crm-form-textarea" disabled ></textarea>