Commit | Line | Data |
---|---|---|
4b513f23 | 1 | (function($, _) { |
6a488035 TO |
2 | if (!CRM.UF) CRM.UF = {}; |
3 | ||
4 | var YESNO = [ | |
5 | {val: 0, label: ts('No')}, | |
6 | {val: 1, label: ts('Yes')} | |
7 | ]; | |
8 | ||
9 | var VISIBILITY = [ | |
10 | {val: 'User and User Admin Only', label: ts('User and User Admin Only'), isInSelectorAllowed: false}, | |
fac01cb1 DG |
11 | {val: 'Public Pages', label: ts('Expose Publicly'), isInSelectorAllowed: true}, |
12 | {val: 'Public Pages and Listings', label: ts('Expose Publicly and for Listings'), isInSelectorAllowed: true} | |
6a488035 TO |
13 | ]; |
14 | ||
15 | var LOCATION_TYPES = _.map(CRM.PseudoConstant.locationType, function(value, key) { | |
16 | return {val: key, label: value}; | |
17 | }); | |
18 | LOCATION_TYPES.unshift({val: '', label: ts('Primary')}); | |
19 | var DEFAULT_LOCATION_TYPE_ID = ''; | |
20 | ||
21 | var PHONE_TYPES = _.map(CRM.PseudoConstant.phoneType, function(value, key) { | |
22 | return {val: key, label: value}; | |
23 | }); | |
1a228b4c | 24 | |
25 | var WEBSITE_TYPES = _.map(CRM.PseudoConstant.websiteType, function(value, key) { | |
26 | return {val: key, label: value}; | |
27 | }); | |
6a488035 | 28 | var DEFAULT_PHONE_TYPE_ID = PHONE_TYPES[0].val; |
1a228b4c | 29 | var DEFAULT_WEBSITE_TYPE_ID = WEBSITE_TYPES[0].val; |
6a488035 TO |
30 | |
31 | /** | |
32 | * Add a help link to a form label | |
33 | */ | |
34 | function addHelp(title, options) { | |
c7915cc9 | 35 | return title + ' <a href="#" onclick=\'CRM.help("' + title + '", ' + JSON.stringify(options) + '); return false;\' title="' + ts('%1 Help', {1: title}) + '" aria-label="' + ts('%1 Help', {1: title}) + '" class="helpicon"></a>'; |
6a488035 TO |
36 | } |
37 | ||
38 | function watchChanges() { | |
39 | CRM.designerApp.vent.trigger('ufUnsaved', true); | |
40 | } | |
41 | ||
42 | /** | |
43 | * Parse a "group_type" expression | |
44 | * | |
45 | * @param string groupTypeExpr example: "Individual,Activity\0ActivityType:2:28" | |
46 | * Note: I've seen problems where HTML "�" != JS '\0', so we support ';;' as an equivalent delimiter | |
47 | * @return Object example: {coreTypes: {"Individual":true,"Activity":true}, subTypes: {"ActivityType":{2: true, 28:true}]}} | |
48 | */ | |
49 | CRM.UF.parseTypeList = function(groupTypeExpr) { | |
50 | var typeList = {coreTypes: {}, subTypes:{}}; | |
51 | // The API may have automatically converted a string with '\0' to an array | |
52 | var parts = _.isArray(groupTypeExpr) ? groupTypeExpr : groupTypeExpr.replace(';;','\0').split('\0'); | |
53 | var coreTypesExpr = parts[0]; | |
54 | var subTypesExpr = parts[1]; | |
55 | ||
fb079bbf | 56 | if (!_.isEmpty(coreTypesExpr)) { |
6a488035 TO |
57 | _.each(coreTypesExpr.split(','), function(coreType){ |
58 | typeList.coreTypes[coreType] = true; | |
59 | }); | |
60 | } | |
61 | ||
99e239bc | 62 | //CRM-15427 Allow Multiple subtype filtering |
fb079bbf | 63 | if (!_.isEmpty(subTypesExpr)) { |
99e239bc | 64 | if (subTypesExpr.indexOf(';;') !== -1) { |
65 | var subTypeparts = subTypesExpr.replace(/;;/g,'\0').split('\0'); | |
66 | _.each(subTypeparts, function(subTypepart) { | |
67 | var subTypes = subTypepart.split(':'); | |
68 | var subTypeKey = subTypes.shift(); | |
69 | typeList.subTypes[subTypeKey] = {}; | |
70 | _.each(subTypes, function(subTypeId) { | |
71 | typeList.subTypes[subTypeKey][subTypeId] = true; | |
72 | }); | |
73 | }); | |
74 | } | |
75 | else { | |
76 | var subTypes = subTypesExpr.split(':'); | |
77 | var subTypeKey = subTypes.shift(); | |
78 | typeList.subTypes[subTypeKey] = {}; | |
79 | _.each(subTypes, function(subTypeId) { | |
80 | typeList.subTypes[subTypeKey][subTypeId] = true; | |
81 | }); | |
82 | } | |
6a488035 TO |
83 | } |
84 | return typeList; | |
85 | }; | |
86 | ||
87 | /** | |
88 | * This function is a hack for generating simulated values of "entity_name" | |
89 | * in the form-field model. | |
90 | * | |
91 | * @param {string} field_type | |
92 | * @return {string} | |
93 | */ | |
94 | CRM.UF.guessEntityName = function(field_type) { | |
95 | switch (field_type) { | |
96 | case 'Contact': | |
13e4fbdd | 97 | case 'Individual': |
bf86535e | 98 | case 'Organization': |
bf86535e | 99 | case 'Household': |
0103eaf0 | 100 | case 'Formatting': |
41fd8e26 | 101 | return 'contact_1'; |
6a488035 TO |
102 | case 'Activity': |
103 | return 'activity_1'; | |
104 | case 'Contribution': | |
105 | return 'contribution_1'; | |
106 | case 'Membership': | |
107 | return 'membership_1'; | |
108 | case 'Participant': | |
109 | return 'participant_1'; | |
cf1182e6 N |
110 | case 'Case': |
111 | return 'case_1'; | |
6a488035 | 112 | default: |
80eda9d1 | 113 | if (CRM.contactSubTypes.length && ($.inArray(field_type,CRM.contactSubTypes) > -1)) { |
21fced3b | 114 | return 'contact_1'; |
115 | } | |
116 | else { | |
117 | throw "Cannot guess entity name for field_type=" + field_type; | |
118 | } | |
6a488035 | 119 | } |
3e64165a | 120 | }; |
6a488035 TO |
121 | |
122 | /** | |
123 | * Represents a field in a customizable form. | |
124 | */ | |
125 | CRM.UF.UFFieldModel = CRM.Backbone.Model.extend({ | |
126 | /** | |
e4f46be0 | 127 | * Backbone.Form description of the field to which this refers |
6a488035 TO |
128 | */ |
129 | defaults: { | |
130 | help_pre: '', | |
131 | help_post: '', | |
132 | /** | |
133 | * @var bool, non-persistent indication of whether this field is unique or duplicate | |
134 | * within its UFFieldCollection | |
135 | */ | |
136 | is_duplicate: false | |
137 | }, | |
138 | schema: { | |
139 | 'id': { | |
140 | type: 'Number' | |
141 | }, | |
142 | 'uf_group_id': { | |
143 | type: 'Number' | |
144 | }, | |
145 | 'entity_name': { | |
146 | // pseudo-field | |
147 | type: 'Text' | |
148 | }, | |
149 | 'field_name': { | |
150 | type: 'Text' | |
151 | }, | |
152 | 'field_type': { | |
153 | type: 'Select', | |
154 | options: ['Contact', 'Individual', 'Organization', 'Contribution', 'Membership', 'Participant', 'Activity'] | |
155 | }, | |
156 | 'help_post': { | |
157 | title: addHelp(ts('Field Post Help'), {id: "help", file:"CRM/UF/Form/Field"}), | |
158 | type: 'TextArea' | |
159 | }, | |
160 | 'help_pre': { | |
161 | title: addHelp(ts('Field Pre Help'), {id: "help", file:"CRM/UF/Form/Field"}), | |
162 | type: 'TextArea' | |
163 | }, | |
164 | 'in_selector': { | |
165 | title: addHelp(ts('Results Columns?'), {id: "in_selector", file:"CRM/UF/Form/Field"}), | |
166 | type: 'Select', | |
167 | options: YESNO | |
168 | }, | |
169 | 'is_active': { | |
170 | title: addHelp(ts('Active?'), {id: "is_active", file:"CRM/UF/Form/Field"}), | |
171 | type: 'Select', | |
172 | options: YESNO | |
173 | }, | |
174 | 'is_multi_summary': { | |
175 | title: ts("Include in multi-record listing?"), | |
176 | type: 'Select', | |
177 | options: YESNO | |
178 | }, | |
179 | 'is_required': { | |
180 | title: addHelp(ts('Required?'), {id: "is_required", file:"CRM/UF/Form/Field"}), | |
181 | type: 'Select', | |
182 | options: YESNO | |
183 | }, | |
184 | 'is_reserved': { | |
185 | type: 'Select', | |
186 | options: YESNO | |
187 | }, | |
188 | 'is_searchable': { | |
189 | title: addHelp(ts("Searchable"), {id: "is_searchable", file:"CRM/UF/Form/Field"}), | |
190 | type: 'Select', | |
191 | options: YESNO | |
192 | }, | |
193 | 'is_view': { | |
194 | title: addHelp(ts('View Only?'), {id: "is_view", file:"CRM/UF/Form/Field"}), | |
195 | type: 'Select', | |
196 | options: YESNO | |
197 | }, | |
198 | 'label': { | |
199 | title: ts('Field Label'), | |
3a3663a4 CW |
200 | type: 'Text', |
201 | editorAttrs: {maxlength: 255} | |
6a488035 TO |
202 | }, |
203 | 'location_type_id': { | |
204 | title: ts('Location Type'), | |
205 | type: 'Select', | |
206 | options: LOCATION_TYPES | |
207 | }, | |
1a228b4c | 208 | 'website_type_id': { |
209 | title: ts('Website Type'), | |
210 | type: 'Select', | |
211 | options: WEBSITE_TYPES | |
212 | }, | |
6a488035 TO |
213 | 'phone_type_id': { |
214 | title: ts('Phone Type'), | |
215 | type: 'Select', | |
216 | options: PHONE_TYPES | |
217 | }, | |
218 | 'visibility': { | |
219 | title: addHelp(ts('Visibility'), {id: "visibility", file:"CRM/UF/Form/Field"}), | |
220 | type: 'Select', | |
221 | options: VISIBILITY | |
222 | }, | |
223 | 'weight': { | |
224 | type: 'Number' | |
225 | } | |
226 | }, | |
227 | initialize: function() { | |
0103eaf0 CW |
228 | if (this.get('field_name').indexOf('formatting') === 0) { |
229 | this.schema.help_pre.title = ts('Markup'); | |
230 | } | |
6a488035 TO |
231 | this.set('entity_name', CRM.UF.guessEntityName(this.get('field_type'))); |
232 | this.on("rel:ufGroupModel", this.applyDefaults, this); | |
233 | this.on('change', watchChanges); | |
234 | }, | |
235 | applyDefaults: function() { | |
236 | var fieldSchema = this.getFieldSchema(); | |
237 | if (fieldSchema && fieldSchema.civiIsLocation && !this.get('location_type_id')) { | |
238 | this.set('location_type_id', DEFAULT_LOCATION_TYPE_ID); | |
239 | } | |
1a228b4c | 240 | if (fieldSchema && fieldSchema.civiIsWebsite && !this.get('website_type_id')) { |
241 | this.set('website_type_id', DEFAULT_WEBSITE_TYPE_ID); | |
242 | } | |
6a488035 TO |
243 | if (fieldSchema && fieldSchema.civiIsPhone && !this.get('phone_type_id')) { |
244 | this.set('phone_type_id', DEFAULT_PHONE_TYPE_ID); | |
245 | } | |
246 | }, | |
247 | isInSelectorAllowed: function() { | |
248 | var visibility = _.first(_.where(VISIBILITY, {val: this.get('visibility')})); | |
c1358f4e | 249 | if (visibility) { |
250 | return visibility.isInSelectorAllowed; | |
251 | } | |
252 | else { | |
253 | return false; | |
254 | } | |
6a488035 TO |
255 | }, |
256 | getFieldSchema: function() { | |
257 | return this.getRel('ufGroupModel').getFieldSchema(this.get('entity_name'), this.get('field_name')); | |
258 | }, | |
259 | /** | |
260 | * Create a uniqueness signature. Ideally, each UFField in a UFGroup should | |
261 | * have a unique signature. | |
262 | * | |
263 | * @return {String} | |
264 | */ | |
265 | getSignature: function() { | |
3e64165a TO |
266 | return this.get("entity_name") + |
267 | '::' + this.get("field_name") + | |
1a228b4c | 268 | '::' + (this.get("location_type_id") ? this.get("location_type_id") : this.get("website_type_id") ? this.get("website_type_id") : '') + |
3e64165a | 269 | '::' + (this.get("phone_type_id") ? this.get("phone_type_id") : ''); |
6a488035 TO |
270 | }, |
271 | ||
272 | /** | |
273 | * This is like destroy(), but it only destroys the item on the client-side; | |
274 | * it does not trigger REST or Backbone.sync() operations. | |
275 | * | |
276 | * @return {Boolean} | |
277 | */ | |
278 | destroyLocal: function() { | |
279 | this.trigger('destroy', this, this.collection, {}); | |
280 | return false; | |
281 | } | |
282 | }); | |
283 | ||
284 | /** | |
285 | * Represents a list of fields in a customizable form | |
286 | * | |
287 | * options: | |
288 | * - uf_group_id: int | |
289 | */ | |
290 | CRM.UF.UFFieldCollection = CRM.Backbone.Collection.extend({ | |
291 | model: CRM.UF.UFFieldModel, | |
292 | uf_group_id: null, // int | |
293 | initialize: function(models, options) { | |
294 | options = options || {}; | |
295 | this.uf_group_id = options.uf_group_id; | |
296 | this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models); | |
297 | this.on('add', this.watchDuplicates, this); | |
298 | this.on('remove', this.unwatchDuplicates, this); | |
299 | this.on('change', watchChanges); | |
300 | this.on('add', watchChanges); | |
301 | this.on('remove', watchChanges); | |
302 | }, | |
303 | getFieldsByName: function(entityName, fieldName) { | |
304 | return this.filter(function(ufFieldModel) { | |
305 | return (ufFieldModel.get('entity_name') == entityName && ufFieldModel.get('field_name') == fieldName); | |
306 | }); | |
307 | }, | |
308 | toSortedJSON: function() { | |
309 | var fields = this.map(function(ufFieldModel){ | |
310 | return ufFieldModel.toStrictJSON(); | |
311 | }); | |
312 | return _.sortBy(fields, function(ufFieldJSON){ | |
313 | return parseInt(ufFieldJSON.weight); | |
314 | }); | |
315 | }, | |
316 | isAddable: function(ufFieldModel) { | |
317 | var entity_name = ufFieldModel.get('entity_name'), | |
318 | field_name = ufFieldModel.get('field_name'), | |
319 | fieldSchema = this.getRel('ufGroupModel').getFieldSchema(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name')); | |
0103eaf0 CW |
320 | if (field_name.indexOf('formatting') === 0) { |
321 | return true; | |
322 | } | |
6a488035 TO |
323 | if (! fieldSchema) { |
324 | return false; | |
325 | } | |
326 | var fields = this.getFieldsByName(entity_name, field_name); | |
327 | var limit = 1; | |
328 | if (fieldSchema.civiIsLocation) { | |
329 | limit *= LOCATION_TYPES.length; | |
330 | } | |
1a228b4c | 331 | if (fieldSchema.civiIsWebsite) { |
332 | limit *= WEBSITE_TYPES.length; | |
333 | } | |
6a488035 TO |
334 | if (fieldSchema.civiIsPhone) { |
335 | limit *= PHONE_TYPES.length; | |
336 | } | |
337 | return fields.length < limit; | |
338 | }, | |
339 | watchDuplicates: function(model, collection, options) { | |
340 | model.on('change:location_type_id', this.markDuplicates, this); | |
1a228b4c | 341 | model.on('change:website_type_id', this.markDuplicates, this); |
6a488035 TO |
342 | model.on('change:phone_type_id', this.markDuplicates, this); |
343 | this.markDuplicates(); | |
344 | }, | |
345 | unwatchDuplicates: function(model, collection, options) { | |
346 | model.off('change:location_type_id', this.markDuplicates, this); | |
1a228b4c | 347 | model.off('change:website_type_id', this.markDuplicates, this); |
6a488035 TO |
348 | model.off('change:phone_type_id', this.markDuplicates, this); |
349 | this.markDuplicates(); | |
350 | }, | |
351 | hasDuplicates: function() { | |
352 | var firstDupe = this.find(function(ufFieldModel){ | |
353 | return ufFieldModel.get('is_duplicate'); | |
354 | }); | |
355 | return firstDupe ? true : false; | |
356 | }, | |
357 | /** | |
358 | * | |
359 | */ | |
360 | markDuplicates: function() { | |
361 | var ufFieldModelsByKey = this.groupBy(function(ufFieldModel) { | |
362 | return ufFieldModel.getSignature(); | |
363 | }); | |
364 | this.each(function(ufFieldModel){ | |
365 | var is_duplicate = ufFieldModelsByKey[ufFieldModel.getSignature()].length > 1; | |
366 | if (is_duplicate != ufFieldModel.get('is_duplicate')) { | |
367 | ufFieldModel.set('is_duplicate', is_duplicate); | |
368 | } | |
369 | }); | |
370 | } | |
371 | }); | |
372 | ||
373 | /** | |
374 | * Represents an entity in a customizable form | |
375 | */ | |
376 | CRM.UF.UFEntityModel = CRM.Backbone.Model.extend({ | |
377 | schema: { | |
378 | 'id': { | |
379 | // title: ts(''), | |
380 | type: 'Number' | |
381 | }, | |
382 | 'entity_name': { | |
383 | title: ts('Entity Name'), | |
384 | help: ts('Symbolic name which referenced in the fields'), | |
385 | type: 'Text' | |
386 | }, | |
387 | 'entity_type': { | |
388 | title: ts('Entity Type'), | |
389 | type: 'Select', | |
390 | options: ['IndividualModel', 'ActivityModel'] | |
391 | }, | |
392 | 'entity_sub_type': { | |
393 | // Use '*' to match all subtypes; use an int to match a specific type id; use empty-string to match none | |
394 | title: ts('Sub Type'), | |
395 | type: 'Text' | |
396 | } | |
397 | }, | |
398 | defaults: { | |
399 | entity_sub_type: '*' | |
400 | }, | |
401 | initialize: function() { | |
402 | }, | |
403 | /** | |
404 | * Get a list of all fields that can be used with this entity. | |
405 | * | |
406 | * @return {Object} keys are field names; values are fieldSchemas | |
407 | */ | |
408 | getFieldSchemas: function() { | |
409 | var ufEntityModel = this; | |
410 | var modelClass= this.getModelClass(); | |
411 | ||
412 | if (this.get('entity_sub_type') == '*') { | |
413 | return _.clone(modelClass.prototype.schema); | |
414 | } | |
415 | ||
416 | var result = {}; | |
417 | _.each(modelClass.prototype.schema, function(fieldSchema, fieldName){ | |
418 | var section = modelClass.prototype.sections[fieldSchema.section]; | |
419 | if (ufEntityModel.isSectionEnabled(section)) { | |
420 | result[fieldName] = fieldSchema; | |
421 | } | |
422 | }); | |
423 | return result; | |
424 | }, | |
425 | isSectionEnabled: function(section) { | |
99e239bc | 426 | //CRM-15427 |
427 | return (!section || !section.extends_entity_column_value || _.contains(section.extends_entity_column_value, this.get('entity_sub_type')) || this.get('entity_sub_type') == '*'); | |
6a488035 TO |
428 | }, |
429 | getSections: function() { | |
430 | var ufEntityModel = this; | |
431 | var result = {}; | |
432 | _.each(ufEntityModel.getModelClass().prototype.sections, function(section, sectionKey){ | |
433 | if (ufEntityModel.isSectionEnabled(section)) { | |
434 | result[sectionKey] = section; | |
435 | } | |
436 | }); | |
437 | return result; | |
438 | }, | |
439 | getModelClass: function() { | |
440 | return CRM.Schema[this.get('entity_type')]; | |
441 | } | |
442 | }); | |
443 | ||
444 | /** | |
445 | * Represents a list of entities in a customizable form | |
446 | * | |
447 | * options: | |
448 | * - ufGroupModel: UFGroupModel | |
449 | */ | |
450 | CRM.UF.UFEntityCollection = CRM.Backbone.Collection.extend({ | |
451 | model: CRM.UF.UFEntityModel, | |
452 | byName: {}, | |
453 | initialize: function(models, options) { | |
454 | options = options || {}; | |
455 | this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models); | |
456 | }, | |
457 | /** | |
458 | * | |
459 | * @param name | |
460 | * @return {UFEntityModel} if found; otherwise, null | |
461 | */ | |
462 | getByName: function(name) { | |
463 | // TODO consider indexing | |
464 | return this.find(function(ufEntityModel){ | |
465 | return ufEntityModel.get('entity_name') == name; | |
466 | }); | |
467 | } | |
468 | }); | |
469 | ||
470 | /** | |
471 | * Represents a customizable form | |
472 | */ | |
473 | CRM.UF.UFGroupModel = CRM.Backbone.Model.extend({ | |
474 | defaults: { | |
d13aab87 | 475 | title: ts('Unnamed Profile'), |
6a488035 TO |
476 | is_active: 1 |
477 | }, | |
478 | schema: { | |
479 | 'id': { | |
480 | // title: ts(''), | |
481 | type: 'Number' | |
482 | }, | |
483 | 'name': { | |
484 | // title: ts(''), | |
485 | type: 'Text' | |
486 | }, | |
487 | 'title': { | |
488 | title: ts('Profile Name'), | |
489 | help: ts(''), | |
490 | type: 'Text', | |
3a3663a4 | 491 | editorAttrs: {maxlength: 64}, |
6a488035 TO |
492 | validators: ['required'] |
493 | }, | |
ce1a9db4 SL |
494 | 'frontend_title': { |
495 | title: ts('Public Title'), | |
496 | help: ts(''), | |
497 | type: 'Text', | |
498 | editorAttrs: {maxlength: 64}, | |
499 | validators: [] | |
500 | }, | |
6a488035 TO |
501 | 'group_type': { |
502 | // For a description of group_type, see CRM_Core_BAO_UFGroup::updateGroupTypes | |
503 | // title: ts(''), | |
504 | type: 'Text' | |
505 | }, | |
506 | 'add_captcha': { | |
507 | title: ts('Include reCAPTCHA?'), | |
508 | help: ts('FIXME'), | |
509 | type: 'Select', | |
510 | options: YESNO | |
511 | }, | |
512 | 'add_to_group_id': { | |
a9f9ea5a LS |
513 | title: ts('Add contacts to a group?'), |
514 | help: ts('Select a group if you want contacts to be automatically added to that group when the profile is submitted.'), | |
6a488035 TO |
515 | type: 'Number' |
516 | }, | |
517 | 'cancel_URL': { | |
518 | title: ts('Cancel Redirect URL'), | |
519 | help: ts('If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL if they click the Cancel button - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed.'), | |
520 | type: 'Text' | |
521 | }, | |
4aad2108 SL |
522 | 'cancel_button_text': { |
523 | title: ts('Cancel Button Text'), | |
524 | help: ts('Text to display on the cancel button when used in create or edit mode'), | |
525 | type: 'Text' | |
526 | }, | |
527 | 'submit_button_text': { | |
528 | title: ts('Submit Button Text'), | |
529 | help: ts('Text to display on the submit button when used in create or edit mode'), | |
530 | type: 'Text' | |
531 | }, | |
6a488035 TO |
532 | 'created_date': { |
533 | //title: ts(''), | |
534 | type: 'Text'// FIXME | |
535 | }, | |
536 | 'created_id': { | |
537 | //title: ts(''), | |
538 | type: 'Number' | |
539 | }, | |
540 | 'help_post': { | |
541 | title: ts('Post-form Help'), | |
3e64165a TO |
542 | help: ts('Explanatory text displayed at the end of the form.') + |
543 | ts('Note that this help text is displayed on profile create/edit screens only.'), | |
6a488035 TO |
544 | type: 'TextArea' |
545 | }, | |
546 | 'help_pre': { | |
47775bc9 | 547 | title: ts('Pre-form Help'), |
3e64165a TO |
548 | help: ts('Explanatory text displayed at the beginning of the form.') + |
549 | ts('Note that this help text is displayed on profile create/edit screens only.'), | |
6a488035 TO |
550 | type: 'TextArea' |
551 | }, | |
552 | 'is_active': { | |
553 | title: ts('Is this CiviCRM Profile active?'), | |
554 | type: 'Select', | |
555 | options: YESNO | |
556 | }, | |
557 | 'is_cms_user': { | |
558 | title: ts('Drupal user account registration option?'),// FIXME | |
559 | help: ts('FIXME'), | |
560 | type: 'Select', | |
561 | options: YESNO // FIXME | |
562 | }, | |
563 | 'is_edit_link': { | |
564 | title: ts('Include profile edit links in search results?'), | |
565 | help: ts('Check this box if you want to include a link in the listings to Edit profile fields. Only users with permission to edit the contact will see this link.'), | |
566 | type: 'Select', | |
567 | options: YESNO | |
568 | }, | |
569 | 'is_map': { | |
570 | title: ts('Enable mapping for this profile?'), | |
571 | help: ts('If enabled, a Map link is included on the profile listings rows and detail screens for any contacts whose records include sufficient location data for your mapping provider.'), | |
572 | type: 'Select', | |
573 | options: YESNO | |
574 | }, | |
575 | 'is_proximity_search': { | |
8e3d52a4 | 576 | title: ts('Proximity Search'), |
6a488035 TO |
577 | help: ts('FIXME'), |
578 | type: 'Select', | |
579 | options: YESNO // FIXME | |
580 | }, | |
581 | 'is_reserved': { | |
582 | // title: ts(''), | |
583 | type: 'Select', | |
584 | options: YESNO | |
585 | }, | |
586 | 'is_uf_link': { | |
587 | title: ts('Include Drupal user account information links in search results?'), // FIXME | |
588 | help: ts('FIXME'), | |
589 | type: 'Select', | |
590 | options: YESNO | |
591 | }, | |
592 | 'is_update_dupe': { | |
593 | title: ts('What to do upon duplicate match'), | |
594 | help: ts('FIXME'), | |
595 | type: 'Select', | |
596 | options: YESNO // FIXME | |
597 | }, | |
598 | 'limit_listings_group_id': { | |
599 | title: ts('Limit listings to a specific Group?'), | |
600 | help: ts('Select a group if you are using this profile for search and listings, AND you want to limit the listings to members of a specific group.'), | |
601 | type: 'Number' | |
602 | }, | |
603 | 'notify': { | |
604 | title: ts('Notify when profile form is submitted?'), | |
605 | help: ts('If you want member(s) of your organization to receive a notification email whenever this Profile form is used to enter or update contact information, enter one or more email addresses here. Multiple email addresses should be separated by a comma (e.g. jane@example.org, paula@example.org). The first email address listed will be used as the FROM address in the notifications.'), | |
606 | type: 'TextArea' | |
607 | }, | |
608 | 'post_URL': { | |
609 | title: ts('Redirect URL'), | |
610 | help: ts("If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL after they've submitted the form, you can also use contact tokens in URL - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed with a generic status message - 'Your contact information has been saved.'"), | |
611 | type: 'Text' | |
612 | }, | |
613 | 'weight': { | |
614 | title: ts('Order'), | |
615 | help: ts('Weight controls the order in which profiles are presented when more than one profile is included in User Registration or My Account screens. Enter a positive or negative integer - lower numbers are displayed ahead of higher numbers.'), | |
616 | type: 'Number' | |
617 | // FIXME positive int | |
618 | } | |
619 | }, | |
620 | initialize: function() { | |
621 | var ufGroupModel = this; | |
622 | ||
623 | if (!this.getRel('ufEntityCollection')) { | |
624 | var ufEntityCollection = new CRM.UF.UFEntityCollection([], { | |
625 | ufGroupModel: this, | |
626 | silent: false | |
627 | }); | |
628 | this.setRel('ufEntityCollection', ufEntityCollection); | |
629 | } | |
630 | ||
631 | if (!this.getRel('ufFieldCollection')) { | |
632 | var ufFieldCollection = new CRM.UF.UFFieldCollection([], { | |
633 | uf_group_id: this.id, | |
634 | ufGroupModel: this | |
635 | }); | |
636 | this.setRel('ufFieldCollection', ufFieldCollection); | |
637 | } | |
638 | ||
639 | if (!this.getRel('paletteFieldCollection')) { | |
640 | var paletteFieldCollection = new CRM.Designer.PaletteFieldCollection([], { | |
641 | ufGroupModel: this | |
642 | }); | |
643 | paletteFieldCollection.sync = function(method, model, options) { | |
3e64165a | 644 | if (!options) options = {}; |
6a488035 TO |
645 | // console.log(method, model, options); |
646 | switch (method) { | |
647 | case 'read': | |
648 | var success = options.success; | |
649 | options.success = function(resp, status, xhr) { | |
650 | if (success) success(resp, status, xhr); | |
651 | model.trigger('sync', model, resp, options); | |
652 | }; | |
653 | success(ufGroupModel.buildPaletteFields()); | |
654 | ||
655 | break; | |
656 | case 'create': | |
657 | case 'update': | |
658 | case 'delete': | |
fb079bbf TO |
659 | throw 'Unsupported method: ' + method; |
660 | ||
6a488035 TO |
661 | default: |
662 | throw 'Unsupported method: ' + method; | |
663 | } | |
664 | }; | |
665 | this.setRel('paletteFieldCollection', paletteFieldCollection); | |
666 | } | |
667 | ||
668 | this.getRel('ufEntityCollection').on('reset', this.resetEntities, this); | |
669 | this.resetEntities(); | |
670 | ||
671 | this.on('change', watchChanges); | |
672 | }, | |
673 | /** | |
674 | * Generate a copy of this UFGroupModel and its fields, with all ID's removed. The result | |
675 | * is suitable for a new, identical UFGroup. | |
676 | * | |
677 | * @return {CRM.UF.UFGroupModel} | |
678 | */ | |
679 | deepCopy: function() { | |
680 | var copy = new CRM.UF.UFGroupModel(_.omit(this.toStrictJSON(), ['id','created_id','created_date','is_reserved','group_type'])); | |
681 | copy.getRel('ufEntityCollection').reset( | |
682 | this.getRel('ufEntityCollection').toJSON() | |
683 | // FIXME: for configurable entities, omit ['id', 'uf_group_id'] | |
684 | ); | |
685 | copy.getRel('ufFieldCollection').reset( | |
686 | this.getRel('ufFieldCollection').map(function(ufFieldModel) { | |
687 | return _.omit(ufFieldModel.toStrictJSON(), ['id', 'uf_group_id']); | |
688 | }) | |
689 | ); | |
e2beed76 SL |
690 | var new_id = 1; |
691 | CRM.api3('UFGroup', 'getsingle', { | |
692 | "return": ["id"], | |
693 | "options": {"limit": 1, "sort": "id DESC"} | |
694 | }).done(function(result) { | |
695 | new_id = Number(result.id) + 1; | |
696 | var copyLabel = ' ' + ts('(Copy)'); | |
697 | var nameSuffix = '_' + new_id; | |
698 | copy.set('title', copy.get('title').slice(0, 64 - copyLabel.length) + copyLabel); | |
699 | copy.set('name', copy.get('name').slice(0, 64 - nameSuffix.length) + nameSuffix); | |
700 | }); | |
6a488035 TO |
701 | return copy; |
702 | }, | |
703 | getModelClass: function(entity_name) { | |
704 | var ufEntity = this.getRel('ufEntityCollection').getByName(entity_name); | |
705 | if (!ufEntity) throw 'Failed to locate entity: ' + entity_name; | |
706 | return ufEntity.getModelClass(); | |
707 | }, | |
708 | getFieldSchema: function(entity_name, field_name) { | |
0103eaf0 CW |
709 | if (field_name.indexOf('formatting') === 0) { |
710 | field_name = 'formatting'; | |
711 | } | |
6a488035 TO |
712 | var modelClass = this.getModelClass(entity_name); |
713 | var fieldSchema = modelClass.prototype.schema[field_name]; | |
714 | if (!fieldSchema) { | |
0103eaf0 | 715 | CRM.console('warn', 'Failed to locate field: ' + entity_name + "." + field_name); |
6a488035 TO |
716 | return null; |
717 | } | |
718 | return fieldSchema; | |
719 | }, | |
720 | /** | |
721 | * Check that the group_type contains *only* the types listed in validTypes | |
722 | * | |
723 | * @param string validTypesExpr | |
99e239bc | 724 | * @param bool allowAllSubtypes |
6a488035 TO |
725 | * @return {Boolean} |
726 | */ | |
99e239bc | 727 | //CRM-15427 |
37375016 | 728 | checkGroupType: function(validTypesExpr, allowAllSubtypes, usedByFilter) { |
6a488035 | 729 | var allMatched = true; |
f80ef0e2 | 730 | allowAllSubtypes = allowAllSubtypes || false; |
37375016 | 731 | usedByFilter = usedByFilter || null; |
fb079bbf | 732 | if (_.isEmpty(this.get('group_type'))) { |
6a488035 TO |
733 | return true; |
734 | } | |
37375016 | 735 | if (usedByFilter && _.isEmpty(this.get('module'))) { |
736 | return false; | |
737 | } | |
6a488035 TO |
738 | |
739 | var actualTypes = CRM.UF.parseTypeList(this.get('group_type')); | |
740 | var validTypes = CRM.UF.parseTypeList(validTypesExpr); | |
741 | ||
742 | // Every actual.coreType is a valid.coreType | |
743 | _.each(actualTypes.coreTypes, function(ignore, actualCoreType) { | |
744 | if (! validTypes.coreTypes[actualCoreType]) { | |
745 | allMatched = false; | |
746 | } | |
747 | }); | |
748 | ||
58770077 | 749 | // CRM-16915 - filter with usedBy module if specified. |
37375016 | 750 | if (usedByFilter && this.get('module') != usedByFilter) { |
751 | allMatched = false; | |
752 | } | |
99e239bc | 753 | //CRM-15427 allow all subtypes |
754 | if (!$.isEmptyObject(validTypes.subTypes) && !allowAllSubtypes) { | |
755 | // Every actual.subType is a valid.subType | |
756 | _.each(actualTypes.subTypes, function(actualSubTypeIds, actualSubTypeKey) { | |
757 | if (!validTypes.subTypes[actualSubTypeKey]) { | |
758 | allMatched = false; | |
759 | return; | |
6a488035 | 760 | } |
99e239bc | 761 | // actualSubTypeIds is a list of all subtypes which can be used by group, |
762 | // so it's sufficient to match any one of them | |
763 | var subTypeMatched = false; | |
764 | _.each(actualSubTypeIds, function(ignore, actualSubTypeId) { | |
765 | if (validTypes.subTypes[actualSubTypeKey][actualSubTypeId]) { | |
766 | subTypeMatched = true; | |
767 | } | |
768 | }); | |
769 | allMatched = allMatched && subTypeMatched; | |
6a488035 | 770 | }); |
99e239bc | 771 | } |
6a488035 TO |
772 | return allMatched; |
773 | }, | |
d35c2913 | 774 | calculateContactEntityType: function() { |
06575e57 | 775 | var ufGroupModel = this; |
776 | ||
777 | // set proper entity model based on selected profile | |
778 | var contactTypes = ['Individual', 'Household', 'Organization']; | |
45609fb1 | 779 | var profileType = ufGroupModel.get('group_type') || ''; |
752ddf35 N |
780 | |
781 | // check if selected profile have subtype defined eg: ["Individual,Contact,Case", "caseType:7"] | |
782 | if (_.isArray(profileType) && profileType[0]) { | |
783 | profileType = profileType[0]; | |
784 | } | |
06575e57 | 785 | profileType = profileType.split(','); |
cf1182e6 | 786 | |
06575e57 | 787 | var ufEntityModel; |
788 | _.each(profileType, function (ptype) { | |
789 | if ($.inArray(ptype, contactTypes) > -1) { | |
f34dad55 | 790 | ufEntityModel = ptype + 'Model'; |
06575e57 | 791 | return true; |
792 | } | |
793 | }); | |
794 | ||
795 | return ufEntityModel; | |
796 | }, | |
f34dad55 | 797 | setUFGroupModel: function(entityType, allEntityModels) { |
798 | var ufGroupModel = this; | |
799 | ||
800 | var newUfEntityModels = []; | |
801 | _.each(allEntityModels, function (values) { | |
752ddf35 | 802 | if (entityType && values.entity_name == 'contact_1') { |
f34dad55 | 803 | values.entity_type = entityType; |
804 | } | |
805 | newUfEntityModels.push(new CRM.UF.UFEntityModel(values)); | |
806 | }); | |
807 | ||
808 | ufGroupModel.getRel('ufEntityCollection').reset(newUfEntityModels); | |
809 | }, | |
6a488035 TO |
810 | resetEntities: function() { |
811 | var ufGroupModel = this; | |
41fd8e26 | 812 | var deleteFieldList = []; |
6a488035 TO |
813 | ufGroupModel.getRel('ufFieldCollection').each(function(ufFieldModel){ |
814 | if (!ufFieldModel.getFieldSchema()) { | |
41fd8e26 | 815 | CRM.alert(ts('This profile no longer includes field "%1"! All references to the field have been removed.', { |
816 | 1: ufFieldModel.get('label') | |
6a488035 | 817 | }), '', 'alert', {expires: false}); |
41fd8e26 | 818 | deleteFieldList.push(ufFieldModel); |
6a488035 TO |
819 | } |
820 | }); | |
41fd8e26 | 821 | |
822 | _.each(deleteFieldList, function(ufFieldModel) { | |
823 | ufFieldModel.destroyLocal(); | |
824 | }); | |
825 | ||
6a488035 | 826 | this.getRel('paletteFieldCollection').reset(this.buildPaletteFields()); |
41fd8e26 | 827 | |
828 | // reset to redraw the cancel after entity type is updated. | |
829 | ufGroupModel.getRel('ufFieldCollection').reset(ufGroupModel.getRel('ufFieldCollection').toJSON()); | |
6a488035 TO |
830 | }, |
831 | /** | |
832 | * | |
833 | * @return {Array} of PaletteFieldModel | |
834 | */ | |
835 | buildPaletteFields: function() { | |
836 | // rebuild list of fields; reuse old instances of PaletteFieldModel and create new ones | |
837 | // as appropriate | |
838 | // Note: The system as a whole is ill-defined in cases where we have an existing | |
839 | // UFField that references a model field that disappears. | |
840 | ||
841 | var ufGroupModel = this; | |
842 | ||
843 | var oldPaletteFieldModelsBySig = {}; | |
844 | this.getRel('paletteFieldCollection').each(function(paletteFieldModel){ | |
845 | oldPaletteFieldModelsBySig[paletteFieldModel.get("entityName") + '::' + paletteFieldModel.get("fieldName")] = paletteFieldModel; | |
846 | }); | |
847 | ||
848 | var newPaletteFieldModels = []; | |
849 | this.getRel('ufEntityCollection').each(function(ufEntityModel){ | |
850 | var modelClass = ufEntityModel.getModelClass(); | |
851 | _.each(ufEntityModel.getFieldSchemas(), function(value, key, list) { | |
852 | var model = oldPaletteFieldModelsBySig[ufEntityModel.get('entity_name') + '::' + key]; | |
853 | if (!model) { | |
854 | model = new CRM.Designer.PaletteFieldModel({ | |
855 | modelClass: modelClass, | |
856 | entityName: ufEntityModel.get('entity_name'), | |
857 | fieldName: key | |
858 | }); | |
859 | } | |
860 | newPaletteFieldModels.push(model); | |
861 | }); | |
862 | }); | |
863 | ||
864 | return newPaletteFieldModels; | |
865 | } | |
866 | }); | |
867 | ||
868 | /** | |
869 | * Represents a list of customizable form | |
870 | */ | |
871 | CRM.UF.UFGroupCollection = CRM.Backbone.Collection.extend({ | |
872 | model: CRM.UF.UFGroupModel | |
873 | }); | |
4b513f23 | 874 | })(CRM.$, CRM._); |