Commit | Line | Data |
---|---|---|
f6c0358e | 1 | (function(angular, $, _) { |
881d52bb | 2 | "use strict"; |
cb46dc65 CW |
3 | angular.module('afGuiEditor', CRM.angRequires('afGuiEditor')) |
4 | ||
d132f0a6 | 5 | .service('afAdmin', function(crmApi4, $parse, $q) { |
cb46dc65 CW |
6 | |
7 | // Parse strings of javascript that php couldn't interpret | |
8 | function evaluate(collection) { | |
9 | _.each(collection, function(item) { | |
10 | if (_.isPlainObject(item)) { | |
11 | evaluate(item['#children']); | |
12 | _.each(item, function(node, idx) { | |
13 | if (_.isString(node)) { | |
14 | var str = _.trim(node); | |
15 | if (str[0] === '{' || str[0] === '[' || str.slice(0, 3) === 'ts(') { | |
16 | item[idx] = $parse(str)({ts: CRM.ts('afform')}); | |
17 | } | |
18 | } | |
19 | }); | |
20 | } | |
21 | }); | |
22 | } | |
23 | ||
24 | function getStyles(node) { | |
25 | return !node || !node.style ? {} : _.transform(node.style.split(';'), function(styles, style) { | |
26 | var keyVal = _.map(style.split(':'), _.trim); | |
27 | if (keyVal.length > 1 && keyVal[1].length) { | |
28 | styles[keyVal[0]] = keyVal[1]; | |
29 | } | |
30 | }, {}); | |
31 | } | |
32 | ||
33 | function setStyle(node, name, val) { | |
34 | var styles = getStyles(node); | |
35 | styles[name] = val; | |
36 | if (!val) { | |
37 | delete styles[name]; | |
38 | } | |
39 | if (_.isEmpty(styles)) { | |
40 | delete node.style; | |
41 | } else { | |
42 | node.style = _.transform(styles, function(combined, val, name) { | |
43 | combined.push(name + ': ' + val); | |
44 | }, []).join('; '); | |
45 | } | |
46 | } | |
47 | ||
48 | // Turns a space-separated list (e.g. css classes) into an array | |
49 | function splitClass(str) { | |
50 | if (_.isArray(str)) { | |
51 | return str; | |
52 | } | |
53 | return str ? _.unique(_.trim(str).split(/\s+/g)) : []; | |
54 | } | |
55 | ||
56 | function modifyClasses(node, toRemove, toAdd) { | |
57 | var classes = splitClass(node['class']); | |
58 | if (toRemove) { | |
59 | classes = _.difference(classes, splitClass(toRemove)); | |
60 | } | |
61 | if (toAdd) { | |
62 | classes = _.unique(classes.concat(splitClass(toAdd))); | |
63 | } | |
64 | node['class'] = classes.join(' '); | |
65 | } | |
66 | ||
67 | return { | |
68 | // Initialize/refresh data about the current afform + available blocks | |
69 | initialize: function(afName) { | |
70 | var promise = crmApi4('Afform', 'get', { | |
71 | layoutFormat: 'shallow', | |
72 | formatWhitespace: true, | |
73 | where: [afName ? ["OR", [["name", "=", afName], ["block", "IS NOT NULL"]]] : ["block", "IS NOT NULL"]] | |
74 | }); | |
75 | promise.then(function(afforms) { | |
76 | CRM.afGuiEditor.blocks = {}; | |
77 | _.each(afforms, function(form) { | |
78 | evaluate(form.layout); | |
79 | if (form.block) { | |
80 | CRM.afGuiEditor.blocks[form.directive_name] = form; | |
81 | } | |
82 | }); | |
83 | }); | |
84 | return promise; | |
85 | }, | |
86 | ||
87 | meta: CRM.afGuiEditor, | |
88 | ||
89 | getField: function(entityType, fieldName) { | |
90 | return CRM.afGuiEditor.entities[entityType].fields[fieldName]; | |
91 | }, | |
92 | ||
93 | // Recursively searches a collection and its children using _.filter | |
94 | // Returns an array of all matches, or an object if the indexBy param is used | |
95 | findRecursive: function findRecursive(collection, predicate, indexBy) { | |
96 | var items = _.filter(collection, predicate); | |
97 | _.each(collection, function(item) { | |
98 | if (_.isPlainObject(item) && item['#children']) { | |
99 | var childMatches = findRecursive(item['#children'], predicate); | |
100 | if (childMatches.length) { | |
101 | Array.prototype.push.apply(items, childMatches); | |
102 | } | |
103 | } | |
104 | }); | |
105 | return indexBy ? _.indexBy(items, indexBy) : items; | |
106 | }, | |
107 | ||
108 | // Applies _.remove() to an item and its children | |
109 | removeRecursive: function removeRecursive(collection, removeParams) { | |
110 | _.remove(collection, removeParams); | |
111 | _.each(collection, function(item) { | |
112 | if (_.isPlainObject(item) && item['#children']) { | |
113 | removeRecursive(item['#children'], removeParams); | |
114 | } | |
115 | }); | |
116 | }, | |
117 | ||
118 | splitClass: splitClass, | |
119 | modifyClasses: modifyClasses, | |
120 | getStyles: getStyles, | |
d132f0a6 CW |
121 | setStyle: setStyle, |
122 | ||
123 | pickIcon: function() { | |
124 | var deferred = $q.defer(); | |
125 | $('#af-gui-icon-picker').off('change').siblings('.crm-icon-picker-button').click(); | |
126 | $('#af-gui-icon-picker').on('change', function() { | |
127 | deferred.resolve($(this).val()); | |
128 | }); | |
129 | return deferred.promise; | |
130 | } | |
cb46dc65 CW |
131 | }; |
132 | }); | |
f6c0358e | 133 | |
d132f0a6 CW |
134 | // Shoehorn in a non-angular widget for picking icons |
135 | $(function() { | |
136 | $('#crm-container').append('<div style="display:none"><input id="af-gui-icon-picker"></div>'); | |
137 | CRM.loadScript(CRM.config.resourceBase + 'js/jquery/jquery.crmIconPicker.js').done(function() { | |
138 | $('#af-gui-icon-picker').crmIconPicker(); | |
139 | }); | |
140 | }); | |
141 | ||
9633741a CW |
142 | angular.module('afGuiEditor').controller('afGuiSaveBlock', function($scope, crmApi4, dialogService) { |
143 | var ts = $scope.ts = CRM.ts(), | |
144 | model = $scope.model, | |
145 | original = $scope.original = { | |
146 | title: model.title, | |
147 | name: model.name | |
148 | }; | |
149 | if (model.name) { | |
150 | $scope.$watch('model.name', function(val, oldVal) { | |
151 | if (!val && model.title === original.title) { | |
152 | model.title += ' ' + ts('(copy)'); | |
153 | } | |
154 | else if (val === original.name && val !== oldVal) { | |
155 | model.title = original.title; | |
156 | } | |
157 | }); | |
158 | } | |
159 | $scope.cancel = function() { | |
160 | dialogService.cancel('saveBlockDialog'); | |
161 | }; | |
162 | $scope.save = function() { | |
163 | $('.ui-dialog:visible').block(); | |
164 | crmApi4('Afform', 'save', {formatWhitespace: true, records: [JSON.parse(angular.toJson(model))]}) | |
165 | .then(function(result) { | |
166 | dialogService.close('saveBlockDialog', result[0]); | |
167 | }); | |
168 | }; | |
169 | }); | |
170 | ||
b4def6e9 CW |
171 | angular.module('afGuiEditor').directive('afGuiEditOptions', function() { |
172 | return { | |
173 | restrict: 'A', | |
174 | templateUrl: '~/afGuiEditor/editOptions.html', | |
175 | scope: true, | |
176 | require: '^^afGuiField', | |
177 | link: function ($scope, element, attrs, afGuiField) { | |
178 | $scope.field = afGuiField; | |
179 | $scope.options = JSON.parse(angular.toJson(afGuiField.getOptions())); | |
180 | var optionKeys = _.map($scope.options, 'key'); | |
181 | $scope.deletedOptions = _.filter(JSON.parse(angular.toJson(afGuiField.getDefn().options || [])), function(item) { | |
182 | return !_.contains(optionKeys, item.key); | |
183 | }); | |
184 | $scope.originalLabels = _.transform(afGuiField.getDefn().options || [], function(originalLabels, item) { | |
185 | originalLabels[item.key] = item.label; | |
186 | }, {}); | |
187 | }, | |
188 | controller: function ($scope) { | |
189 | var ts = $scope.ts = CRM.ts(); | |
190 | ||
191 | $scope.deleteOption = function(option, $index) { | |
192 | $scope.options.splice($index, 1); | |
193 | $scope.deletedOptions.push(option); | |
194 | }; | |
195 | ||
196 | $scope.restoreOption = function(option, $index) { | |
197 | $scope.deletedOptions.splice($index, 1); | |
198 | $scope.options.push(option); | |
199 | }; | |
6fb9e8d2 | 200 | |
b4def6e9 CW |
201 | $scope.save = function() { |
202 | $scope.field.getSet('options', JSON.parse(angular.toJson($scope.options))); | |
203 | $scope.close(); | |
204 | }; | |
205 | ||
206 | $scope.close = function() { | |
207 | $scope.field.setEditingOptions(false); | |
192695ae | 208 | $('#afGuiEditor').removeClass('af-gui-editing-content'); |
b4def6e9 | 209 | }; |
66af6937 CW |
210 | } |
211 | }; | |
212 | }); | |
213 | ||
b4def6e9 CW |
214 | // Connect bootstrap dropdown.js with angular |
215 | // Allows menu content to be conditionally rendered only if open | |
216 | // This gives a large performance boost for a page with lots of menus | |
217 | angular.module('afGuiEditor').directive('afGuiMenu', function() { | |
218 | return { | |
219 | restrict: 'A', | |
220 | link: function($scope, element, attrs) { | |
221 | $scope.menu = {}; | |
222 | element | |
223 | .on('show.bs.dropdown', function() { | |
224 | $scope.$apply(function() { | |
225 | $scope.menu.open = true; | |
226 | }); | |
227 | }) | |
228 | .on('hidden.bs.dropdown', function() { | |
229 | $scope.$apply(function() { | |
230 | $scope.menu.open = false; | |
231 | }); | |
232 | }); | |
233 | } | |
234 | }; | |
235 | }); | |
236 | ||
192695ae | 237 | // Menu item to control the border property of a node |
cb46dc65 | 238 | angular.module('afGuiEditor').directive('afGuiMenuItemBorder', function(afAdmin) { |
192695ae CW |
239 | return { |
240 | restrict: 'A', | |
241 | templateUrl: '~/afGuiEditor/menu-item-border.html', | |
242 | scope: { | |
243 | node: '=afGuiMenuItemBorder' | |
244 | }, | |
245 | link: function($scope, element, attrs) { | |
246 | var ts = $scope.ts = CRM.ts(); | |
247 | ||
248 | $scope.getSetBorderWidth = function(width) { | |
249 | return getSetBorderProp($scope.node, 0, arguments.length ? width : null); | |
250 | }; | |
251 | ||
252 | $scope.getSetBorderStyle = function(style) { | |
253 | return getSetBorderProp($scope.node, 1, arguments.length ? style : null); | |
254 | }; | |
255 | ||
256 | $scope.getSetBorderColor = function(color) { | |
257 | return getSetBorderProp($scope.node, 2, arguments.length ? color : null); | |
258 | }; | |
259 | ||
260 | function getSetBorderProp(node, idx, val) { | |
261 | var border = getBorder(node) || ['1px', '', '#000000']; | |
262 | if (val === null) { | |
263 | return border[idx]; | |
264 | } | |
265 | border[idx] = val; | |
cb46dc65 | 266 | afAdmin.setStyle(node, 'border', val ? border.join(' ') : null); |
192695ae CW |
267 | } |
268 | ||
269 | function getBorder(node) { | |
cb46dc65 | 270 | var border = _.map((afAdmin.getStyles(node).border || '').split(' '), _.trim); |
192695ae CW |
271 | return border.length > 2 ? border : null; |
272 | } | |
273 | } | |
274 | }; | |
275 | }); | |
276 | ||
277 | // Menu item to control the background property of a node | |
cb46dc65 | 278 | angular.module('afGuiEditor').directive('afGuiMenuItemBackground', function(afAdmin) { |
192695ae CW |
279 | return { |
280 | restrict: 'A', | |
281 | templateUrl: '~/afGuiEditor/menu-item-background.html', | |
282 | scope: { | |
283 | node: '=afGuiMenuItemBackground' | |
284 | }, | |
285 | link: function($scope, element, attrs) { | |
286 | var ts = $scope.ts = CRM.ts(); | |
287 | ||
288 | $scope.getSetBackgroundColor = function(color) { | |
289 | if (!arguments.length) { | |
cb46dc65 | 290 | return afAdmin.getStyles($scope.node)['background-color'] || '#ffffff'; |
192695ae | 291 | } |
cb46dc65 | 292 | afAdmin.setStyle($scope.node, 'background-color', color); |
192695ae CW |
293 | }; |
294 | } | |
295 | }; | |
296 | }); | |
297 | ||
f6c0358e | 298 | })(angular, CRM.$, CRM._); |