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