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