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