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