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',
48 containment
: '#afGuiEditor-canvas-body'
51 $scope
.isSelectedFieldset = function(entityName
) {
52 return entityName
=== ctrl
.editor
.getSelectedEntityName();
55 $scope
.selectEntity = function() {
56 if (ctrl
.node
['af-fieldset']) {
57 ctrl
.editor
.selectEntity(ctrl
.node
['af-fieldset']);
63 fieldset
: ts('Fieldset')
70 $scope
.getSetChildren = function(val
) {
71 var collection
= block
.layout
|| (ctrl
.node
&& ctrl
.node
['#children']);
72 return arguments
.length
? (collection
= val
) : collection
;
75 $scope
.isRepeatable = function() {
76 return ctrl
.node
['af-fieldset'] || (block
.directive
&& afGui
.meta
.blocks
[block
.directive
].repeat
) || ctrl
.join
;
79 $scope
.toggleRepeat = function() {
80 if ('af-repeat' in ctrl
.node
) {
83 delete ctrl
.node
['af-repeat'];
84 delete ctrl
.node
['add-icon'];
87 ctrl
.node
['af-repeat'] = ts('Add');
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
;
100 ctrl
.node
.min
= '' + val
;
103 return ctrl
.node
.min
? parseInt(ctrl
.node
.min
, 10) : null;
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
;
111 if (typeof val
!== 'number') {
112 delete ctrl
.node
.max
;
115 ctrl
.node
.max
= '' + val
;
118 return ctrl
.node
.max
? parseInt(ctrl
.node
.max
, 10) : null;
121 $scope
.pickAddIcon = function() {
122 afGui
.pickIcon().then(function(val
) {
123 ctrl
.node
['add-icon'] = val
;
127 function getBlockNode() {
128 return !ctrl
.join
? ctrl
.node
: (ctrl
.node
['#children'] && ctrl
.node
['#children'].length
=== 1 ? ctrl
.node
['#children'][0] : null);
131 function setBlockDirective(directive
) {
133 ctrl
.node
['#children'] = [{'#tag': directive
}];
135 delete ctrl
.node
['#children'];
136 delete ctrl
.node
['class'];
137 ctrl
.node
['#tag'] = directive
;
141 function overrideBlockContents(layout
) {
142 ctrl
.node
['#children'] = layout
|| [];
144 ctrl
.node
['#tag'] = 'div';
145 ctrl
.node
['class'] = 'af-container';
147 block
.layout
= block
.directive
= null;
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')
156 $scope
.getLayout = function() {
160 return _
.intersection(afGui
.splitClass(ctrl
.node
['class']), _
.keys($scope
.layouts
))[0] || 'af-layout-rows';
163 $scope
.setLayout = function(val
) {
164 var classes
= ['af-container'];
165 if (val
!== 'af-layout-rows') {
168 afGui
.modifyClasses(ctrl
.node
, _
.keys($scope
.layouts
), classes
);
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
);
178 overrideBlockContents(block
.layout
);
182 function initializeBlockContainer() {
184 // Cancel the below $watch expressions if already set
185 _
.each(block
.listeners
, function(deregister
) {
189 block
= $scope
.block
= {
197 _
.each(afGui
.meta
.blocks
, function(blockInfo
, directive
) {
198 if (directive
=== ctrl
.node
['#tag'] || (blockInfo
.join
&& blockInfo
.join
=== ctrl
.getFieldEntityType())) {
201 text
: blockInfo
.title
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
);
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
);
218 $scope
.saveBlock = function() {
219 var options
= CRM
.utils
.adjustDialogDefaults({
223 title
: ts('Save block')
229 layout
: ctrl
.node
['#children']
232 model
.join
= ctrl
.join
;
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
;
240 model
.block
= ctrl
.getFieldEntityType();
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();
250 this.node
= ctrl
.node
;
252 this.getNodeType = function(node
) {
253 if (!node
|| !node
['#tag']) {
256 if (node
['#tag'] === 'af-field') {
259 if ('af-fieldset' in node
) {
262 if (node
['af-join']) {
265 if (node
['#tag'] && node
['#tag'] in afGui
.meta
.blocks
) {
268 if (node
['#tag'] && (node
['#tag'].slice(0, 19) === 'crm-search-display-')) {
269 return 'searchDisplay';
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;
277 this.removeElement = function(element
) {
278 afGui
.removeRecursive($scope
.getSetChildren(), {$$hashKey
: element
.$$hashKey
});
281 this.getEntityName = function() {
282 return ctrl
.entityName
? ctrl
.entityName
.split('-join-')[0] : null;
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
;
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
);
297 // If entityName is not declared, this field belongs to a search
299 prefix
= _
.includes(fieldName
, '.') ? fieldName
.split('.')[0] : null;
300 _
.each(afGui
.meta
.searchDisplays
, function(searchDisplay
) {
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];
310 if (!entityType
&& fieldName
&& afGui
.getField(searchDisplay
['saved_search.api_entity'], fieldName
)) {
311 entityType
= searchDisplay
['saved_search.api_entity'];
317 return entityType
|| _
.map(afGui
.meta
.searchDisplays
, 'saved_search.api_entity')[0];
323 })(angular
, CRM
.$, CRM
._
);