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