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