5b1253fc920f8e61bc2155171f65ac1257cf67ec
1 (function(angular
, $, _
) {
4 angular
.module('afGuiEditor', CRM
.angRequires('afGuiEditor'))
6 .service('afGui', function(crmApi4
, $parse
, $q
) {
8 // Parse strings of javascript that php couldn't interpret
9 function evaluate(collection
) {
10 _
.each(collection
, function(item
) {
11 if (_
.isPlainObject(item
)) {
12 evaluate(item
['#children']);
13 _
.each(item
, function(node
, idx
) {
14 if (_
.isString(node
)) {
15 var str
= _
.trim(node
);
16 if (str
[0] === '{' || str
[0] === '[' || str
.slice(0, 3) === 'ts(') {
17 item
[idx
] = $parse(str
)({ts
: CRM
.ts('afform')});
25 function getStyles(node
) {
26 return !node
|| !node
.style
? {} : _
.transform(node
.style
.split(';'), function(styles
, style
) {
27 var keyVal
= _
.map(style
.split(':'), _
.trim
);
28 if (keyVal
.length
> 1 && keyVal
[1].length
) {
29 styles
[keyVal
[0]] = keyVal
[1];
34 function setStyle(node
, name
, val
) {
35 var styles
= getStyles(node
);
40 if (_
.isEmpty(styles
)) {
43 node
.style
= _
.transform(styles
, function(combined
, val
, name
) {
44 combined
.push(name
+ ': ' + val
);
49 // Turns a space-separated list (e.g. css classes) into an array
50 function splitClass(str
) {
54 return str
? _
.unique(_
.trim(str
).split(/\s+/g)) : [];
57 function modifyClasses(node
, toRemove
, toAdd
) {
58 var classes
= splitClass(node
['class']);
60 classes
= _
.difference(classes
, splitClass(toRemove
));
63 classes
= _
.unique(classes
.concat(splitClass(toAdd
)));
65 node
['class'] = classes
.join(' ');
69 // Called when loading a new afform for editing - clears out stale metadata
70 resetMeta: function() {
71 _
.each(CRM
.afGuiEditor
.entities
, function(entity
) {
74 CRM
.afGuiEditor
.blocks
= {};
77 // Takes the results from api.Afform.loadAdminData and processes the metadata
78 // Note this runs once when loading a new afform for editing (just after this.resetMeta is called)
79 // and it also runs when adding new entities or joins to the form.
80 addMeta: function(data
) {
81 evaluate(data
.definition
.layout
);
82 if (data
.definition
.type
=== 'block') {
83 CRM
.afGuiEditor
.blocks
[data
.definition
.directive_name
] = data
.definition
;
85 // Add new or updated blocks
86 _
.each(data
.blocks
, function(block
) {
87 // Avoid overwriting complete block record with an incomplete one
88 if (!CRM
.afGuiEditor
.blocks
[block
.directive_name
] || block
.layout
) {
90 evaluate(block
.layout
);
92 CRM
.afGuiEditor
.blocks
[block
.directive_name
] = block
;
95 _
.each(data
.entities
, function(entity
, entityName
) {
96 if (!CRM
.afGuiEditor
.entities
[entityName
]) {
97 CRM
.afGuiEditor
.entities
[entityName
] = entity
;
100 _
.each(data
.fields
, function(fields
, entityName
) {
101 if (CRM
.afGuiEditor
.entities
[entityName
]) {
102 CRM
.afGuiEditor
.entities
[entityName
].fields
= fields
;
105 // Optimization - since contact fields are a combination of these three,
106 // the server doesn't send contact fields if sending contact-type fields
107 if ('Individual' in data
.fields
|| 'Household' in data
.fields
|| 'Organization' in data
.fields
) {
108 CRM
.afGuiEditor
.entities
.Contact
.fields
= _
.assign({},
109 (CRM
.afGuiEditor
.entities
.Individual
|| {}).fields
,
110 (CRM
.afGuiEditor
.entities
.Household
|| {}).fields
,
111 (CRM
.afGuiEditor
.entities
.Organization
|| {}).fields
116 meta
: CRM
.afGuiEditor
,
118 getField: function(entityType
, fieldName
) {
119 return CRM
.afGuiEditor
.entities
[entityType
].fields
[fieldName
];
122 // Recursively searches a collection and its children using _.filter
123 // Returns an array of all matches, or an object if the indexBy param is used
124 findRecursive
: function findRecursive(collection
, predicate
, indexBy
) {
125 var items
= _
.filter(collection
, predicate
);
126 _
.each(collection
, function(item
) {
127 if (_
.isPlainObject(item
) && item
['#children']) {
128 var childMatches
= findRecursive(item
['#children'], predicate
);
129 if (childMatches
.length
) {
130 Array
.prototype.push
.apply(items
, childMatches
);
134 return indexBy
? _
.indexBy(items
, indexBy
) : items
;
137 // Applies _.remove() to an item and its children
138 removeRecursive
: function removeRecursive(collection
, removeParams
) {
139 _
.remove(collection
, removeParams
);
140 _
.each(collection
, function(item
) {
141 if (_
.isPlainObject(item
) && item
['#children']) {
142 removeRecursive(item
['#children'], removeParams
);
147 splitClass
: splitClass
,
148 modifyClasses
: modifyClasses
,
149 getStyles
: getStyles
,
152 pickIcon: function() {
153 var deferred
= $q
.defer();
154 $('#af-gui-icon-picker').off('change').siblings('.crm-icon-picker-button').click();
155 $('#af-gui-icon-picker').on('change', function() {
156 deferred
.resolve($(this).val());
158 return deferred
.promise
;
163 // Shoehorn in a non-angular widget for picking icons
165 $('#crm-container').append('<div style="display:none"><input id="af-gui-icon-picker"></div>');
166 CRM
.loadScript(CRM
.config
.resourceBase
+ 'js/jquery/jquery.crmIconPicker.js').done(function() {
167 $('#af-gui-icon-picker').crmIconPicker();
171 // Connect bootstrap dropdown.js with angular
172 // Allows menu content to be conditionally rendered only if open
173 // This gives a large performance boost for a page with lots of menus
174 angular
.module('afGuiEditor').directive('afGuiMenu', function() {
177 link: function($scope
, element
, attrs
) {
180 .on('show.bs.dropdown', function() {
181 $scope
.$apply(function() {
182 $scope
.menu
.open
= true;
185 .on('hidden.bs.dropdown', function() {
186 $scope
.$apply(function() {
187 $scope
.menu
.open
= false;
194 })(angular
, CRM
.$, CRM
._
);