4cbe9a44f2a3999300d8591ae5b99fa1f5bc1cb9
[civicrm-core.git] / ext / afform / admin / ang / afGuiEditor / afGuiEditor.component.js
1 // https://civicrm.org/licensing
2 (function(angular, $, _) {
3 "use strict";
4
5 angular.module('afGuiEditor').component('afGuiEditor', {
6 templateUrl: '~/afGuiEditor/afGuiEditor.html',
7 bindings: {
8 data: '<',
9 entity: '<',
10 mode: '@'
11 },
12 controllerAs: 'editor',
13 controller: function($scope, crmApi4, afGui, $parse, $timeout, $location) {
14 var ts = $scope.ts = CRM.ts('afform');
15 $scope.afform = null;
16 $scope.saving = false;
17 $scope.selectedEntityName = null;
18 this.meta = afGui.meta;
19 var editor = this;
20
21 this.$onInit = function() {
22 // Load the current form plus blocks & fields
23 afGui.resetMeta();
24 afGui.addMeta(this.data);
25 initializeForm();
26 };
27
28 // Initialize the current form
29 function initializeForm() {
30 $scope.afform = editor.data.definition;
31 if (!$scope.afform) {
32 alert('Error: unknown form');
33 }
34 if (editor.mode === 'clone') {
35 delete $scope.afform.name;
36 $scope.afform.title += ' ' + ts('(copy)');
37 }
38 $scope.canvasTab = 'layout';
39 $scope.layoutHtml = '';
40 editor.layout = {'#children': []};
41 $scope.entities = {};
42
43 if ($scope.afform.type === 'form') {
44 editor.allowEntityConfig = true;
45 editor.layout['#children'] = afGui.findRecursive($scope.afform.layout, {'#tag': 'af-form'})[0]['#children'];
46 $scope.entities = afGui.findRecursive(editor.layout['#children'], {'#tag': 'af-entity'}, 'name');
47
48 if (editor.mode === 'create') {
49 editor.addEntity(editor.entity);
50 editor.layout['#children'].push(afGui.meta.elements.submit.element);
51 }
52 }
53
54 if ($scope.afform.type === 'block') {
55 editor.layout['#children'] = $scope.afform.layout;
56 editor.blockEntity = $scope.afform.join || $scope.afform.block;
57 $scope.entities[editor.blockEntity] = {
58 type: editor.blockEntity,
59 name: editor.blockEntity,
60 label: afGui.getEntity(editor.blockEntity).label
61 };
62 }
63
64 if ($scope.afform.type === 'search') {
65 editor.layout['#children'] = afGui.findRecursive($scope.afform.layout, {'af-fieldset': ''})[0]['#children'];
66
67 }
68
69 // Set changesSaved to true on initial load, false thereafter whenever changes are made to the model
70 $scope.changesSaved = editor.mode === 'edit' ? 1 : false;
71 $scope.$watch('afform', function () {
72 $scope.changesSaved = $scope.changesSaved === 1;
73 }, true);
74 }
75
76 $scope.updateLayoutHtml = function() {
77 $scope.layoutHtml = '...Loading...';
78 crmApi4('Afform', 'convert', {layout: [editor.layout], from: 'deep', to: 'html', formatWhitespace: true})
79 .then(function(r){
80 $scope.layoutHtml = r[0].layout || '(Error)';
81 })
82 .catch(function(r){
83 $scope.layoutHtml = '(Error)';
84 });
85 };
86
87 this.addEntity = function(type, selectTab) {
88 var meta = afGui.meta.entities[type],
89 num = 1;
90 // Give this new entity a unique name
91 while (!!$scope.entities[type + num]) {
92 num++;
93 }
94 $scope.entities[type + num] = _.assign($parse(meta.defaults)($scope), {
95 '#tag': 'af-entity',
96 type: meta.entity,
97 name: type + num,
98 label: meta.label + ' ' + num,
99 loading: true,
100 });
101
102 function addToCanvas() {
103 // Add this af-entity tag after the last existing one
104 var pos = 1 + _.findLastIndex(editor.layout['#children'], {'#tag': 'af-entity'});
105 editor.layout['#children'].splice(pos, 0, $scope.entities[type + num]);
106 // Create a new af-fieldset container for the entity
107 var fieldset = _.cloneDeep(afGui.meta.elements.fieldset.element);
108 fieldset['af-fieldset'] = type + num;
109 fieldset['#children'][0]['#children'][0]['#text'] = meta.label + ' ' + num;
110 // Add boilerplate contents
111 _.each(meta.boilerplate, function (tag) {
112 fieldset['#children'].push(tag);
113 });
114 // Attempt to place the new af-fieldset after the last one on the form
115 pos = 1 + _.findLastIndex(editor.layout['#children'], 'af-fieldset');
116 if (pos) {
117 editor.layout['#children'].splice(pos, 0, fieldset);
118 } else {
119 editor.layout['#children'].push(fieldset);
120 }
121 delete $scope.entities[type + num].loading;
122 if (selectTab) {
123 editor.selectEntity(type + num);
124 }
125 }
126
127 if (meta.fields) {
128 addToCanvas();
129 } else {
130 crmApi4('Afform', 'loadAdminData', {
131 definition: {type: 'form'},
132 entity: type
133 }, 0).then(function(data) {
134 afGui.addMeta(data);
135 addToCanvas();
136 });
137 }
138 };
139
140 this.removeEntity = function(entityName) {
141 delete $scope.entities[entityName];
142 afGui.removeRecursive(editor.layout['#children'], {'#tag': 'af-entity', name: entityName});
143 afGui.removeRecursive(editor.layout['#children'], {'af-fieldset': entityName});
144 this.selectEntity(null);
145 };
146
147 this.selectEntity = function(entityName) {
148 $scope.selectedEntityName = entityName;
149 };
150
151 this.getEntity = function(entityName) {
152 return $scope.entities[entityName];
153 };
154
155 this.getSelectedEntityName = function() {
156 return $scope.selectedEntityName;
157 };
158
159 // Validates that a drag-n-drop action is allowed
160 this.onDrop = function(event, ui) {
161 var sort = ui.item.sortable;
162 // Check if this is a callback for an item dropped into a different container
163 // @see https://github.com/angular-ui/ui-sortable notes on canceling
164 if (!sort.received && sort.source[0] !== sort.droptarget[0]) {
165 var $source = $(sort.source[0]),
166 $target = $(sort.droptarget[0]),
167 $item = $(ui.item[0]);
168 // Fields cannot be dropped outside their own entity
169 if ($item.is('[af-gui-field]') || $item.has('[af-gui-field]').length) {
170 if ($source.closest('[data-entity]').attr('data-entity') !== $target.closest('[data-entity]').attr('data-entity')) {
171 return sort.cancel();
172 }
173 }
174 // Entity-fieldsets cannot be dropped into other entity-fieldsets
175 if ((sort.model['af-fieldset'] || $item.has('.af-gui-fieldset').length) && $target.closest('.af-gui-fieldset').length) {
176 return sort.cancel();
177 }
178 }
179 };
180
181 $scope.save = function() {
182 $scope.saving = $scope.changesSaved = true;
183 crmApi4('Afform', 'save', {formatWhitespace: true, records: [JSON.parse(angular.toJson($scope.afform))]})
184 .then(function (data) {
185 $scope.saving = false;
186 $scope.afform.name = data[0].name;
187 if (editor.mode !== 'edit') {
188 $location.url('/edit/' + data[0].name);
189 }
190 });
191 };
192
193 $scope.$watch('afform.title', function(newTitle, oldTitle) {
194 if (typeof oldTitle === 'string') {
195 _.each($scope.entities, function(entity) {
196 if (entity.data && entity.data.source === oldTitle) {
197 entity.data.source = newTitle;
198 }
199 });
200 }
201 });
202 }
203 });
204
205 })(angular, CRM.$, CRM._);