1 // https://civicrm.org/licensing
2 (function(angular
, $, _
) {
5 angular
.module('afGuiEditor').component('afGuiContainer', {
6 templateUrl
: '~/afGuiEditor/elements/afGuiContainer.html',
13 require
: {editor
: '^^afGuiEditor'},
14 controller: function($scope
, crmApi4
, dialogService
, afGui
) {
15 var ts
= $scope
.ts
= CRM
.ts('org.civicrm.afform_admin'),
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
) {
24 crmApi4('Afform', 'loadAdminData', {
25 definition
: {name
: afGui
.meta
.blocks
[blockTag
].name
},
26 skipEntities
: _
.transform(afGui
.meta
.entities
, function(result
, entity
, entityName
) {
28 result
.push(entityName
);
31 }, 0).then(function(data
) {
33 initializeBlockContainer();
37 initializeBlockContainer();
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 containment
: '#afGuiEditor-canvas-body'
49 $scope
.isSelectedFieldset = function(entityName
) {
50 return entityName
=== ctrl
.editor
.getSelectedEntityName();
53 $scope
.selectEntity = function() {
54 if (ctrl
.node
['af-fieldset']) {
55 ctrl
.editor
.selectEntity(ctrl
.node
['af-fieldset']);
61 fieldset
: ts('Fieldset')
68 $scope
.getSetChildren = function(val
) {
69 var collection
= block
.layout
|| (ctrl
.node
&& ctrl
.node
['#children']);
70 return arguments
.length
? (collection
= val
) : collection
;
73 $scope
.isRepeatable = function() {
74 return ctrl
.node
['af-fieldset'] || (block
.directive
&& afGui
.meta
.blocks
[block
.directive
].repeat
) || ctrl
.join
;
77 $scope
.toggleRepeat = function() {
78 if ('af-repeat' in ctrl
.node
) {
81 delete ctrl
.node
['af-repeat'];
82 delete ctrl
.node
['add-icon'];
85 ctrl
.node
['af-repeat'] = ts('Add');
89 $scope
.getSetMin = function(val
) {
90 if (arguments
.length
) {
91 if (ctrl
.node
.max
&& val
> parseInt(ctrl
.node
.max
, 10)) {
92 ctrl
.node
.max
= '' + val
;
98 ctrl
.node
.min
= '' + val
;
101 return ctrl
.node
.min
? parseInt(ctrl
.node
.min
, 10) : null;
104 $scope
.getSetMax = function(val
) {
105 if (arguments
.length
) {
106 if (ctrl
.node
.min
&& val
&& val
< parseInt(ctrl
.node
.min
, 10)) {
107 ctrl
.node
.min
= '' + val
;
109 if (typeof val
!== 'number') {
110 delete ctrl
.node
.max
;
113 ctrl
.node
.max
= '' + val
;
116 return ctrl
.node
.max
? parseInt(ctrl
.node
.max
, 10) : null;
119 $scope
.pickAddIcon = function() {
120 afGui
.pickIcon().then(function(val
) {
121 ctrl
.node
['add-icon'] = val
;
125 function getBlockNode() {
126 return !ctrl
.join
? ctrl
.node
: (ctrl
.node
['#children'] && ctrl
.node
['#children'].length
=== 1 ? ctrl
.node
['#children'][0] : null);
129 function setBlockDirective(directive
) {
131 ctrl
.node
['#children'] = [{'#tag': directive
}];
133 delete ctrl
.node
['#children'];
134 delete ctrl
.node
['class'];
135 ctrl
.node
['#tag'] = directive
;
139 function overrideBlockContents(layout
) {
140 ctrl
.node
['#children'] = layout
|| [];
142 ctrl
.node
['#tag'] = 'div';
143 ctrl
.node
['class'] = 'af-container';
145 block
.layout
= block
.directive
= null;
149 'af-layout-rows': ts('Contents display as rows'),
150 'af-layout-cols': ts('Contents are evenly-spaced columns'),
151 'af-layout-inline': ts('Contents are arranged inline')
154 $scope
.getLayout = function() {
158 return _
.intersection(afGui
.splitClass(ctrl
.node
['class']), _
.keys($scope
.layouts
))[0] || 'af-layout-rows';
161 $scope
.setLayout = function(val
) {
162 var classes
= ['af-container'];
163 if (val
!== 'af-layout-rows') {
166 afGui
.modifyClasses(ctrl
.node
, _
.keys($scope
.layouts
), classes
);
169 $scope
.selectBlockDirective = function() {
170 if (block
.directive
) {
171 block
.layout
= _
.cloneDeep(afGui
.meta
.blocks
[block
.directive
].layout
);
172 block
.original
= block
.directive
;
173 setBlockDirective(block
.directive
);
176 overrideBlockContents(block
.layout
);
180 function initializeBlockContainer() {
182 // Cancel the below $watch expressions if already set
183 _
.each(block
.listeners
, function(deregister
) {
187 block
= $scope
.block
= {
195 _
.each(afGui
.meta
.blocks
, function(blockInfo
, directive
) {
196 if (directive
=== ctrl
.node
['#tag'] || (blockInfo
.join
&& blockInfo
.join
=== ctrl
.getFieldEntityType())) {
199 text
: blockInfo
.title
204 if (getBlockNode() && getBlockNode()['#tag'] in afGui
.meta
.blocks
) {
205 block
.directive
= block
.original
= getBlockNode()['#tag'];
206 block
.layout
= _
.cloneDeep(afGui
.meta
.blocks
[block
.directive
].layout
);
209 block
.listeners
.push($scope
.$watch('block.layout', function (layout
, oldVal
) {
210 if (block
.directive
&& layout
&& layout
!== oldVal
&& !angular
.equals(layout
, afGui
.meta
.blocks
[block
.directive
].layout
)) {
211 overrideBlockContents(block
.layout
);
216 $scope
.saveBlock = function() {
217 var options
= CRM
.utils
.adjustDialogDefaults({
221 title
: ts('Save block')
227 layout
: ctrl
.node
['#children']
230 model
.join
= ctrl
.join
;
232 if ($scope
.block
&& $scope
.block
.original
) {
233 model
.title
= afGui
.meta
.blocks
[$scope
.block
.original
].title
;
234 model
.name
= afGui
.meta
.blocks
[$scope
.block
.original
].name
;
235 model
.block
= afGui
.meta
.blocks
[$scope
.block
.original
].block
;
238 model
.block
= ctrl
.getFieldEntityType();
240 dialogService
.open('saveBlockDialog', '~/afGuiEditor/saveBlock.html', model
, options
)
241 .then(function(block
) {
242 afGui
.meta
.blocks
[block
.directive_name
] = block
;
243 setBlockDirective(block
.directive_name
);
244 initializeBlockContainer();
248 this.node
= ctrl
.node
;
250 this.getNodeType = function(node
) {
251 if (!node
|| !node
['#tag']) {
254 if (node
['#tag'] === 'af-field') {
257 if ('af-fieldset' in node
) {
260 if (node
['af-join']) {
263 if (node
['#tag'] && node
['#tag'] in afGui
.meta
.blocks
) {
266 if (node
['#tag'] && (node
['#tag'].slice(0, 19) === 'crm-search-display-')) {
267 return 'searchDisplay';
269 var classes
= afGui
.splitClass(node
['class']),
270 types
= ['af-container', 'af-text', 'af-button', 'af-markup'],
271 type
= _
.intersection(types
, classes
);
272 return type
.length
? type
[0].replace('af-', '') : null;
275 this.removeElement = function(element
) {
276 afGui
.removeRecursive($scope
.getSetChildren(), {$$hashKey
: element
.$$hashKey
});
279 this.getEntityName = function() {
280 return ctrl
.entityName
? ctrl
.entityName
.split('-join-')[0] : null;
283 // Returns the primary entity type for this container e.g. "Contact"
284 this.getMainEntityType = function() {
285 return ctrl
.editor
&& ctrl
.editor
.getEntity(ctrl
.getEntityName()).type
;
288 // Returns the entity type for fields within this conainer (join entity type if this is a join, else the primary entity type)
289 this.getFieldEntityType = function(fieldName
) {
290 // If entityName is declared for this fieldset, return entity-type or join-type
291 if (ctrl
.entityName
) {
292 var joinType
= ctrl
.entityName
.split('-join-');
293 return joinType
[1] || (ctrl
.editor
&& ctrl
.editor
.getEntity(joinType
[0]).type
);
295 // If entityName is not declared, this field belongs to a search
297 prefix
= _
.includes(fieldName
, '.') ? fieldName
.split('.')[0] : null;
298 _
.each(afGui
.meta
.searchDisplays
, function(searchDisplay
) {
300 _
.each(searchDisplay
['saved_search.api_params'].join
, function(join
) {
301 var joinInfo
= join
[0].split(' AS ');
302 if (prefix
=== joinInfo
[1]) {
303 entityType
= joinInfo
[0];
308 if (!entityType
&& fieldName
&& afGui
.getField(searchDisplay
['saved_search.api_entity'], fieldName
)) {
309 entityType
= searchDisplay
['saved_search.api_entity'];
315 return entityType
|| _
.map(afGui
.meta
.searchDisplays
, 'saved_search.api_entity')[0];
321 })(angular
, CRM
.$, CRM
._
);