reverted changes
[civicrm-core.git] / js / model / crm.uf.js
1 (function($, _) {
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},
11 {val: 'Public Pages', label: ts('Public Pages'), isInSelectorAllowed: true},
12 {val: 'Public Pages and Listings', label: ts('Public Pages and Listings'), isInSelectorAllowed: true}
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 });
24
25 var WEBSITE_TYPES = _.map(CRM.PseudoConstant.websiteType, function(value, key) {
26 return {val: key, label: value};
27 });
28 var DEFAULT_PHONE_TYPE_ID = PHONE_TYPES[0].val;
29 var DEFAULT_WEBSITE_TYPE_ID = WEBSITE_TYPES[0].val;
30
31 /**
32 * Add a help link to a form label
33 */
34 function addHelp(title, options) {
35 return title + ' <a href="#" onclick=\'CRM.help("' + title + '", ' + JSON.stringify(options) + '); return false;\' title="' + ts('%1 Help', {1: title}) + '" class="helpicon"></a>';
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 "&#00;" != 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
56 if (!_.isEmpty(coreTypesExpr)) {
57 _.each(coreTypesExpr.split(','), function(coreType){
58 typeList.coreTypes[coreType] = true;
59 });
60 }
61
62 //CRM-15427 Allow Multiple subtype filtering
63 if (!_.isEmpty(subTypesExpr)) {
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 }
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':
97 case 'Individual':
98 case 'Organization':
99 case 'Household':
100 case 'Formatting':
101 return 'contact_1';
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';
110 case 'Case':
111 return 'case_1';
112 default:
113 if (!$.isEmptyObject(CRM.contactSubTypes) && ($.inArray(field_type,CRM.contactSubTypes) > -1)) {
114 return 'contact_1';
115 }
116 else {
117 throw "Cannot guess entity name for field_type=" + field_type;
118 }
119 }
120 };
121
122 /**
123 * Represents a field in a customizable form.
124 */
125 CRM.UF.UFFieldModel = CRM.Backbone.Model.extend({
126 /**
127 * Backbone.Form description of the field to which this refers
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'),
200 type: 'Text'
201 },
202 'location_type_id': {
203 title: ts('Location Type'),
204 type: 'Select',
205 options: LOCATION_TYPES
206 },
207 'website_type_id': {
208 title: ts('Website Type'),
209 type: 'Select',
210 options: WEBSITE_TYPES
211 },
212 'phone_type_id': {
213 title: ts('Phone Type'),
214 type: 'Select',
215 options: PHONE_TYPES
216 },
217 'visibility': {
218 title: addHelp(ts('Visibility'), {id: "visibility", file:"CRM/UF/Form/Field"}),
219 type: 'Select',
220 options: VISIBILITY
221 },
222 'weight': {
223 type: 'Number'
224 }
225 },
226 initialize: function() {
227 if (this.get('field_name').indexOf('formatting') === 0) {
228 this.schema.help_pre.title = ts('Markup');
229 }
230 this.set('entity_name', CRM.UF.guessEntityName(this.get('field_type')));
231 this.on("rel:ufGroupModel", this.applyDefaults, this);
232 this.on('change', watchChanges);
233 },
234 applyDefaults: function() {
235 var fieldSchema = this.getFieldSchema();
236 if (fieldSchema && fieldSchema.civiIsLocation && !this.get('location_type_id')) {
237 this.set('location_type_id', DEFAULT_LOCATION_TYPE_ID);
238 }
239 if (fieldSchema && fieldSchema.civiIsWebsite && !this.get('website_type_id')) {
240 this.set('website_type_id', DEFAULT_WEBSITE_TYPE_ID);
241 }
242 if (fieldSchema && fieldSchema.civiIsPhone && !this.get('phone_type_id')) {
243 this.set('phone_type_id', DEFAULT_PHONE_TYPE_ID);
244 }
245 },
246 isInSelectorAllowed: function() {
247 var visibility = _.first(_.where(VISIBILITY, {val: this.get('visibility')}));
248 if (visibility) {
249 return visibility.isInSelectorAllowed;
250 }
251 else {
252 return false;
253 }
254 },
255 getFieldSchema: function() {
256 return this.getRel('ufGroupModel').getFieldSchema(this.get('entity_name'), this.get('field_name'));
257 },
258 /**
259 * Create a uniqueness signature. Ideally, each UFField in a UFGroup should
260 * have a unique signature.
261 *
262 * @return {String}
263 */
264 getSignature: function() {
265 return this.get("entity_name") +
266 '::' + this.get("field_name") +
267 '::' + (this.get("location_type_id") ? this.get("location_type_id") : this.get("website_type_id") ? this.get("website_type_id") : '') +
268 '::' + (this.get("phone_type_id") ? this.get("phone_type_id") : '');
269 },
270
271 /**
272 * This is like destroy(), but it only destroys the item on the client-side;
273 * it does not trigger REST or Backbone.sync() operations.
274 *
275 * @return {Boolean}
276 */
277 destroyLocal: function() {
278 this.trigger('destroy', this, this.collection, {});
279 return false;
280 }
281 });
282
283 /**
284 * Represents a list of fields in a customizable form
285 *
286 * options:
287 * - uf_group_id: int
288 */
289 CRM.UF.UFFieldCollection = CRM.Backbone.Collection.extend({
290 model: CRM.UF.UFFieldModel,
291 uf_group_id: null, // int
292 initialize: function(models, options) {
293 options = options || {};
294 this.uf_group_id = options.uf_group_id;
295 this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
296 this.on('add', this.watchDuplicates, this);
297 this.on('remove', this.unwatchDuplicates, this);
298 this.on('change', watchChanges);
299 this.on('add', watchChanges);
300 this.on('remove', watchChanges);
301 },
302 getFieldsByName: function(entityName, fieldName) {
303 return this.filter(function(ufFieldModel) {
304 return (ufFieldModel.get('entity_name') == entityName && ufFieldModel.get('field_name') == fieldName);
305 });
306 },
307 toSortedJSON: function() {
308 var fields = this.map(function(ufFieldModel){
309 return ufFieldModel.toStrictJSON();
310 });
311 return _.sortBy(fields, function(ufFieldJSON){
312 return parseInt(ufFieldJSON.weight);
313 });
314 },
315 isAddable: function(ufFieldModel) {
316 var entity_name = ufFieldModel.get('entity_name'),
317 field_name = ufFieldModel.get('field_name'),
318 fieldSchema = this.getRel('ufGroupModel').getFieldSchema(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
319 if (field_name.indexOf('formatting') === 0) {
320 return true;
321 }
322 if (! fieldSchema) {
323 return false;
324 }
325 var fields = this.getFieldsByName(entity_name, field_name);
326 var limit = 1;
327 if (fieldSchema.civiIsLocation) {
328 limit *= LOCATION_TYPES.length;
329 }
330 if (fieldSchema.civiIsWebsite) {
331 limit *= WEBSITE_TYPES.length;
332 }
333 if (fieldSchema.civiIsPhone) {
334 limit *= PHONE_TYPES.length;
335 }
336 return fields.length < limit;
337 },
338 watchDuplicates: function(model, collection, options) {
339 model.on('change:location_type_id', this.markDuplicates, this);
340 model.on('change:website_type_id', this.markDuplicates, this);
341 model.on('change:phone_type_id', this.markDuplicates, this);
342 this.markDuplicates();
343 },
344 unwatchDuplicates: function(model, collection, options) {
345 model.off('change:location_type_id', this.markDuplicates, this);
346 model.off('change:website_type_id', this.markDuplicates, this);
347 model.off('change:phone_type_id', this.markDuplicates, this);
348 this.markDuplicates();
349 },
350 hasDuplicates: function() {
351 var firstDupe = this.find(function(ufFieldModel){
352 return ufFieldModel.get('is_duplicate');
353 });
354 return firstDupe ? true : false;
355 },
356 /**
357 *
358 */
359 markDuplicates: function() {
360 var ufFieldModelsByKey = this.groupBy(function(ufFieldModel) {
361 return ufFieldModel.getSignature();
362 });
363 this.each(function(ufFieldModel){
364 var is_duplicate = ufFieldModelsByKey[ufFieldModel.getSignature()].length > 1;
365 if (is_duplicate != ufFieldModel.get('is_duplicate')) {
366 ufFieldModel.set('is_duplicate', is_duplicate);
367 }
368 });
369 }
370 });
371
372 /**
373 * Represents an entity in a customizable form
374 */
375 CRM.UF.UFEntityModel = CRM.Backbone.Model.extend({
376 schema: {
377 'id': {
378 // title: ts(''),
379 type: 'Number'
380 },
381 'entity_name': {
382 title: ts('Entity Name'),
383 help: ts('Symbolic name which referenced in the fields'),
384 type: 'Text'
385 },
386 'entity_type': {
387 title: ts('Entity Type'),
388 type: 'Select',
389 options: ['IndividualModel', 'ActivityModel']
390 },
391 'entity_sub_type': {
392 // Use '*' to match all subtypes; use an int to match a specific type id; use empty-string to match none
393 title: ts('Sub Type'),
394 type: 'Text'
395 }
396 },
397 defaults: {
398 entity_sub_type: '*'
399 },
400 initialize: function() {
401 },
402 /**
403 * Get a list of all fields that can be used with this entity.
404 *
405 * @return {Object} keys are field names; values are fieldSchemas
406 */
407 getFieldSchemas: function() {
408 var ufEntityModel = this;
409 var modelClass= this.getModelClass();
410
411 if (this.get('entity_sub_type') == '*') {
412 return _.clone(modelClass.prototype.schema);
413 }
414
415 var result = {};
416 _.each(modelClass.prototype.schema, function(fieldSchema, fieldName){
417 var section = modelClass.prototype.sections[fieldSchema.section];
418 if (ufEntityModel.isSectionEnabled(section)) {
419 result[fieldName] = fieldSchema;
420 }
421 });
422 return result;
423 },
424 isSectionEnabled: function(section) {
425 //CRM-15427
426 return (!section || !section.extends_entity_column_value || _.contains(section.extends_entity_column_value, this.get('entity_sub_type')) || this.get('entity_sub_type') == '*');
427 },
428 getSections: function() {
429 var ufEntityModel = this;
430 var result = {};
431 _.each(ufEntityModel.getModelClass().prototype.sections, function(section, sectionKey){
432 if (ufEntityModel.isSectionEnabled(section)) {
433 result[sectionKey] = section;
434 }
435 });
436 return result;
437 },
438 getModelClass: function() {
439 return CRM.Schema[this.get('entity_type')];
440 }
441 });
442
443 /**
444 * Represents a list of entities in a customizable form
445 *
446 * options:
447 * - ufGroupModel: UFGroupModel
448 */
449 CRM.UF.UFEntityCollection = CRM.Backbone.Collection.extend({
450 model: CRM.UF.UFEntityModel,
451 byName: {},
452 initialize: function(models, options) {
453 options = options || {};
454 this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
455 },
456 /**
457 *
458 * @param name
459 * @return {UFEntityModel} if found; otherwise, null
460 */
461 getByName: function(name) {
462 // TODO consider indexing
463 return this.find(function(ufEntityModel){
464 return ufEntityModel.get('entity_name') == name;
465 });
466 }
467 });
468
469 /**
470 * Represents a customizable form
471 */
472 CRM.UF.UFGroupModel = CRM.Backbone.Model.extend({
473 defaults: {
474 title: ts('Unnamed Profile'),
475 is_active: 1
476 },
477 schema: {
478 'id': {
479 // title: ts(''),
480 type: 'Number'
481 },
482 'name': {
483 // title: ts(''),
484 type: 'Text'
485 },
486 'title': {
487 title: ts('Profile Name'),
488 help: ts(''),
489 type: 'Text',
490 validators: ['required']
491 },
492 'group_type': {
493 // For a description of group_type, see CRM_Core_BAO_UFGroup::updateGroupTypes
494 // title: ts(''),
495 type: 'Text'
496 },
497 'add_captcha': {
498 title: ts('Include reCAPTCHA?'),
499 help: ts('FIXME'),
500 type: 'Select',
501 options: YESNO
502 },
503 'add_to_group_id': {
504 title: ts('Add new contacts to a Group?'),
505 help: ts('Select a group if you are using this profile for adding new contacts, AND you want the new contacts to be automatically assigned to a group.'),
506 type: 'Number'
507 },
508 'cancel_URL': {
509 title: ts('Cancel Redirect URL'),
510 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.'),
511 type: 'Text'
512 },
513 'created_date': {
514 //title: ts(''),
515 type: 'Text'// FIXME
516 },
517 'created_id': {
518 //title: ts(''),
519 type: 'Number'
520 },
521 'help_post': {
522 title: ts('Post-form Help'),
523 help: ts('Explanatory text displayed at the end of the form.') +
524 ts('Note that this help text is displayed on profile create/edit screens only.'),
525 type: 'TextArea'
526 },
527 'help_pre': {
528 title: ts('Pre-form Help'),
529 help: ts('Explanatory text displayed at the beginning of the form.') +
530 ts('Note that this help text is displayed on profile create/edit screens only.'),
531 type: 'TextArea'
532 },
533 'is_active': {
534 title: ts('Is this CiviCRM Profile active?'),
535 type: 'Select',
536 options: YESNO
537 },
538 'is_cms_user': {
539 title: ts('Drupal user account registration option?'),// FIXME
540 help: ts('FIXME'),
541 type: 'Select',
542 options: YESNO // FIXME
543 },
544 'is_edit_link': {
545 title: ts('Include profile edit links in search results?'),
546 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.'),
547 type: 'Select',
548 options: YESNO
549 },
550 'is_map': {
551 title: ts('Enable mapping for this profile?'),
552 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.'),
553 type: 'Select',
554 options: YESNO
555 },
556 'is_proximity_search': {
557 title: ts('Proximity Search'),
558 help: ts('FIXME'),
559 type: 'Select',
560 options: YESNO // FIXME
561 },
562 'is_reserved': {
563 // title: ts(''),
564 type: 'Select',
565 options: YESNO
566 },
567 'is_uf_link': {
568 title: ts('Include Drupal user account information links in search results?'), // FIXME
569 help: ts('FIXME'),
570 type: 'Select',
571 options: YESNO
572 },
573 'is_update_dupe': {
574 title: ts('What to do upon duplicate match'),
575 help: ts('FIXME'),
576 type: 'Select',
577 options: YESNO // FIXME
578 },
579 'limit_listings_group_id': {
580 title: ts('Limit listings to a specific Group?'),
581 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.'),
582 type: 'Number'
583 },
584 'notify': {
585 title: ts('Notify when profile form is submitted?'),
586 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.'),
587 type: 'TextArea'
588 },
589 'post_URL': {
590 title: ts('Redirect URL'),
591 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.'"),
592 type: 'Text'
593 },
594 'weight': {
595 title: ts('Order'),
596 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.'),
597 type: 'Number'
598 // FIXME positive int
599 }
600 },
601 initialize: function() {
602 var ufGroupModel = this;
603
604 if (!this.getRel('ufEntityCollection')) {
605 var ufEntityCollection = new CRM.UF.UFEntityCollection([], {
606 ufGroupModel: this,
607 silent: false
608 });
609 this.setRel('ufEntityCollection', ufEntityCollection);
610 }
611
612 if (!this.getRel('ufFieldCollection')) {
613 var ufFieldCollection = new CRM.UF.UFFieldCollection([], {
614 uf_group_id: this.id,
615 ufGroupModel: this
616 });
617 this.setRel('ufFieldCollection', ufFieldCollection);
618 }
619
620 if (!this.getRel('paletteFieldCollection')) {
621 var paletteFieldCollection = new CRM.Designer.PaletteFieldCollection([], {
622 ufGroupModel: this
623 });
624 paletteFieldCollection.sync = function(method, model, options) {
625 if (!options) options = {};
626 // console.log(method, model, options);
627 switch (method) {
628 case 'read':
629 var success = options.success;
630 options.success = function(resp, status, xhr) {
631 if (success) success(resp, status, xhr);
632 model.trigger('sync', model, resp, options);
633 };
634 success(ufGroupModel.buildPaletteFields());
635
636 break;
637 case 'create':
638 case 'update':
639 case 'delete':
640 throw 'Unsupported method: ' + method;
641
642 default:
643 throw 'Unsupported method: ' + method;
644 }
645 };
646 this.setRel('paletteFieldCollection', paletteFieldCollection);
647 }
648
649 this.getRel('ufEntityCollection').on('reset', this.resetEntities, this);
650 this.resetEntities();
651
652 this.on('change', watchChanges);
653 },
654 /**
655 * Generate a copy of this UFGroupModel and its fields, with all ID's removed. The result
656 * is suitable for a new, identical UFGroup.
657 *
658 * @return {CRM.UF.UFGroupModel}
659 */
660 deepCopy: function() {
661 var copy = new CRM.UF.UFGroupModel(_.omit(this.toStrictJSON(), ['id','created_id','created_date','is_reserved','group_type']));
662 copy.getRel('ufEntityCollection').reset(
663 this.getRel('ufEntityCollection').toJSON()
664 // FIXME: for configurable entities, omit ['id', 'uf_group_id']
665 );
666 copy.getRel('ufFieldCollection').reset(
667 this.getRel('ufFieldCollection').map(function(ufFieldModel) {
668 return _.omit(ufFieldModel.toStrictJSON(), ['id', 'uf_group_id']);
669 })
670 );
671 copy.set('title', ts('%1 (Copy)', {
672 1: copy.get('title')
673 }));
674 return copy;
675 },
676 getModelClass: function(entity_name) {
677 var ufEntity = this.getRel('ufEntityCollection').getByName(entity_name);
678 if (!ufEntity) throw 'Failed to locate entity: ' + entity_name;
679 return ufEntity.getModelClass();
680 },
681 getFieldSchema: function(entity_name, field_name) {
682 if (field_name.indexOf('formatting') === 0) {
683 field_name = 'formatting';
684 }
685 var modelClass = this.getModelClass(entity_name);
686 var fieldSchema = modelClass.prototype.schema[field_name];
687 if (!fieldSchema) {
688 CRM.console('warn', 'Failed to locate field: ' + entity_name + "." + field_name);
689 return null;
690 }
691 return fieldSchema;
692 },
693 /**
694 * Check that the group_type contains *only* the types listed in validTypes
695 *
696 * @param string validTypesExpr
697 * @param bool allowAllSubtypes
698 * @return {Boolean}
699 */
700 //CRM-15427
701 checkGroupType: function(validTypesExpr, allowAllSubtypes) {
702 var allMatched = true;
703 allowAllSubtypes = allowAllSubtypes || false;
704 if (_.isEmpty(this.get('group_type'))) {
705 return true;
706 }
707
708 var actualTypes = CRM.UF.parseTypeList(this.get('group_type'));
709 var validTypes = CRM.UF.parseTypeList(validTypesExpr);
710
711 // Every actual.coreType is a valid.coreType
712 _.each(actualTypes.coreTypes, function(ignore, actualCoreType) {
713 if (! validTypes.coreTypes[actualCoreType]) {
714 allMatched = false;
715 }
716 });
717
718 //CRM-15427 allow all subtypes
719 if (!$.isEmptyObject(validTypes.subTypes) && !allowAllSubtypes) {
720 // Every actual.subType is a valid.subType
721 _.each(actualTypes.subTypes, function(actualSubTypeIds, actualSubTypeKey) {
722 if (!validTypes.subTypes[actualSubTypeKey]) {
723 allMatched = false;
724 return;
725 }
726 // actualSubTypeIds is a list of all subtypes which can be used by group,
727 // so it's sufficient to match any one of them
728 var subTypeMatched = false;
729 _.each(actualSubTypeIds, function(ignore, actualSubTypeId) {
730 if (validTypes.subTypes[actualSubTypeKey][actualSubTypeId]) {
731 subTypeMatched = true;
732 }
733 });
734 allMatched = allMatched && subTypeMatched;
735 });
736 }
737 return allMatched;
738 },
739 calculateContactEntityType: function() {
740 var ufGroupModel = this;
741
742 // set proper entity model based on selected profile
743 var contactTypes = ['Individual', 'Household', 'Organization'];
744 var profileType = ufGroupModel.get('group_type') || '';
745
746 // check if selected profile have subtype defined eg: ["Individual,Contact,Case", "caseType:7"]
747 if (_.isArray(profileType) && profileType[0]) {
748 profileType = profileType[0];
749 }
750 profileType = profileType.split(',');
751
752 var ufEntityModel;
753 _.each(profileType, function (ptype) {
754 if ($.inArray(ptype, contactTypes) > -1) {
755 ufEntityModel = ptype + 'Model';
756 return true;
757 }
758 });
759
760 return ufEntityModel;
761 },
762 setUFGroupModel: function(entityType, allEntityModels) {
763 var ufGroupModel = this;
764
765 var newUfEntityModels = [];
766 _.each(allEntityModels, function (values) {
767 if (entityType && values.entity_name == 'contact_1') {
768 values.entity_type = entityType;
769 }
770 newUfEntityModels.push(new CRM.UF.UFEntityModel(values));
771 });
772
773 ufGroupModel.getRel('ufEntityCollection').reset(newUfEntityModels);
774 },
775 resetEntities: function() {
776 var ufGroupModel = this;
777 var deleteFieldList = [];
778 ufGroupModel.getRel('ufFieldCollection').each(function(ufFieldModel){
779 if (!ufFieldModel.getFieldSchema()) {
780 CRM.alert(ts('This profile no longer includes field "%1"! All references to the field have been removed.', {
781 1: ufFieldModel.get('label')
782 }), '', 'alert', {expires: false});
783 deleteFieldList.push(ufFieldModel);
784 }
785 });
786
787 _.each(deleteFieldList, function(ufFieldModel) {
788 ufFieldModel.destroyLocal();
789 });
790
791 this.getRel('paletteFieldCollection').reset(this.buildPaletteFields());
792
793 // reset to redraw the cancel after entity type is updated.
794 ufGroupModel.getRel('ufFieldCollection').reset(ufGroupModel.getRel('ufFieldCollection').toJSON());
795 },
796 /**
797 *
798 * @return {Array} of PaletteFieldModel
799 */
800 buildPaletteFields: function() {
801 // rebuild list of fields; reuse old instances of PaletteFieldModel and create new ones
802 // as appropriate
803 // Note: The system as a whole is ill-defined in cases where we have an existing
804 // UFField that references a model field that disappears.
805
806 var ufGroupModel = this;
807
808 var oldPaletteFieldModelsBySig = {};
809 this.getRel('paletteFieldCollection').each(function(paletteFieldModel){
810 oldPaletteFieldModelsBySig[paletteFieldModel.get("entityName") + '::' + paletteFieldModel.get("fieldName")] = paletteFieldModel;
811 });
812
813 var newPaletteFieldModels = [];
814 this.getRel('ufEntityCollection').each(function(ufEntityModel){
815 var modelClass = ufEntityModel.getModelClass();
816 _.each(ufEntityModel.getFieldSchemas(), function(value, key, list) {
817 var model = oldPaletteFieldModelsBySig[ufEntityModel.get('entity_name') + '::' + key];
818 if (!model) {
819 model = new CRM.Designer.PaletteFieldModel({
820 modelClass: modelClass,
821 entityName: ufEntityModel.get('entity_name'),
822 fieldName: key
823 });
824 }
825 newPaletteFieldModels.push(model);
826 });
827 });
828
829 return newPaletteFieldModels;
830 }
831 });
832
833 /**
834 * Represents a list of customizable form
835 */
836 CRM.UF.UFGroupCollection = CRM.Backbone.Collection.extend({
837 model: CRM.UF.UFGroupModel
838 });
839 })(CRM.$, CRM._);