Merge pull request #19487 from civicrm/5.34
[civicrm-core.git] / ext / afform / admin / ang / afGuiEditor / elements / afGuiContainer.component.js
1 // https://civicrm.org/licensing
2 (function(angular, $, _) {
3 "use strict";
4
5 angular.module('afGuiEditor').component('afGuiContainer', {
6 templateUrl: '~/afGuiEditor/elements/afGuiContainer.html',
7 bindings: {
8 node: '<',
9 join: '<',
10 entityName: '<',
11 deleteThis: '&'
12 },
13 require: {editor: '^^afGuiEditor'},
14 controller: function($scope, crmApi4, dialogService, afGui) {
15 var ts = $scope.ts = CRM.ts(),
16 ctrl = this;
17
18 this.$onInit = function() {
19 if (ctrl.node['#tag'] && ((ctrl.node['#tag'] in afGui.meta.blocks) || ctrl.join)) {
20 var blockNode = getBlockNode(),
21 blockTag = blockNode ? blockNode['#tag'] : null;
22 if (blockTag && (blockTag in afGui.meta.blocks) && !afGui.meta.blocks[blockTag].layout) {
23 ctrl.loading = true;
24 crmApi4('Afform', 'loadAdminData', {
25 definition: {name: afGui.meta.blocks[blockTag].name}
26 }, 0).then(function(data) {
27 afGui.addMeta(data);
28 initializeBlockContainer();
29 ctrl.loading = false;
30 });
31 }
32 initializeBlockContainer();
33 }
34 };
35
36 $scope.isSelectedFieldset = function(entityName) {
37 return entityName === ctrl.editor.getSelectedEntityName();
38 };
39
40 $scope.selectEntity = function() {
41 if (ctrl.node['af-fieldset']) {
42 ctrl.editor.selectEntity(ctrl.node['af-fieldset']);
43 }
44 };
45
46 $scope.tags = {
47 div: ts('Container'),
48 fieldset: ts('Fieldset')
49 };
50
51 // Block settings
52 var block = {};
53 $scope.block = null;
54
55 $scope.getSetChildren = function(val) {
56 var collection = block.layout || (ctrl.node && ctrl.node['#children']);
57 return arguments.length ? (collection = val) : collection;
58 };
59
60 $scope.isRepeatable = function() {
61 return ctrl.node['af-fieldset'] || (block.directive && afGui.meta.blocks[block.directive].repeat) || ctrl.join;
62 };
63
64 $scope.toggleRepeat = function() {
65 if ('af-repeat' in ctrl.node) {
66 delete ctrl.node.max;
67 delete ctrl.node.min;
68 delete ctrl.node['af-repeat'];
69 delete ctrl.node['add-icon'];
70 } else {
71 ctrl.node.min = '1';
72 ctrl.node['af-repeat'] = ts('Add');
73 }
74 };
75
76 $scope.getSetMin = function(val) {
77 if (arguments.length) {
78 if (ctrl.node.max && val > parseInt(ctrl.node.max, 10)) {
79 ctrl.node.max = '' + val;
80 }
81 if (!val) {
82 delete ctrl.node.min;
83 }
84 else {
85 ctrl.node.min = '' + val;
86 }
87 }
88 return ctrl.node.min ? parseInt(ctrl.node.min, 10) : null;
89 };
90
91 $scope.getSetMax = function(val) {
92 if (arguments.length) {
93 if (ctrl.node.min && val && val < parseInt(ctrl.node.min, 10)) {
94 ctrl.node.min = '' + val;
95 }
96 if (typeof val !== 'number') {
97 delete ctrl.node.max;
98 }
99 else {
100 ctrl.node.max = '' + val;
101 }
102 }
103 return ctrl.node.max ? parseInt(ctrl.node.max, 10) : null;
104 };
105
106 $scope.pickAddIcon = function() {
107 afGui.pickIcon().then(function(val) {
108 ctrl.node['add-icon'] = val;
109 });
110 };
111
112 function getBlockNode() {
113 return !ctrl.join ? ctrl.node : (ctrl.node['#children'] && ctrl.node['#children'].length === 1 ? ctrl.node['#children'][0] : null);
114 }
115
116 function setBlockDirective(directive) {
117 if (ctrl.join) {
118 ctrl.node['#children'] = [{'#tag': directive}];
119 } else {
120 delete ctrl.node['#children'];
121 delete ctrl.node['class'];
122 ctrl.node['#tag'] = directive;
123 }
124 }
125
126 function overrideBlockContents(layout) {
127 ctrl.node['#children'] = layout || [];
128 if (!ctrl.join) {
129 ctrl.node['#tag'] = 'div';
130 ctrl.node['class'] = 'af-container';
131 }
132 block.layout = block.directive = null;
133 }
134
135 $scope.layouts = {
136 'af-layout-rows': ts('Contents display as rows'),
137 'af-layout-cols': ts('Contents are evenly-spaced columns'),
138 'af-layout-inline': ts('Contents are arranged inline')
139 };
140
141 $scope.getLayout = function() {
142 if (!ctrl.node) {
143 return '';
144 }
145 return _.intersection(afGui.splitClass(ctrl.node['class']), _.keys($scope.layouts))[0] || 'af-layout-rows';
146 };
147
148 $scope.setLayout = function(val) {
149 var classes = ['af-container'];
150 if (val !== 'af-layout-rows') {
151 classes.push(val);
152 }
153 afGui.modifyClasses(ctrl.node, _.keys($scope.layouts), classes);
154 };
155
156 $scope.selectBlockDirective = function() {
157 if (block.directive) {
158 block.layout = _.cloneDeep(afGui.meta.blocks[block.directive].layout);
159 block.original = block.directive;
160 setBlockDirective(block.directive);
161 }
162 else {
163 overrideBlockContents(block.layout);
164 }
165 };
166
167 function initializeBlockContainer() {
168
169 // Cancel the below $watch expressions if already set
170 _.each(block.listeners, function(deregister) {
171 deregister();
172 });
173
174 block = $scope.block = {
175 directive: null,
176 layout: null,
177 original: null,
178 options: [],
179 listeners: []
180 };
181
182 _.each(afGui.meta.blocks, function(blockInfo, directive) {
183 if (directive === ctrl.node['#tag'] || blockInfo.join === ctrl.getFieldEntityType()) {
184 block.options.push({
185 id: directive,
186 text: blockInfo.title
187 });
188 }
189 });
190
191 if (getBlockNode() && getBlockNode()['#tag'] in afGui.meta.blocks) {
192 block.directive = block.original = getBlockNode()['#tag'];
193 block.layout = _.cloneDeep(afGui.meta.blocks[block.directive].layout);
194 }
195
196 block.listeners.push($scope.$watch('block.layout', function (layout, oldVal) {
197 if (block.directive && layout && layout !== oldVal && !angular.equals(layout, afGui.meta.blocks[block.directive].layout)) {
198 overrideBlockContents(block.layout);
199 }
200 }, true));
201 }
202
203 $scope.saveBlock = function() {
204 var options = CRM.utils.adjustDialogDefaults({
205 width: '500px',
206 height: '300px',
207 autoOpen: false,
208 title: ts('Save block')
209 });
210 var model = {
211 title: '',
212 name: null,
213 layout: ctrl.node['#children']
214 };
215 if (ctrl.join) {
216 model.join = ctrl.join;
217 }
218 if ($scope.block && $scope.block.original) {
219 model.title = afGui.meta.blocks[$scope.block.original].title;
220 model.name = afGui.meta.blocks[$scope.block.original].name;
221 model.block = afGui.meta.blocks[$scope.block.original].block;
222 }
223 else {
224 model.block = ctrl.container.getFieldEntityType() || '*';
225 }
226 dialogService.open('saveBlockDialog', '~/afGuiEditor/saveBlock.html', model, options)
227 .then(function(block) {
228 afGui.meta.blocks[block.directive_name] = block;
229 setBlockDirective(block.directive_name);
230 initializeBlockContainer();
231 });
232 };
233
234 this.node = ctrl.node;
235
236 this.getNodeType = function(node) {
237 if (!node || !node['#tag']) {
238 return null;
239 }
240 if (node['#tag'] === 'af-field') {
241 return 'field';
242 }
243 if ('af-fieldset' in node) {
244 return 'fieldset';
245 }
246 if (node['af-join']) {
247 return 'join';
248 }
249 if (node['#tag'] && node['#tag'] in afGui.meta.blocks) {
250 return 'container';
251 }
252 if (node['#tag'] && (node['#tag'].slice(0, 19) === 'crm-search-display-')) {
253 return 'searchDisplay';
254 }
255 var classes = afGui.splitClass(node['class']),
256 types = ['af-container', 'af-text', 'af-button', 'af-markup'],
257 type = _.intersection(types, classes);
258 return type.length ? type[0].replace('af-', '') : null;
259 };
260
261 this.removeElement = function(element) {
262 afGui.removeRecursive($scope.getSetChildren(), {$$hashKey: element.$$hashKey});
263 };
264
265 this.getEntityName = function() {
266 return ctrl.entityName ? ctrl.entityName.split('-join-')[0] : null;
267 };
268
269 // Returns the primary entity type for this container e.g. "Contact"
270 this.getMainEntityType = function() {
271 return ctrl.editor && ctrl.editor.getEntity(ctrl.getEntityName()).type;
272 };
273
274 // Returns the entity type for fields within this conainer (join entity type if this is a join, else the primary entity type)
275 this.getFieldEntityType = function(fieldName) {
276 // If entityName is declared for this fieldset, return entity-type or join-type
277 if (ctrl.entityName) {
278 var joinType = ctrl.entityName.split('-join-');
279 return joinType[1] || (ctrl.editor && ctrl.editor.getEntity(joinType[0]).type);
280 }
281 // If entityName is not declared, this field belongs to a search
282 var entityType,
283 prefix = _.includes(fieldName, '.') ? fieldName.split('.')[0] : null;
284 _.each(afGui.meta.searchDisplays, function(searchDisplay) {
285 if (prefix) {
286 _.each(searchDisplay['saved_search.api_params'].join, function(join) {
287 var joinInfo = join[0].split(' AS ');
288 if (prefix === joinInfo[1]) {
289 entityType = joinInfo[0];
290 return false;
291 }
292 });
293 }
294 if (!entityType && afGui.getField(searchDisplay['saved_search.api_entity'], fieldName)) {
295 entityType = searchDisplay['saved_search.api_entity'];
296 }
297 if (entityType) {
298 return false;
299 }
300 });
301 return entityType;
302 };
303
304 }
305 });
306
307 })(angular, CRM.$, CRM._);