'js' => [
'ang/afGuiEditor.js',
'ang/afGuiEditor/*.js',
+ 'ang/afGuiEditor/*/*.js',
],
'css' => ['ang/afGuiEditor.css'],
'partials' => ['ang/afGuiEditor'],
#afGuiEditor .af-gui-element {
position: relative;
padding: 0 3px 3px;
+ display: block;
}
#afGuiEditor .af-gui-container {
position: relative;
padding: 22px 3px 3px;
min-height: 40px;
+ display: block;
+}
+
+#afGuiEditor af-gui-markup,
+#afGuiEditor af-gui-field {
+ display: block;
}
#afGuiEditor .af-gui-container-type-fieldset {
});
});
- angular.module('afGuiEditor').directive('afGuiContainer', function(crmApi4, dialogService, afAdmin) {
- return {
- restrict: 'A',
- templateUrl: '~/afGuiEditor/container.html',
- scope: {
- node: '=afGuiContainer',
- join: '=',
- entityName: '='
- },
- require: ['^^afGuiEditor', '?^^afGuiContainer'],
- link: function($scope, element, attrs, ctrls) {
- var ts = $scope.ts = CRM.ts();
- $scope.editor = ctrls[0];
- $scope.parentContainer = ctrls[1];
-
- $scope.isSelectedFieldset = function(entityName) {
- return entityName === $scope.editor.getSelectedEntityName();
- };
-
- $scope.selectEntity = function() {
- if ($scope.node['af-fieldset']) {
- $scope.editor.selectEntity($scope.node['af-fieldset']);
- }
- };
-
- $scope.tags = {
- div: ts('Container'),
- fieldset: ts('Fieldset')
- };
-
- // Block settings
- var block = {};
- $scope.block = null;
-
- $scope.getSetChildren = function(val) {
- var collection = block.layout || ($scope.node && $scope.node['#children']);
- return arguments.length ? (collection = val) : collection;
- };
-
- $scope.isRepeatable = function() {
- return $scope.node['af-fieldset'] || (block.directive && $scope.editor.meta.blocks[block.directive].repeat) || $scope.join;
- };
-
- $scope.toggleRepeat = function() {
- if ('af-repeat' in $scope.node) {
- delete $scope.node.max;
- delete $scope.node.min;
- delete $scope.node['af-repeat'];
- delete $scope.node['add-icon'];
- } else {
- $scope.node.min = '1';
- $scope.node['af-repeat'] = ts('Add');
- }
- };
-
- $scope.getSetMin = function(val) {
- if (arguments.length) {
- if ($scope.node.max && val > parseInt($scope.node.max, 10)) {
- $scope.node.max = '' + val;
- }
- if (!val) {
- delete $scope.node.min;
- }
- else {
- $scope.node.min = '' + val;
- }
- }
- return $scope.node.min ? parseInt($scope.node.min, 10) : null;
- };
-
- $scope.getSetMax = function(val) {
- if (arguments.length) {
- if ($scope.node.min && val && val < parseInt($scope.node.min, 10)) {
- $scope.node.min = '' + val;
- }
- if (typeof val !== 'number') {
- delete $scope.node.max;
- }
- else {
- $scope.node.max = '' + val;
- }
- }
- return $scope.node.max ? parseInt($scope.node.max, 10) : null;
- };
-
- $scope.pickAddIcon = function() {
- afAdmin.pickIcon().then(function(val) {
- $scope.node['add-icon'] = val;
- });
- };
-
- function getBlockNode() {
- return !$scope.join ? $scope.node : ($scope.node['#children'] && $scope.node['#children'].length === 1 ? $scope.node['#children'][0] : null);
- }
-
- function setBlockDirective(directive) {
- if ($scope.join) {
- $scope.node['#children'] = [{'#tag': directive}];
- } else {
- delete $scope.node['#children'];
- delete $scope.node['class'];
- $scope.node['#tag'] = directive;
- }
- }
-
- function overrideBlockContents(layout) {
- $scope.node['#children'] = layout || [];
- if (!$scope.join) {
- $scope.node['#tag'] = 'div';
- $scope.node['class'] = 'af-container';
- }
- block.layout = block.directive = null;
- }
-
- $scope.layouts = {
- 'af-layout-rows': ts('Contents display as rows'),
- 'af-layout-cols': ts('Contents are evenly-spaced columns'),
- 'af-layout-inline': ts('Contents are arranged inline')
- };
-
- $scope.getLayout = function() {
- if (!$scope.node) {
- return '';
- }
- return _.intersection(afAdmin.splitClass($scope.node['class']), _.keys($scope.layouts))[0] || 'af-layout-rows';
- };
-
- $scope.setLayout = function(val) {
- var classes = ['af-container'];
- if (val !== 'af-layout-rows') {
- classes.push(val);
- }
- afAdmin.modifyClasses($scope.node, _.keys($scope.layouts), classes);
- };
-
- $scope.selectBlockDirective = function() {
- if (block.directive) {
- block.layout = _.cloneDeep($scope.editor.meta.blocks[block.directive].layout);
- block.original = block.directive;
- setBlockDirective(block.directive);
- }
- else {
- overrideBlockContents(block.layout);
- }
- };
-
- if (($scope.node['#tag'] in $scope.editor.meta.blocks) || $scope.join) {
- initializeBlockContainer();
- }
-
- function initializeBlockContainer() {
-
- // Cancel the below $watch expressions if already set
- _.each(block.listeners, function(deregister) {
- deregister();
- });
-
- block = $scope.block = {
- directive: null,
- layout: null,
- original: null,
- options: [],
- listeners: []
- };
-
- _.each($scope.editor.meta.blocks, function(blockInfo, directive) {
- if (directive === $scope.node['#tag'] || blockInfo.join === $scope.container.getFieldEntityType()) {
- block.options.push({
- id: directive,
- text: blockInfo.title
- });
- }
- });
-
- if (getBlockNode() && getBlockNode()['#tag'] in $scope.editor.meta.blocks) {
- block.directive = block.original = getBlockNode()['#tag'];
- block.layout = _.cloneDeep($scope.editor.meta.blocks[block.directive].layout);
- }
-
- block.listeners.push($scope.$watch('block.layout', function (layout, oldVal) {
- if (block.directive && layout && layout !== oldVal && !angular.equals(layout, $scope.editor.meta.blocks[block.directive].layout)) {
- overrideBlockContents(block.layout);
- }
- }, true));
- }
-
- $scope.saveBlock = function() {
- var options = CRM.utils.adjustDialogDefaults({
- width: '500px',
- height: '300px',
- autoOpen: false,
- title: ts('Save block')
- });
- var model = {
- title: '',
- name: null,
- layout: $scope.node['#children']
- };
- if ($scope.join) {
- model.join = $scope.join;
- }
- if ($scope.block && $scope.block.original) {
- model.title = $scope.editor.meta.blocks[$scope.block.original].title;
- model.name = $scope.editor.meta.blocks[$scope.block.original].name;
- model.block = $scope.editor.meta.blocks[$scope.block.original].block;
- }
- else {
- model.block = $scope.container.getFieldEntityType() || '*';
- }
- dialogService.open('saveBlockDialog', '~/afGuiEditor/saveBlock.html', model, options)
- .then(function(block) {
- $scope.editor.meta.blocks[block.directive_name] = block;
- setBlockDirective(block.directive_name);
- initializeBlockContainer();
- });
- };
-
- },
- controller: function($scope, afAdmin) {
- var container = $scope.container = this;
- this.node = $scope.node;
-
- this.getNodeType = function(node) {
- if (!node) {
- return null;
- }
- if (node['#tag'] === 'af-field') {
- return 'field';
- }
- if (node['af-fieldset']) {
- return 'fieldset';
- }
- if (node['af-join']) {
- return 'join';
- }
- if (node['#tag'] && node['#tag'] in $scope.editor.meta.blocks) {
- return 'container';
- }
- var classes = afAdmin.splitClass(node['class']),
- types = ['af-container', 'af-text', 'af-button', 'af-markup'],
- type = _.intersection(types, classes);
- return type.length ? type[0].replace('af-', '') : null;
- };
-
- this.removeElement = function(element) {
- afAdmin.removeRecursive($scope.getSetChildren(), {$$hashKey: element.$$hashKey});
- };
-
- this.getEntityName = function() {
- return $scope.entityName.split('-join-')[0];
- };
-
- // Returns the primary entity type for this container e.g. "Contact"
- this.getMainEntityType = function() {
- return $scope.editor && $scope.editor.getEntity(container.getEntityName()).type;
- };
-
- // Returns the entity type for fields within this conainer (join entity type if this is a join, else the primary entity type)
- this.getFieldEntityType = function() {
- var joinType = $scope.entityName.split('-join-');
- return joinType[1] || ($scope.editor && $scope.editor.getEntity(joinType[0]).type);
- };
-
- }
- };
- });
-
angular.module('afGuiEditor').controller('afGuiSaveBlock', function($scope, crmApi4, dialogService) {
var ts = $scope.ts = CRM.ts(),
model = $scope.model,
};
});
- angular.module('afGuiEditor').directive('afGuiField', function() {
- return {
- restrict: 'A',
- templateUrl: '~/afGuiEditor/field.html',
- scope: {
- node: '=afGuiField'
- },
- require: ['^^afGuiEditor', '^^afGuiContainer'],
- link: function($scope, element, attrs, ctrls) {
- $scope.editor = ctrls[0];
- $scope.container = ctrls[1];
- },
- controller: function($scope, afAdmin) {
- var ts = $scope.ts = CRM.ts();
- $scope.editingOptions = false;
- var yesNo = [
- {key: '1', label: ts('Yes')},
- {key: '0', label: ts('No')}
- ];
-
- $scope.getEntity = function() {
- return $scope.editor ? $scope.editor.getEntity($scope.container.getEntityName()) : {};
- };
-
- $scope.getDefn = this.getDefn = function() {
- return $scope.editor ? afAdmin.getField($scope.container.getFieldEntityType(), $scope.node.name) : {};
- };
-
- $scope.hasOptions = function() {
- var inputType = $scope.getProp('input_type');
- return _.contains(['CheckBox', 'Radio', 'Select'], inputType) && !(inputType === 'CheckBox' && !$scope.getDefn().options);
- };
-
- $scope.getOptions = this.getOptions = function() {
- if ($scope.node.defn && $scope.node.defn.options) {
- return $scope.node.defn.options;
- }
- return $scope.getDefn().options || ($scope.getProp('input_type') === 'CheckBox' ? null : yesNo);
- };
-
- $scope.resetOptions = function() {
- delete $scope.node.defn.options;
- };
-
- $scope.editOptions = function() {
- $scope.editingOptions = true;
- $('#afGuiEditor').addClass('af-gui-editing-content');
- };
-
- $scope.inputTypeCanBe = function(type) {
- var defn = $scope.getDefn();
- switch (type) {
- case 'CheckBox':
- case 'Radio':
- case 'Select':
- return !(!defn.options && defn.data_type !== 'Boolean');
-
- case 'TextArea':
- case 'RichTextEditor':
- return (defn.data_type === 'Text' || defn.data_type === 'String');
- }
- return true;
- };
-
- // Returns a value from either the local field defn or the base defn
- $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];
- };
-
- // Checks for a value in either the local field defn or the base defn
- $scope.propIsset = function(propName) {
- var val = $scope.getProp(propName);
- return !(typeof val === 'undefined' || val === null);
- };
-
- $scope.toggleLabel = function() {
- $scope.node.defn = $scope.node.defn || {};
- if ($scope.node.defn.label === false) {
- delete $scope.node.defn.label;
- } else {
- $scope.node.defn.label = false;
- }
- };
-
- $scope.toggleRequired = function() {
- getSet('required', !getSet('required'));
- return false;
- };
-
- $scope.toggleHelp = function(position) {
- getSet('help_' + position, $scope.propIsset('help_' + position) ? null : ($scope.getDefn()['help_' + position] || ts('Enter text')));
- return false;
- };
-
- // Getter/setter for definition props
- $scope.getSet = function(propName) {
- return _.wrap(propName, getSet);
- };
-
- // 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] && !(!val && !fieldDefn[item]))) {
- localDefn[item] = val;
- } else {
- delete localDefn[item];
- clearOut($scope.node, ['defn'].concat(path));
- }
- return val;
- }
- return $scope.getProp(propName);
- }
- this.getSet = getSet;
-
- this.setEditingOptions = function(val) {
- $scope.editingOptions = val;
- };
-
- // 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;
- }
-
- // Recursively clears out empty arrays and objects
- function clearOut(parent, path) {
- var item;
- while (path.length && _.every(drillDown(parent, path), _.isEmpty)) {
- item = path.pop();
- delete drillDown(parent, path)[item];
- }
- }
- }
- };
- });
-
angular.module('afGuiEditor').directive('afGuiEditOptions', function() {
return {
restrict: 'A',
};
});
- angular.module('afGuiEditor').directive('afGuiText', function() {
- return {
- restrict: 'A',
- templateUrl: '~/afGuiEditor/text.html',
- scope: {
- node: '=afGuiText'
- },
- require: '^^afGuiContainer',
- link: function($scope, element, attrs, container) {
- $scope.container = container;
- },
- controller: function($scope, afAdmin) {
- var ts = $scope.ts = CRM.ts();
-
- $scope.tags = {
- p: ts('Normal Text'),
- legend: ts('Fieldset Legend'),
- h1: ts('Heading 1'),
- h2: ts('Heading 2'),
- h3: ts('Heading 3'),
- h4: ts('Heading 4'),
- h5: ts('Heading 5'),
- h6: ts('Heading 6')
- };
-
- $scope.alignments = {
- 'text-left': ts('Align left'),
- 'text-center': ts('Align center'),
- 'text-right': ts('Align right'),
- 'text-justify': ts('Justify')
- };
-
- $scope.getAlign = function() {
- return _.intersection(afAdmin.splitClass($scope.node['class']), _.keys($scope.alignments))[0] || 'text-left';
- };
-
- $scope.setAlign = function(val) {
- afAdmin.modifyClasses($scope.node, _.keys($scope.alignments), val === 'text-left' ? null : val);
- };
-
- $scope.styles = _.transform(CRM.afGuiEditor.styles, function(styles, val, key) {
- styles['text-' + key] = val;
- });
-
- // Getter/setter for ng-model
- $scope.getSetStyle = function(val) {
- if (arguments.length) {
- return afAdmin.modifyClasses($scope.node, _.keys($scope.styles), val === 'text-default' ? null : val);
- }
- return _.intersection(afAdmin.splitClass($scope.node['class']), _.keys($scope.styles))[0] || 'text-default';
- };
-
- }
- };
- });
-
- var richtextId = 0;
- angular.module('afGuiEditor').directive('afGuiMarkup', function($sce, $timeout) {
- return {
- restrict: 'A',
- templateUrl: '~/afGuiEditor/markup.html',
- scope: {
- node: '=afGuiMarkup'
- },
- require: '^^afGuiContainer',
- link: function($scope, element, attrs, container) {
- $scope.container = container;
- // CRM.wysiwyg doesn't work without a dom id
- $scope.id = 'af-markup-editor-' + richtextId++;
-
- // When creating a new markup container, go straight to edit mode
- $timeout(function() {
- if ($scope.node['#markup'] === false) {
- $scope.edit();
- }
- });
- },
- controller: function($scope) {
- var ts = $scope.ts = CRM.ts();
-
- $scope.getMarkup = function() {
- return $sce.trustAsHtml($scope.node['#markup'] || '');
- };
-
- $scope.edit = function() {
- $('#afGuiEditor').addClass('af-gui-editing-content');
- $scope.editingMarkup = true;
- CRM.wysiwyg.create('#' + $scope.id);
- CRM.wysiwyg.setVal('#' + $scope.id, $scope.node['#markup'] || '<p></p>');
- };
-
- $scope.save = function() {
- $scope.node['#markup'] = CRM.wysiwyg.getVal('#' + $scope.id);
- $scope.close();
- };
-
- $scope.close = function() {
- CRM.wysiwyg.destroy('#' + $scope.id);
- $('#afGuiEditor').removeClass('af-gui-editing-content');
- // If a newly-added wysiwyg was canceled, just remove it
- if ($scope.node['#markup'] === false) {
- $scope.container.removeElement($scope.node);
- } else {
- $scope.editingMarkup = false;
- }
- };
- }
- };
- });
-
-
- angular.module('afGuiEditor').directive('afGuiButton', function() {
- return {
- restrict: 'A',
- templateUrl: '~/afGuiEditor/button.html',
- scope: {
- node: '=afGuiButton'
- },
- require: '^^afGuiContainer',
- link: function($scope, element, attrs, container) {
- $scope.container = container;
- },
- controller: function($scope, afAdmin) {
- var ts = $scope.ts = CRM.ts();
-
- // TODO: Add action selector to UI
- // $scope.actions = {
- // "afform.submit()": ts('Submit Form')
- // };
-
- $scope.styles = _.transform(CRM.afGuiEditor.styles, function(styles, val, key) {
- styles['btn-' + key] = val;
- });
-
- // Getter/setter for ng-model
- $scope.getSetStyle = function(val) {
- if (arguments.length) {
- return afAdmin.modifyClasses($scope.node, _.keys($scope.styles), ['btn', val]);
- }
- return _.intersection(afAdmin.splitClass($scope.node['class']), _.keys($scope.styles))[0] || '';
- };
-
- $scope.pickIcon = function() {
- afAdmin.pickIcon().then(function(val) {
- $scope.node['crm-icon'] = val;
- });
- };
-
- }
- };
- });
-
// Connect bootstrap dropdown.js with angular
// Allows menu content to be conditionally rendered only if open
// This gives a large performance boost for a page with lots of menus
</div>
<div id="afGuiEditor-canvas-body" class="panel-body" ng-if="canvasTab === 'layout'">
- <div ng-if="editor.layout" af-gui-container="editor.layout" entity-name="" />
+ <af-gui-container ng-if="editor.layout" node="editor.layout" entity-name="" ></af-gui-container>
</div>
<div class="panel-body" ng-if="canvasTab === 'markup'">
<p class="help-block">{{:: ts('This is a read-only preview of the auto-generated markup.') }}</p>
+++ /dev/null
-<div class="af-gui-bar" ng-if="node['#tag'] !== 'af-form'" ng-click="selectEntity()" >
- <div class="form-inline" af-gui-menu>
- <span ng-if="container.getNodeType(node) == 'fieldset'">{{ editor.getEntity(entityName).label }}</span>
- <span ng-if="block">{{ join ? ts(join) + ':' : ts('Block:') }}</span>
- <span ng-if="!block">{{ tags[node['#tag']].toLowerCase() }}</span>
- <select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()">
- <option value="">{{:: ts('Custom') }}</option>
- <option ng-value="option.id" ng-repeat="option in block.options track by option.id">{{ option.text }}</option>
- </select>
- <button type="button" class="btn btn-default btn-xs" ng-if="block && !block.layout" ng-click="saveBlock()">{{:: ts('Save...') }}</button>
- <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button pull-right" data-toggle="dropdown" title="{{:: ts('Configure') }}">
- <span><i class="crm-i fa-gear"></i></span>
- </button>
- <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/container-menu.html'"></ul>
- </div>
-</div>
-<div ui-sortable="{handle: '.af-gui-bar', connectWith: '[ui-sortable]', cancel: 'input,textarea,button,select,option,a,.dropdown-menu', placeholder: 'af-gui-dropzone', containment: '#afGuiEditor-canvas-body'}" ui-sortable-update="editor.onDrop" ng-model="getSetChildren" ng-model-options="{getterSetter: true}" class="af-gui-layout {{ getLayout() }}">
- <div ng-repeat="item in getSetChildren()" >
- <div ng-switch="container.getNodeType(item)">
- <div ng-switch-when="fieldset" af-gui-container="item" style="{{ item.style }}" class="af-gui-container af-gui-fieldset af-gui-container-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="container" af-gui-container="item" style="{{ item.style }}" class="af-gui-container af-gui-container-type-{{ item['#tag'] }}" entity-name="entityName" data-entity="{{ entityName }}" />
- <div ng-switch-when="join" af-gui-container="item" style="{{ item.style }}" class="af-gui-container" join="item['af-join']" entity-name="entityName + '-join-' + item['af-join']" data-entity="{{ entityName + '-join-' + item['af-join'] }}" />
- <div ng-switch-when="field" af-gui-field="item" />
- <div ng-switch-when="text" af-gui-text="item" class="af-gui-element af-gui-text" />
- <div ng-switch-when="markup" af-gui-markup="item" class="af-gui-markup" />
- <div ng-switch-when="button" af-gui-button="item" class="af-gui-element af-gui-button" />
- </div>
- </div>
-</div>
-<div ng-if="node['af-repeat'] || node['af-repeat'] === ''" class="af-gui-button">
- <button class="btn btn-xs btn-primary disabled">
- <span class="crm-editable-enabled" ng-click="pickAddIcon()" >
- <i class="crm-i {{ node['add-icon'] || 'fa-plus' }}"></i>
- </span>
- <span af-gui-editable ng-model="node['af-repeat']">{{ node['af-repeat'] }}</span>
- </button>
-</div>
</div>
</li>
<li role="separator" class="divider"></li>
-<li><a href ng-click="container.removeElement(node)"><span class="text-danger">{{:: ts('Delete this button') }}</span></a></li>
+<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger">{{:: ts('Delete this button') }}</span></a></li>
--- /dev/null
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afGuiEditor').component('afGuiButton', {
+ templateUrl: '~/afGuiEditor/elements/afGuiButton.html',
+ bindings: {
+ node: '=',
+ deleteThis: '&'
+ },
+ controller: function($scope, afAdmin) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ // TODO: Add action selector to UI
+ // $scope.actions = {
+ // "afform.submit()": ts('Submit Form')
+ // };
+
+ $scope.styles = _.transform(CRM.afGuiEditor.styles, function(styles, val, key) {
+ styles['btn-' + key] = val;
+ });
+
+ // Getter/setter for ng-model
+ $scope.getSetStyle = function(val) {
+ if (arguments.length) {
+ return afAdmin.modifyClasses(ctrl.node, _.keys($scope.styles), ['btn', val]);
+ }
+ return _.intersection(afAdmin.splitClass(ctrl.node['class']), _.keys($scope.styles))[0] || '';
+ };
+
+ $scope.pickIcon = function() {
+ afAdmin.pickIcon().then(function(val) {
+ ctrl.node['crm-icon'] = val;
+ });
+ };
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
<span><i class="crm-i fa-gear"></i></span>
</button>
- <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/button-menu.html'"></ul>
+ <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiButton-menu.html'"></ul>
</div>
</div>
</div>
<button type="button" class="btn {{ getSetStyle() }} disabled">
<span class="crm-editable-enabled" ng-click="pickIcon()" >
- <i class="crm-i {{ node['crm-icon'] }}"></i>
+ <i class="crm-i {{ $ctrl.node['crm-icon'] }}"></i>
</span>
- <span af-gui-editable ng-model="node['#children'][0]['#text']" >
- {{ node['#children'][0]['#text'] }}
+ <span af-gui-editable ng-model="$ctrl.node['#children'][0]['#text']" >
+ {{ $ctrl.node['#children'][0]['#text'] }}
</span>
</button>
-<li ng-if="!node['af-fieldset'] && !block.layout"><a href ng-click="saveBlock()">{{:: ts('Save as block') }}</a></li>
-<li ng-if="!node['af-fieldset'] && !block.layout" role="separator" class="divider"></li>
-<li ng-if="tags[node['#tag']]">
+<li ng-if="!$ctrl.node['af-fieldset'] && !block.layout"><a href ng-click="saveBlock()">{{:: ts('Save as block') }}</a></li>
+<li ng-if="!$ctrl.node['af-fieldset'] && !block.layout" role="separator" class="divider"></li>
+<li ng-if="tags[$ctrl.node['#tag']]">
<div class="af-gui-field-select-in-dropdown form-inline" ng-click="$event.stopPropagation()">
{{:: ts('Element:') }}
- <select class="form-control" ng-model="node['#tag']" title="{{:: ts('Container type') }}">
+ <select class="form-control" ng-model="$ctrl.node['#tag']" title="{{:: ts('Container type') }}">
<option ng-repeat="(opt, label) in tags" value="{{ opt }}">{{ label }}</option>
</select>
</div>
<li ng-if="isRepeatable()" ng-click="$event.stopPropagation()">
<div class="af-gui-field-select-in-dropdown form-inline">
<label ng-click="toggleRepeat()">
- <i class="crm-i fa-{{ node['af-repeat'] || node['af-repeat'] === '' ? 'check-' : '' }}square-o"></i>
+ <i class="crm-i fa-{{ $ctrl.node['af-repeat'] || $ctrl.node['af-repeat'] === '' ? 'check-' : '' }}square-o"></i>
{{:: ts('Repeat') }}
</label>
- <span ng-style="{visibility: node['af-repeat'] || node['af-repeat'] === '' ? 'visible' : 'hidden'}">
+ <span ng-style="{visibility: $ctrl.node['af-repeat'] || $ctrl.node['af-repeat'] === '' ? 'visible' : 'hidden'}">
<input type="number" class="form-control" ng-model="getSetMin" ng-model-options="{getterSetter: true}" placeholder="{{:: ts('min') }}" min="0" step="1" />
- <input type="number" class="form-control" ng-model="getSetMax" ng-model-options="{getterSetter: true}" placeholder="{{:: ts('max') }}" min="2" step="1" />
</span>
</div>
</div>
</li>
-<li af-gui-menu-item-border="node"></li>
-<li af-gui-menu-item-background="node"></li>
+<li af-gui-menu-item-border="$ctrl.node"></li>
+<li af-gui-menu-item-background="$ctrl.node"></li>
<li role="separator" class="divider"></li>
-<li><a href ng-click="parentContainer.removeElement(node)"><span class="text-danger">{{ !block ? ts('Delete this container') : ts('Delete this block') }}</span></a></li>
+<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger">{{ !block ? ts('Delete this container') : ts('Delete this block') }}</span></a></li>
--- /dev/null
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afGuiEditor').component('afGuiContainer', {
+ templateUrl: '~/afGuiEditor/elements/afGuiContainer.html',
+ bindings: {
+ node: '<',
+ join: '<',
+ entityName: '<',
+ deleteThis: '&'
+ },
+ require: {editor: '^^afGuiEditor'},
+ controller: function($scope, crmApi4, dialogService, afAdmin) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ this.$onInit = function() {
+ if ((ctrl.node['#tag'] in afAdmin.meta.blocks) || ctrl.join) {
+ initializeBlockContainer();
+ }
+ };
+
+ $scope.isSelectedFieldset = function(entityName) {
+ return entityName === ctrl.editor.getSelectedEntityName();
+ };
+
+ $scope.selectEntity = function() {
+ if (ctrl.node['af-fieldset']) {
+ ctrl.editor.selectEntity(ctrl.node['af-fieldset']);
+ }
+ };
+
+ $scope.tags = {
+ div: ts('Container'),
+ fieldset: ts('Fieldset')
+ };
+
+ // Block settings
+ var block = {};
+ $scope.block = null;
+
+ $scope.getSetChildren = function(val) {
+ var collection = block.layout || (ctrl.node && ctrl.node['#children']);
+ return arguments.length ? (collection = val) : collection;
+ };
+
+ $scope.isRepeatable = function() {
+ return ctrl.node['af-fieldset'] || (block.directive && afAdmin.meta.blocks[block.directive].repeat) || ctrl.join;
+ };
+
+ $scope.toggleRepeat = function() {
+ if ('af-repeat' in ctrl.node) {
+ delete ctrl.node.max;
+ delete ctrl.node.min;
+ delete ctrl.node['af-repeat'];
+ delete ctrl.node['add-icon'];
+ } else {
+ ctrl.node.min = '1';
+ ctrl.node['af-repeat'] = ts('Add');
+ }
+ };
+
+ $scope.getSetMin = function(val) {
+ if (arguments.length) {
+ if (ctrl.node.max && val > parseInt(ctrl.node.max, 10)) {
+ ctrl.node.max = '' + val;
+ }
+ if (!val) {
+ delete ctrl.node.min;
+ }
+ else {
+ ctrl.node.min = '' + val;
+ }
+ }
+ return ctrl.node.min ? parseInt(ctrl.node.min, 10) : null;
+ };
+
+ $scope.getSetMax = function(val) {
+ if (arguments.length) {
+ if (ctrl.node.min && val && val < parseInt(ctrl.node.min, 10)) {
+ ctrl.node.min = '' + val;
+ }
+ if (typeof val !== 'number') {
+ delete ctrl.node.max;
+ }
+ else {
+ ctrl.node.max = '' + val;
+ }
+ }
+ return ctrl.node.max ? parseInt(ctrl.node.max, 10) : null;
+ };
+
+ $scope.pickAddIcon = function() {
+ afAdmin.pickIcon().then(function(val) {
+ ctrl.node['add-icon'] = val;
+ });
+ };
+
+ function getBlockNode() {
+ return !ctrl.join ? ctrl.node : (ctrl.node['#children'] && ctrl.node['#children'].length === 1 ? ctrl.node['#children'][0] : null);
+ }
+
+ function setBlockDirective(directive) {
+ if (ctrl.join) {
+ ctrl.node['#children'] = [{'#tag': directive}];
+ } else {
+ delete ctrl.node['#children'];
+ delete ctrl.node['class'];
+ ctrl.node['#tag'] = directive;
+ }
+ }
+
+ function overrideBlockContents(layout) {
+ ctrl.node['#children'] = layout || [];
+ if (!ctrl.join) {
+ ctrl.node['#tag'] = 'div';
+ ctrl.node['class'] = 'af-container';
+ }
+ block.layout = block.directive = null;
+ }
+
+ $scope.layouts = {
+ 'af-layout-rows': ts('Contents display as rows'),
+ 'af-layout-cols': ts('Contents are evenly-spaced columns'),
+ 'af-layout-inline': ts('Contents are arranged inline')
+ };
+
+ $scope.getLayout = function() {
+ if (!ctrl.node) {
+ return '';
+ }
+ return _.intersection(afAdmin.splitClass(ctrl.node['class']), _.keys($scope.layouts))[0] || 'af-layout-rows';
+ };
+
+ $scope.setLayout = function(val) {
+ var classes = ['af-container'];
+ if (val !== 'af-layout-rows') {
+ classes.push(val);
+ }
+ afAdmin.modifyClasses(ctrl.node, _.keys($scope.layouts), classes);
+ };
+
+ $scope.selectBlockDirective = function() {
+ if (block.directive) {
+ block.layout = _.cloneDeep(afAdmin.meta.blocks[block.directive].layout);
+ block.original = block.directive;
+ setBlockDirective(block.directive);
+ }
+ else {
+ overrideBlockContents(block.layout);
+ }
+ };
+
+ function initializeBlockContainer() {
+
+ // Cancel the below $watch expressions if already set
+ _.each(block.listeners, function(deregister) {
+ deregister();
+ });
+
+ block = $scope.block = {
+ directive: null,
+ layout: null,
+ original: null,
+ options: [],
+ listeners: []
+ };
+
+ _.each(afAdmin.meta.blocks, function(blockInfo, directive) {
+ if (directive === ctrl.node['#tag'] || blockInfo.join === ctrl.getFieldEntityType()) {
+ block.options.push({
+ id: directive,
+ text: blockInfo.title
+ });
+ }
+ });
+
+ if (getBlockNode() && getBlockNode()['#tag'] in afAdmin.meta.blocks) {
+ block.directive = block.original = getBlockNode()['#tag'];
+ block.layout = _.cloneDeep(afAdmin.meta.blocks[block.directive].layout);
+ }
+
+ block.listeners.push($scope.$watch('block.layout', function (layout, oldVal) {
+ if (block.directive && layout && layout !== oldVal && !angular.equals(layout, afAdmin.meta.blocks[block.directive].layout)) {
+ overrideBlockContents(block.layout);
+ }
+ }, true));
+ }
+
+ $scope.saveBlock = function() {
+ var options = CRM.utils.adjustDialogDefaults({
+ width: '500px',
+ height: '300px',
+ autoOpen: false,
+ title: ts('Save block')
+ });
+ var model = {
+ title: '',
+ name: null,
+ layout: ctrl.node['#children']
+ };
+ if (ctrl.join) {
+ model.join = ctrl.join;
+ }
+ if ($scope.block && $scope.block.original) {
+ model.title = afAdmin.meta.blocks[$scope.block.original].title;
+ model.name = afAdmin.meta.blocks[$scope.block.original].name;
+ model.block = afAdmin.meta.blocks[$scope.block.original].block;
+ }
+ else {
+ model.block = ctrl.container.getFieldEntityType() || '*';
+ }
+ dialogService.open('saveBlockDialog', '~/afGuiEditor/saveBlock.html', model, options)
+ .then(function(block) {
+ afAdmin.meta.blocks[block.directive_name] = block;
+ setBlockDirective(block.directive_name);
+ initializeBlockContainer();
+ });
+ };
+
+ this.node = ctrl.node;
+
+ this.getNodeType = function(node) {
+ if (!node) {
+ return null;
+ }
+ if (node['#tag'] === 'af-field') {
+ return 'field';
+ }
+ if (node['af-fieldset']) {
+ return 'fieldset';
+ }
+ if (node['af-join']) {
+ return 'join';
+ }
+ if (node['#tag'] && node['#tag'] in afAdmin.meta.blocks) {
+ return 'container';
+ }
+ var classes = afAdmin.splitClass(node['class']),
+ types = ['af-container', 'af-text', 'af-button', 'af-markup'],
+ type = _.intersection(types, classes);
+ return type.length ? type[0].replace('af-', '') : null;
+ };
+
+ this.removeElement = function(element) {
+ afAdmin.removeRecursive($scope.getSetChildren(), {$$hashKey: element.$$hashKey});
+ };
+
+ this.getEntityName = function() {
+ return ctrl.entityName.split('-join-')[0];
+ };
+
+ // Returns the primary entity type for this container e.g. "Contact"
+ this.getMainEntityType = function() {
+ return ctrl.editor && ctrl.editor.getEntity(ctrl.getEntityName()).type;
+ };
+
+ // Returns the entity type for fields within this conainer (join entity type if this is a join, else the primary entity type)
+ this.getFieldEntityType = function() {
+ var joinType = ctrl.entityName.split('-join-');
+ return joinType[1] || (ctrl.editor && ctrl.editor.getEntity(joinType[0]).type);
+ };
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<div class="af-gui-bar" ng-if="$ctrl.node['#tag'] !== 'af-form'" ng-click="selectEntity()" >
+ <div class="form-inline" af-gui-menu>
+ <span ng-if="$ctrl.getNodeType($ctrl.node) == 'fieldset'">{{ $ctrl.editor.getEntity($ctrl.entityName).label }}</span>
+ <span ng-if="block">{{ $ctrl.join ? ts($ctrl.join) + ':' : ts('Block:') }}</span>
+ <span ng-if="!block">{{ tags[$ctrl.node['#tag']].toLowerCase() }}</span>
+ <select ng-if="block" ng-model="block.directive" ng-change="selectBlockDirective()">
+ <option value="">{{:: ts('Custom') }}</option>
+ <option ng-value="option.id" ng-repeat="option in block.options track by option.id">{{ option.text }}</option>
+ </select>
+ <button type="button" class="btn btn-default btn-xs" ng-if="block && !block.layout" ng-click="saveBlock()">{{:: ts('Save...') }}</button>
+ <button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button pull-right" data-toggle="dropdown" title="{{:: ts('Configure') }}">
+ <span><i class="crm-i fa-gear"></i></span>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiContainer-menu.html'"></ul>
+ </div>
+</div>
+<div ui-sortable="{handle: '.af-gui-bar', connectWith: '[ui-sortable]', cancel: 'input,textarea,button,select,option,a,.dropdown-menu', placeholder: 'af-gui-dropzone', containment: '#afGuiEditor-canvas-body'}" ui-sortable-update="$ctrl.editor.onDrop" ng-model="getSetChildren" ng-model-options="{getterSetter: true}" class="af-gui-layout {{ getLayout() }}">
+ <div ng-repeat="item in getSetChildren()" >
+ <div ng-switch="$ctrl.getNodeType(item)">
+ <af-gui-container ng-switch-when="fieldset" node="item" delete-this="$ctrl.removeElement(item)" style="{{ item.style }}" class="af-gui-container af-gui-fieldset af-gui-container-type-{{ item['#tag'] }}" ng-class="{'af-entity-selected': isSelectedFieldset(item['af-fieldset'])}" entity-name="item['af-fieldset']" data-entity="{{ item['af-fieldset'] }}" ></af-gui-container>
+ <af-gui-container ng-switch-when="container" node="item" delete-this="$ctrl.removeElement(item)" style="{{ item.style }}" class="af-gui-container af-gui-container-type-{{ item['#tag'] }}" entity-name="$ctrl.entityName" data-entity="{{ $ctrl.entityName }}" ></af-gui-container>
+ <af-gui-container ng-switch-when="join" node="item" delete-this="$ctrl.removeElement(item)" style="{{ item.style }}" class="af-gui-container" join="item['af-join']" entity-name="$ctrl.entityName + '-join-' + item['af-join']" data-entity="{{ $ctrl.entityName + '-join-' + item['af-join'] }}" ></af-gui-container>
+ <af-gui-field ng-switch-when="field" node="item" delete-this="$ctrl.removeElement(item)" ></af-gui-field>
+ <af-gui-text ng-switch-when="text" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-text" ></af-gui-text>
+ <af-gui-markup ng-switch-when="markup" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-markup" ></af-gui-markup>
+ <af-gui-button ng-switch-when="button" node="item" delete-this="$ctrl.removeElement(item)" class="af-gui-element af-gui-button" ></af-gui-button>
+ </div>
+ </div>
+</div>
+<div ng-if="$ctrl.node['af-repeat'] || $ctrl.node['af-repeat'] === ''" class="af-gui-button">
+ <button class="btn btn-xs btn-primary disabled">
+ <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>
+ </button>
+</div>
<div href ng-click="$event.stopPropagation()" class="af-gui-field-select-in-dropdown">
<label>{{:: ts('Type:') }}</label>
<select class="form-control" ng-model="getSet('input_type')" ng-model-options="{getterSetter: true}" title="{{:: ts('Field type') }}">
- <option ng-repeat="(type, label) in editor.meta.inputType" value="{{ type }}" ng-if="inputTypeCanBe(type)">{{ label }}</option>
+ <option ng-repeat="(type, label) in meta.inputType" value="{{ type }}" ng-if="inputTypeCanBe(type)">{{ label }}</option>
</select>
</div>
</li>
</li>
<li>
<a href ng-click="toggleLabel(); $event.stopPropagation();" title="{{:: ts('Show field label') }}">
- <i class="crm-i" ng-class="{'fa-square-o': node.defn.title === false, 'fa-check-square-o': node.defn.title !== false}"></i>
+ <i class="crm-i" ng-class="{'fa-square-o': $ctrl.node.defn.title === false, 'fa-check-square-o': $ctrl.node.defn.title !== false}"></i>
{{:: ts('Label') }}
</a>
</li>
<li role="separator" class="divider" ng-if="hasOptions()"></li>
<li ng-if="hasOptions()" ng-click="$event.stopPropagation()">
<a href ng-click="resetOptions()" title="{{:: ts('Reset the option list for this field') }}">
- <i class="crm-i fa-{{ node.defn.options ? '' : 'check-' }}circle-o"></i>
+ <i class="crm-i fa-{{ $ctrl.node.defn.options ? '' : 'check-' }}circle-o"></i>
{{:: ts('Default option list') }}
</a>
</li>
<li ng-if="hasOptions()">
<a href ng-click="editOptions()" title="{{:: ts('Customize the option list for this field') }}">
- <i class="crm-i fa-{{ !node.defn.options ? '' : 'check-' }}circle-o"></i>
+ <i class="crm-i fa-{{ !$ctrl.node.defn.options ? '' : 'check-' }}circle-o"></i>
{{:: ts('Customize options') }}
</a>
</li>
<li role="separator" class="divider"></li>
<li>
- <a href ng-click="container.removeElement(node)" title="{{:: ts('Remove field from form') }}">
+ <a href ng-click="$ctrl.deleteThis()" title="{{:: ts('Remove field from form') }}">
<span class="text-danger">{{:: ts('Delete this field') }}</span>
</a>
</li>
--- /dev/null
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afGuiEditor').component('afGuiField', {
+ templateUrl: '~/afGuiEditor/elements/afGuiField.html',
+ bindings: {
+ node: '=',
+ deleteThis: '&'
+ },
+ require: {
+ editor: '^^afGuiEditor',
+ container: '^^afGuiContainer'
+ },
+ controller: function($scope, afAdmin) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ $scope.editingOptions = false;
+ var yesNo = [
+ {key: '1', label: ts('Yes')},
+ {key: '0', label: ts('No')}
+ ];
+
+ this.$onInit = function() {
+ $scope.meta = afAdmin.meta;
+ };
+
+ $scope.getEntity = function() {
+ return ctrl.editor ? ctrl.editor.getEntity(ctrl.container.getEntityName()) : {};
+ };
+
+ $scope.getDefn = this.getDefn = function() {
+ return ctrl.editor ? afAdmin.getField(ctrl.container.getFieldEntityType(), ctrl.node.name) : {};
+ };
+
+ $scope.hasOptions = function() {
+ var inputType = $scope.getProp('input_type');
+ return _.contains(['CheckBox', 'Radio', 'Select'], inputType) && !(inputType === 'CheckBox' && !$scope.getDefn().options);
+ };
+
+ $scope.getOptions = this.getOptions = function() {
+ if (ctrl.node.defn && ctrl.node.defn.options) {
+ return ctrl.node.defn.options;
+ }
+ return $scope.getDefn().options || ($scope.getProp('input_type') === 'CheckBox' ? null : yesNo);
+ };
+
+ $scope.resetOptions = function() {
+ delete ctrl.node.defn.options;
+ };
+
+ $scope.editOptions = function() {
+ $scope.editingOptions = true;
+ $('#afGuiEditor').addClass('af-gui-editing-content');
+ };
+
+ $scope.inputTypeCanBe = function(type) {
+ var defn = $scope.getDefn();
+ switch (type) {
+ case 'CheckBox':
+ case 'Radio':
+ case 'Select':
+ return !(!defn.options && defn.data_type !== 'Boolean');
+
+ case 'TextArea':
+ case 'RichTextEditor':
+ return (defn.data_type === 'Text' || defn.data_type === 'String');
+ }
+ return true;
+ };
+
+ // Returns a value from either the local field defn or the base defn
+ $scope.getProp = function(propName) {
+ var path = propName.split('.'),
+ item = path.pop(),
+ localDefn = drillDown(ctrl.node.defn || {}, path);
+ if (typeof localDefn[item] !== 'undefined') {
+ return localDefn[item];
+ }
+ return drillDown($scope.getDefn(), path)[item];
+ };
+
+ // Checks for a value in either the local field defn or the base defn
+ $scope.propIsset = function(propName) {
+ var val = $scope.getProp(propName);
+ return !(typeof val === 'undefined' || val === null);
+ };
+
+ $scope.toggleLabel = function() {
+ ctrl.node.defn = ctrl.node.defn || {};
+ if (ctrl.node.defn.label === false) {
+ delete ctrl.node.defn.label;
+ } else {
+ ctrl.node.defn.label = false;
+ }
+ };
+
+ $scope.toggleRequired = function() {
+ getSet('required', !getSet('required'));
+ return false;
+ };
+
+ $scope.toggleHelp = function(position) {
+ getSet('help_' + position, $scope.propIsset('help_' + position) ? null : ($scope.getDefn()['help_' + position] || ts('Enter text')));
+ return false;
+ };
+
+ // Getter/setter for definition props
+ $scope.getSet = function(propName) {
+ return _.wrap(propName, getSet);
+ };
+
+ // Getter/setter callback
+ function getSet(propName, val) {
+ if (arguments.length > 1) {
+ var path = propName.split('.'),
+ item = path.pop(),
+ localDefn = drillDown(ctrl.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] && !(!val && !fieldDefn[item]))) {
+ localDefn[item] = val;
+ } else {
+ delete localDefn[item];
+ clearOut(ctrl.node, ['defn'].concat(path));
+ }
+ return val;
+ }
+ return $scope.getProp(propName);
+ }
+ this.getSet = getSet;
+
+ this.setEditingOptions = function(val) {
+ $scope.editingOptions = val;
+ };
+
+ // 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;
+ }
+
+ // Recursively clears out empty arrays and objects
+ function clearOut(parent, path) {
+ var item;
+ while (path.length && _.every(drillDown(parent, path), _.isEmpty)) {
+ item = path.pop();
+ delete drillDown(parent, path)[item];
+ }
+ }
+ }
+ });
+
+})(angular, CRM.$, CRM._);
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
<span><i class="crm-i fa-gear"></i></span>
</button>
- <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/field-menu.html'"></ul>
+ <ul class="dropdown-menu dropdown-menu-right" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiField-menu.html'"></ul>
</div>
</div>
</div>
- <label ng-style="{visibility: node.defn.label === false ? 'hidden' : 'visible'}" ng-class="{'af-gui-field-required': getProp('required')}" class="af-gui-node-title">
+ <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="getDefn().label">{{ getProp('label') }}</span>
</label>
<div class="af-gui-field-help" ng-if="propIsset('help_pre')">
--- /dev/null
+<li>
+ <a href ng-click="edit()">{{:: ts('Edit content') }}</a>
+</li>
+<li role="separator" class="divider"></li>
+<li af-gui-menu-item-border="$ctrl.node"></li>
+<li af-gui-menu-item-background="$ctrl.node"></li>
+<li role="separator" class="divider"></li>
+<li>
+ <a href ng-click="$ctrl.deleteThis()"><span class="text-danger">{{:: ts('Delete this content') }}</span></a>
+</li>
--- /dev/null
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+ "use strict";
+
+ var richtextId = 0;
+
+ angular.module('afGuiEditor').component('afGuiMarkup', {
+ templateUrl: '~/afGuiEditor/elements/afGuiMarkup.html',
+ bindings: {
+ node: '=',
+ deleteThis: '&'
+ },
+ controller: function($scope, $sce, $timeout) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ this.$onInit = function() {
+ // CRM.wysiwyg doesn't work without a dom id
+ $scope.id = 'af-markup-editor-' + richtextId++;
+
+ // When creating a new markup container, go straight to edit mode
+ $timeout(function() {
+ if (ctrl.node['#markup'] === false) {
+ $scope.edit();
+ }
+ });
+ };
+
+ $scope.getMarkup = function() {
+ return $sce.trustAsHtml(ctrl.node['#markup'] || '');
+ };
+
+ $scope.edit = function() {
+ $('#afGuiEditor').addClass('af-gui-editing-content');
+ $scope.editingMarkup = true;
+ CRM.wysiwyg.create('#' + $scope.id);
+ CRM.wysiwyg.setVal('#' + $scope.id, ctrl.node['#markup'] || '<p></p>');
+ };
+
+ $scope.save = function() {
+ ctrl.node['#markup'] = CRM.wysiwyg.getVal('#' + $scope.id);
+ $scope.close();
+ };
+
+ $scope.close = function() {
+ CRM.wysiwyg.destroy('#' + $scope.id);
+ $('#afGuiEditor').removeClass('af-gui-editing-content');
+ // If a newly-added wysiwyg was canceled, just remove it
+ if (ctrl.node['#markup'] === false) {
+ $scope.container.removeElement(ctrl.node);
+ } else {
+ $scope.editingMarkup = false;
+ }
+ };
+ }
+ });
+
+})(angular, CRM.$, CRM._);
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
<span><i class="crm-i fa-gear"></i></span>
</button>
- <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/markup-menu.html'"></ul>
+ <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiMarkup-menu.html'"></ul>
</div>
</div>
</div>
-<div ng-if="!editingMarkup" ng-click="edit()" class="af-gui-markup-content crm-editable-enabled {{ node['class'] }}">
- <div ng-bind-html="getMarkup()" style="{{ node.style }}"></div>
+<div ng-if="!editingMarkup" ng-click="edit()" class="af-gui-markup-content crm-editable-enabled {{ $ctrl.node['class'] }}">
+ <div ng-bind-html="getMarkup()" style="{{ $ctrl.node.style }}"></div>
<div class="af-gui-markup-content-overlay"></div>
</div>
<div class="af-gui-content-editing-area">
</li>
<li role="separator" class="divider"></li>
<li>
- <a href ng-click="container.removeElement(node)"><span class="text-danger">{{:: ts('Delete this text') }}</span></a>
+ <a href ng-click="$ctrl.deleteThis()"><span class="text-danger">{{:: ts('Delete this text') }}</span></a>
</li>
--- /dev/null
+// https://civicrm.org/licensing
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afGuiEditor').component('afGuiText', {
+ templateUrl: '~/afGuiEditor/elements/afGuiText.html',
+ bindings: {
+ node: '=',
+ deleteThis: '&'
+ },
+ controller: function($scope, afAdmin) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ $scope.tags = {
+ p: ts('Normal Text'),
+ legend: ts('Fieldset Legend'),
+ h1: ts('Heading 1'),
+ h2: ts('Heading 2'),
+ h3: ts('Heading 3'),
+ h4: ts('Heading 4'),
+ h5: ts('Heading 5'),
+ h6: ts('Heading 6')
+ };
+
+ $scope.alignments = {
+ 'text-left': ts('Align left'),
+ 'text-center': ts('Align center'),
+ 'text-right': ts('Align right'),
+ 'text-justify': ts('Justify')
+ };
+
+ $scope.getAlign = function() {
+ return _.intersection(afAdmin.splitClass(ctrl.node['class']), _.keys($scope.alignments))[0] || 'text-left';
+ };
+
+ $scope.setAlign = function(val) {
+ afAdmin.modifyClasses(ctrl.node, _.keys($scope.alignments), val === 'text-left' ? null : val);
+ };
+
+ $scope.styles = _.transform(CRM.afGuiEditor.styles, function(styles, val, key) {
+ styles['text-' + key] = val;
+ });
+
+ // Getter/setter for ng-model
+ $scope.getSetStyle = function(val) {
+ if (arguments.length) {
+ return afAdmin.modifyClasses(ctrl.node, _.keys($scope.styles), val === 'text-default' ? null : val);
+ }
+ return _.intersection(afAdmin.splitClass(ctrl.node['class']), _.keys($scope.styles))[0] || 'text-default';
+ };
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
<span><i class="crm-i fa-gear"></i></span>
</button>
- <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/text-menu.html'"></ul>
+ <ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiEditor/elements/afGuiText-menu.html'"></ul>
</div>
</div>
</div>
-<p class="af-gui-node-title {{ node['class'] + ' af-gui-text-' + node['#tag'] }}" >
- <span af-gui-editable ng-model="node['#children'][0]['#text']">{{ node['#children'][0]['#text'] }}</span>
+<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>
</p>
+++ /dev/null
-<li>
- <a href ng-click="edit()">{{:: ts('Edit content') }}</a>
-</li>
-<li role="separator" class="divider"></li>
-<li af-gui-menu-item-border="node"></li>
-<li af-gui-menu-item-background="node"></li>
-<li role="separator" class="divider"></li>
-<li>
- <a href ng-click="container.removeElement(node)"><span class="text-danger">{{:: ts('Delete this content') }}</span></a>
-</li>