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