Merge pull request #2712 from pratik-joshi/CRM-14363
[civicrm-core.git] / js / view / crm.designer.js
1 (function($) {
2 if (!CRM.Designer) CRM.Designer = {};
3
4 /**
5 * When rendering a template with Marionette.ItemView, the list of variables is determined by
6 * serializeData(). The normal behavior is to map each property of this.model to a template
7 * variable.
8 *
9 * This function extends that practice by exporting variables "_view", "_model", "_collection",
10 * and "_options". This makes it easier for the template to, e.g., access computed properties of
11 * a model (by calling "_model.getComputedProperty"), or to access constructor options (by
12 * calling "_options.myoption").
13 *
14 * @return {*}
15 */
16 var extendedSerializeData = function() {
17 var result = Marionette.ItemView.prototype.serializeData.apply(this);
18 result._view = this;
19 result._model = this.model;
20 result._collection = this.collection;
21 result._options = this.options;
22 return result;
23 };
24
25 /**
26 * Display a dialog window with an editable form for a UFGroupModel
27 *
28 * The implementation here is very "jQuery-style" and not "Backbone-style";
29 * it's been extracted
30 *
31 * options:
32 * - model: CRM.UF.UFGroupModel
33 */
34 CRM.Designer.DesignerDialog = Backbone.Marionette.Layout.extend({
35 serializeData: extendedSerializeData,
36 template: '#designer_dialog_template',
37 className: 'crm-designer-dialog',
38 regions: {
39 designerRegion: '.crm-designer'
40 },
41 /** @var bool whether this dialog is currently open */
42 isDialogOpen: false,
43 /** @var bool whether any changes have been made */
44 isUfUnsaved: false,
45 /** @var obj handle for the CRM.alert containing undo link */
46 undoAlert: null,
47 /** @var bool whether this dialog is being re-opened by the undo link */
48 undoState: false,
49
50 initialize: function(options) {
51 CRM.designerApp.vent.on('ufUnsaved', this.onUfChanged, this);
52 },
53 onClose: function() {
54 this.undoAlert && this.undoAlert.close && this.undoAlert.close();
55 CRM.designerApp.vent.off('ufUnsaved', this.onUfChanged, this);
56 },
57 onUfChanged: function(isUfUnsaved) {
58 this.isUfUnsaved = isUfUnsaved;
59 },
60 onRender: function() {
61 var designerDialog = this;
62 designerDialog.$el.dialog({
63 autoOpen: true, // note: affects accordion height
64 title: 'Edit Profile',
65 width: '75%',
66 height: 600,
67 minWidth: 500,
68 minHeight: 600, // to allow dropping in big whitespace, coordinate with min-height of .crm-designer-fields
69 open: function() {
70 // Prevent conflicts with other onbeforeunload handlers
71 designerDialog.oldOnBeforeUnload = window.onbeforeunload;
72 // Warn of unsaved changes when navigating away from the page
73 window.onbeforeunload = function() {
74 if (designerDialog.isDialogOpen && designerDialog.isUfUnsaved) {
75 return ts("Your profile has not been saved.");
76 }
77 if (designerDialog.oldOnBeforeUnload) {
78 return designerDialog.oldOnBeforeUnload.apply(arguments);
79 }
80 };
81 designerDialog.undoAlert && designerDialog.undoAlert.close && designerDialog.undoAlert.close();
82 designerDialog.isDialogOpen = true;
83 // Initialize new dialog if we are not re-opening unsaved changes
84 if (designerDialog.undoState === false) {
85 designerDialog.designerRegion && designerDialog.designerRegion.close && designerDialog.designerRegion.close();
86 designerDialog.$el.block({message: 'Loading...', theme: true});
87 designerDialog.options.findCreateUfGroupModel({
88 onLoad: function(ufGroupModel) {
89 designerDialog.model = ufGroupModel;
90 var designerLayout = new CRM.Designer.DesignerLayout({
91 model: ufGroupModel,
92 el: '<div class="full-height"></div>'
93 });
94 designerDialog.$el.unblock();
95 designerDialog.designerRegion.show(designerLayout);
96 CRM.designerApp.vent.trigger('resize');
97 designerDialog.isUfUnsaved = false;
98 }
99 });
100 }
101 designerDialog.undoState = false;
102 // CRM-12188
103 CRM.designerApp.DetachedProfiles = [];
104 },
105 close: function() {
106 window.onbeforeunload = designerDialog.oldOnBeforeUnload;
107 designerDialog.isDialogOpen = false;
108
109 designerDialog.undoAlert && designerDialog.undoAlert.close && designerDialog.undoAlert.close();
110 if (designerDialog.isUfUnsaved) {
111 designerDialog.undoAlert = CRM.alert('<p>' + ts('Your changes to "%1" have not been saved.', {1: designerDialog.model.get('title')}) + '</p><a href="#" class="crm-undo">' + ts('Restore unsaved changes') + '</a>', ts('Unsaved Changes'), 'alert', {expires: 60000});
112 $('.ui-notify-message a.crm-undo').click(function() {
113 designerDialog.undoState = true;
114 designerDialog.$el.dialog('open');
115 return false;
116 });
117 }
118 // CRM-12188
119 CRM.designerApp.restorePreviewArea();
120 },
121 resize: function() {
122 CRM.designerApp.vent.trigger('resize');
123 }
124 });
125 }
126 });
127
128 /**
129 * Display a complete form-editing UI, including canvas, palette, and
130 * buttons.
131 *
132 * options:
133 * - model: CRM.UF.UFGroupModel
134 */
135 CRM.Designer.DesignerLayout = Backbone.Marionette.Layout.extend({
136 serializeData: extendedSerializeData,
137 template: '#designer_template',
138 regions: {
139 buttons: '.crm-designer-buttonset-region',
140 palette: '.crm-designer-palette-region',
141 form: '.crm-designer-form-region',
142 fields: '.crm-designer-fields-region'
143 },
144 initialize: function() {
145 CRM.designerApp.vent.on('resize', this.onResize, this);
146 },
147 onClose: function() {
148 CRM.designerApp.vent.off('resize', this.onResize, this);
149 },
150 onRender: function() {
151 this.buttons.show(new CRM.Designer.ToolbarView({
152 model: this.model
153 }));
154 this.palette.show(new CRM.Designer.PaletteView({
155 model: this.model
156 }));
157 this.form.show(new CRM.Designer.UFGroupView({
158 model: this.model
159 }));
160 this.fields.show(new CRM.Designer.UFFieldCanvasView({
161 model: this.model
162 }));
163 },
164 onResize: function() {
165 if (! this.hasResizedBefore) {
166 this.hasResizedBefore = true;
167 this.$('.crm-designer-toolbar').resizable({
168 handles: 'w',
169 maxWidth: 400,
170 minWidth: 150,
171 resize: function(event, ui) {
172 $('.crm-designer-canvas').css('margin-right', (ui.size.width + 10) + 'px');
173 $(this).css({left: '', height: ''});
174 }
175 }).css({left: '', height: ''});
176 }
177 }
178 });
179
180 /**
181 * Display toolbar with working button
182 *
183 * options:
184 * - model: CRM.UF.UFGroupModel
185 */
186 CRM.Designer.ToolbarView = Backbone.Marionette.ItemView.extend({
187 serializeData: extendedSerializeData,
188 template: '#designer_buttons_template',
189 previewMode: false,
190 events: {
191 'click .crm-designer-save': 'doSave',
192 'click .crm-designer-preview': 'doPreview'
193 },
194 onRender: function() {
195 this.$('.crm-designer-save').button().attr({
196 disabled: 'disabled',
197 style: 'opacity:.5; box-shadow:none; cursor:default;'
198 });
199 this.$('.crm-designer-preview').button();
200 },
201 initialize: function(options) {
202 CRM.designerApp.vent.on('ufUnsaved', this.onUfChanged, this);
203 },
204 onUfChanged: function(isUfUnsaved) {
205 if (isUfUnsaved) {
206 this.$('.crm-designer-save').removeAttr('style').prop('disabled', false);
207 }
208 },
209 doSave: function(event) {
210 var ufGroupModel = this.model;
211 if (ufGroupModel.getRel('ufFieldCollection').hasDuplicates()) {
212 CRM.alert(ts('Please correct errors before saving.'), '', 'alert');
213 return;
214 }
215 var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
216 $dialog.block({message: 'Saving...', theme: true});
217 var profile = ufGroupModel.toStrictJSON();
218 profile["api.UFField.replace"] = {values: ufGroupModel.getRel('ufFieldCollection').toSortedJSON(), 'option.autoweight': 0};
219 CRM.api('UFGroup', 'create', profile, {
220 success: function(data) {
221 $dialog.unblock();
222 var error = false;
223 if (data.is_error) {
224 CRM.alert(data.error_message);
225 error = true;
226 }
227 _.each(data.values, function(ufGroupResponse) {
228 if (ufGroupResponse['api.UFField.replace'].is_error) {
229 CRM.alert(ufGroupResponse['api.UFField.replace'].error_message);
230 error = true;
231 }
232 });
233 if (!error) {
234 if (!ufGroupModel.get('id')) {
235 ufGroupModel.set('id', data.id);
236 }
237 CRM.designerApp.vent.trigger('ufUnsaved', false);
238 CRM.designerApp.vent.trigger('ufSaved');
239 $dialog.dialog('close');
240 }
241 }
242 });
243 return false;
244 },
245 doPreview: function(event) {
246 this.previewMode = !this.previewMode;
247 if (!this.previewMode) {
248 $('.crm-designer-preview-canvas').html('');
249 $('.crm-designer-canvas > *, .crm-designer-palette-region').show();
250 $('.crm-designer-preview span').html(ts('Preview'));
251 return;
252 }
253 if (this.model.getRel('ufFieldCollection').hasDuplicates()) {
254 CRM.alert(ts('Please correct errors before previewing.'), '', 'alert');
255 return;
256 }
257 var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
258 $dialog.block({message: 'Loading...', theme: true});
259 // CRM-12188
260 CRM.designerApp.clearPreviewArea();
261 $.ajax({
262 url: CRM.url("civicrm/ajax/inline"),
263 type: 'POST',
264 data: {
265 'qfKey': CRM.profilePreviewKey,
266 'class_name': 'CRM_UF_Form_Inline_Preview',
267 'snippet': 1,
268 'ufData': JSON.stringify({
269 ufGroup: this.model.toStrictJSON(),
270 ufFieldCollection: this.model.getRel('ufFieldCollection').toSortedJSON()
271 })
272 }
273 }).done(function(data) {
274 $dialog.unblock();
275 $('.crm-designer-canvas > *, .crm-designer-palette-region').hide();
276 $('.crm-designer-preview-canvas').html(data).show();
277 $('.crm-designer-preview span').html(ts('Edit'));
278 });
279 return false;
280 }
281 });
282
283 /**
284 * Display a selection of available fields
285 *
286 * options:
287 * - model: CRM.UF.UFGroupModel
288 */
289 CRM.Designer.PaletteView = Backbone.Marionette.ItemView.extend({
290 serializeData: extendedSerializeData,
291 template: '#palette_template',
292 el: '<div class="full-height"></div>',
293 openTreeNodes: [],
294 events: {
295 'keyup .crm-designer-palette-search input': 'doSearch',
296 'change .crm-contact-types': 'doSetPaletteEntity',
297 'click .crm-designer-palette-clear-search': 'clearSearch',
298 'click .crm-designer-palette-toggle': 'toggleAll',
299 'click .crm-designer-palette-add button': 'doNewCustomFieldDialog',
300 'click #crm-designer-add-custom-set': 'doNewCustomSetDialog',
301 'dblclick .crm-designer-palette-field': 'doAddToCanvas'
302 },
303 initialize: function() {
304 this.model.getRel('ufFieldCollection')
305 .on('add', this.toggleActive, this)
306 .on('remove', this.toggleActive, this);
307 this.model.getRel('paletteFieldCollection')
308 .on('reset', this.render, this);
309 CRM.designerApp.vent.on('resize', this.onResize, this);
310 },
311 onClose: function() {
312 this.model.getRel('ufFieldCollection')
313 .off('add', this.toggleActive, this)
314 .off('remove', this.toggleActive, this);
315 this.model.getRel('paletteFieldCollection')
316 .off('reset', this.render, this);
317 CRM.designerApp.vent.off('resize', this.onResize, this);
318 },
319 onRender: function() {
320 var paletteView = this;
321
322 // Prepare data for jstree
323 var treeData = [];
324 var paletteFieldsByEntitySection = this.model.getRel('paletteFieldCollection').getFieldsByEntitySection();
325
326 paletteView.model.getRel('ufEntityCollection').each(function(ufEntityModel){
327 _.each(ufEntityModel.getSections(), function(section, sectionKey){
328 var defaultValue = paletteView.selectedContactType;
329 if (!defaultValue) {
330 defaultValue = paletteView.model.calculateContactEntityType();
331 }
332
333 // set selected option as default, since we are rebuilding palette
334 paletteView.$('.crm-contact-types').val(defaultValue).prop('selected','selected');
335
336 var entitySection = ufEntityModel.get('entity_name') + '-' + sectionKey;
337 var items = [];
338 if (paletteFieldsByEntitySection[entitySection]) {
339 _.each(paletteFieldsByEntitySection[entitySection], function(paletteFieldModel, k) {
340 items.push({data: paletteFieldModel.getLabel(), attr: {'class': 'crm-designer-palette-field', 'data-plm-cid': paletteFieldModel.cid}});
341 });
342 }
343 if (section.is_addable) {
344 items.push({data: ts('+ Add New Field'), attr: {'class': 'crm-designer-palette-add'}});
345 }
346 if (items.length > 0) {
347 treeData.push({
348 data: section.title,
349 children: items,
350 state: _.contains(paletteView.openTreeNodes, sectionKey) ? 'open' : 'closed',
351 attr: {
352 'class': 'crm-designer-palette-section',
353 'data-section': sectionKey,
354 'data-entity': ufEntityModel.get('entity_name')
355 }
356 });
357 }
358 })
359 });
360
361 this.$('.crm-designer-palette-tree').jstree({
362 'json_data': {data: treeData},
363 'search': {
364 'case_insensitive' : true,
365 'show_only_matches': true
366 },
367 themes: {
368 "theme": 'classic',
369 "dots": false,
370 "icons": false,
371 "url": CRM.config.resourceBase + 'packages/jquery/plugins/jstree/themes/classic/style.css'
372 },
373 'plugins': ['themes', 'json_data', 'ui', 'search']
374 }).bind('loaded.jstree', function () {
375 $('.crm-designer-palette-field', this).draggable({
376 appendTo: '.crm-designer',
377 zIndex: $(this.$el).zIndex() + 5000,
378 helper: 'clone',
379 connectToSortable: '.crm-designer-fields' // FIXME: tight canvas/palette coupling
380 });
381 paletteView.model.getRel('ufFieldCollection').each(function(ufFieldModel) {
382 paletteView.toggleActive(ufFieldModel, paletteView.model.getRel('ufFieldCollection'))
383 });
384 paletteView.$('.crm-designer-palette-add a').replaceWith('<button>' + $('.crm-designer-palette-add a').first().text() + '</<button>');
385 paletteView.$('.crm-designer-palette-tree > ul').append('<li><button id="crm-designer-add-custom-set">+ ' + ts('Add Set of Custom Fields') + '</button></li>');
386 paletteView.$('.crm-designer-palette-tree button').button();
387 }).bind("select_node.jstree", function (e, data) {
388 $(this).jstree("toggle_node", data.rslt.obj);
389 $(this).jstree("deselect_node", data.rslt.obj);
390 });
391
392 // FIXME: tight canvas/palette coupling
393 this.$(".crm-designer-fields").droppable({
394 activeClass: "ui-state-default",
395 hoverClass: "ui-state-hover",
396 accept: ":not(.ui-sortable-helper)"
397 });
398
399 this.onResize();
400 },
401 onResize: function() {
402 var pos = this.$('.crm-designer-palette-tree').position();
403 var div = this.$('.crm-designer-palette-tree').closest('.crm-container').height();
404 this.$('.crm-designer-palette-tree').css({height: div - pos.top});
405 },
406 doSearch: function(event) {
407 $('.crm-designer-palette-tree').jstree("search", $(event.target).val());
408 },
409 doSetPaletteEntity: function(event) {
410 this.selectedContactType = $('.crm-contact-types :selected').val();
411 // loop through entity collection and remove non-valid entity section's
412 var newUfEntityModels = [];
413 this.model.getRel('ufEntityCollection').each(function(oldUfEntityModel){
414 var values = oldUfEntityModel.toJSON();
415 if (values.entity_name == 'contact_1') {
416 values.entity_type = $('.crm-contact-types :selected').val();
417 }
418 newUfEntityModels.push(new CRM.UF.UFEntityModel(values));
419 });
420 this.model.getRel('ufEntityCollection').reset(newUfEntityModels);
421 },
422 doAddToCanvas: function(event) {
423 var paletteFieldModel = this.model.getRel('paletteFieldCollection').get($(event.currentTarget).attr('data-plm-cid'));
424 paletteFieldModel.addToUFCollection(this.model.getRel('ufFieldCollection'));
425 event.stopPropagation();
426 },
427 doNewCustomFieldDialog: function(event) {
428 var paletteView = this;
429 var entityKey = $(event.currentTarget).closest('.crm-designer-palette-section').attr('data-entity');
430 var sectionKey = $(event.currentTarget).closest('.crm-designer-palette-section').attr('data-section');
431 var ufEntityModel = paletteView.model.getRel('ufEntityCollection').getByName(entityKey);
432 var sections = ufEntityModel.getSections();
433 var url = CRM.url('civicrm/admin/custom/group/field/add', {
434 reset: 1,
435 action: 'add',
436 gid: sections[sectionKey].custom_group_id
437 });
438 CRM.loadForm(url).on('crmFormSuccess', function(e, data) {
439 paletteView.doRefresh('custom_' + data.id);
440 });
441 return false;
442 },
443 doNewCustomSetDialog: function(event) {
444 var paletteView = this;
445 var url = CRM.url('civicrm/admin/custom/group', 'action=add&reset=1');
446 // Create custom field set and automatically go to next step (create fields) after save button is clicked.
447 CRM.loadForm(url, {refreshAction: ['next']})
448 .on('crmFormSuccess', function(e, data) {
449 // When form switches to create custom field context, modify button behavior to only continue for "save and new"
450 data.customField && ($(this).data('crmSnippet').options.crmForm.refreshAction = ['next_new']);
451 paletteView.doRefresh(data.customField ? 'custom_' + data.id : null);
452 });
453 return false;
454 },
455 doRefresh: function(fieldToAdd) {
456 var ufGroupModel = this.model;
457 this.getOpenTreeNodes();
458 CRM.Schema.reloadModels()
459 .done(function(data){
460 ufGroupModel.resetEntities();
461 if (fieldToAdd) {
462 var field = ufGroupModel.getRel('paletteFieldCollection').getFieldByName(null, fieldToAdd);
463 field.addToUFCollection(ufGroupModel.getRel('ufFieldCollection'));
464 }
465 })
466 .fail(function() {
467 CRM.alert(ts('Failed to retrieve schema'), ts('Error'), 'error');
468 });
469 },
470 clearSearch: function(event) {
471 $('.crm-designer-palette-search input').val('').keyup();
472 return false;
473 },
474 toggleActive: function(ufFieldModel, ufFieldCollection, options) {
475 var paletteFieldCollection = this.model.getRel('paletteFieldCollection');
476 var paletteFieldModel = paletteFieldCollection.getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
477 var isAddable = ufFieldCollection.isAddable(ufFieldModel);
478 if (paletteFieldModel) {
479 this.$('[data-plm-cid='+paletteFieldModel.cid+']').toggleClass('disabled', !isAddable);
480 }
481 },
482 toggleAll: function(event) {
483 if ($('.crm-designer-palette-search input').val() == '') {
484 $('.crm-designer-palette-tree').jstree($(event.target).attr('rel'));
485 }
486 return false;
487 },
488 getOpenTreeNodes: function() {
489 var paletteView = this;
490 this.openTreeNodes = [];
491 this.$('.crm-designer-palette-section.jstree-open').each(function() {
492 paletteView.openTreeNodes.push($(this).data('section'));
493 })
494 }
495 });
496
497 /**
498 * Display all UFFieldModel objects in a UFGroupModel.
499 *
500 * options:
501 * - model: CRM.UF.UFGroupModel
502 */
503 CRM.Designer.UFFieldCanvasView = Backbone.Marionette.View.extend({
504 initialize: function() {
505 this.model.getRel('ufFieldCollection')
506 .on('add', this.updatePlaceholder, this)
507 .on('remove', this.updatePlaceholder, this)
508 .on('add', this.addUFFieldView, this)
509 .on('reset', this.render, this);
510 },
511 onClose: function() {
512 this.model.getRel('ufFieldCollection')
513 .off('add', this.updatePlaceholder, this)
514 .off('remove', this.updatePlaceholder, this)
515 .off('add', this.addUFFieldView, this)
516 .off('reset', this.render, this);
517 },
518 render: function() {
519 var ufFieldCanvasView = this;
520 this.$el.html(_.template($('#field_canvas_view_template').html()));
521
522 // BOTTOM: Setup field-level editing
523 var $fields = this.$('.crm-designer-fields');
524 this.updatePlaceholder();
525 var ufFieldModels = this.model.getRel('ufFieldCollection').sortBy(function(ufFieldModel) {
526 return parseInt(ufFieldModel.get('weight'));
527 });
528 _.each(ufFieldModels, function(ufFieldModel) {
529 ufFieldCanvasView.addUFFieldView(ufFieldModel, ufFieldCanvasView.model.getRel('ufFieldCollection'), {skipWeights: true});
530 });
531 this.$(".crm-designer-fields").sortable({
532 placeholder: 'crm-designer-row-placeholder',
533 forcePlaceholderSize: true,
534 receive: function(event, ui) {
535 var paletteFieldModel = ufFieldCanvasView.model.getRel('paletteFieldCollection').get(ui.item.attr('data-plm-cid'));
536 var ufFieldModel = paletteFieldModel.addToUFCollection(
537 ufFieldCanvasView.model.getRel('ufFieldCollection'),
538 {skipWeights: true}
539 );
540 if (null == ufFieldModel) {
541 ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').remove();
542 } else {
543 // Move from end to the 'dropped' position
544 var ufFieldViewEl = ufFieldCanvasView.$('div[data-field-cid='+ufFieldModel.cid+']').parent();
545 ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').replaceWith(ufFieldViewEl);
546 }
547 // note: the sortable() update callback will call updateWeight
548 },
549 update: function() {
550 ufFieldCanvasView.updateWeights();
551 }
552 });
553 },
554 /** Determine visual order of fields and set the model values for "weight" */
555 updateWeights: function() {
556 var ufFieldCanvasView = this;
557 var weight = 1;
558 var rows = this.$('.crm-designer-row').each(function(key, row) {
559 if ($(row).hasClass('placeholder')) {
560 return;
561 }
562 var ufFieldCid = $(row).attr('data-field-cid');
563 var ufFieldModel = ufFieldCanvasView.model.getRel('ufFieldCollection').get(ufFieldCid);
564 ufFieldModel.set('weight', weight);
565 weight++;
566 });
567 },
568 addUFFieldView: function(ufFieldModel, ufFieldCollection, options) {
569 var paletteFieldModel = this.model.getRel('paletteFieldCollection').getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
570 var ufFieldView = new CRM.Designer.UFFieldView({
571 el: $("<div></div>"),
572 model: ufFieldModel,
573 paletteFieldModel: paletteFieldModel
574 });
575 ufFieldView.render();
576 this.$('.crm-designer-fields').append(ufFieldView.$el);
577 if (! (options && options.skipWeights)) {
578 this.updateWeights();
579 }
580 },
581 updatePlaceholder: function() {
582 if (this.model.getRel('ufFieldCollection').isEmpty()) {
583 this.$('.placeholder').css({display: 'block', border: '0 none', cursor: 'default'});
584 } else {
585 this.$('.placeholder').hide();
586 }
587 }
588 });
589
590 /**
591 * options:
592 * - model: CRM.UF.UFFieldModel
593 * - paletteFieldModel: CRM.Designer.PaletteFieldModel
594 */
595 CRM.Designer.UFFieldView = Backbone.Marionette.Layout.extend({
596 serializeData: extendedSerializeData,
597 template: '#field_row_template',
598 expanded: false,
599 regions: {
600 summary: '.crm-designer-field-summary',
601 detail: '.crm-designer-field-detail'
602 },
603 events: {
604 "click .crm-designer-action-settings": 'doToggleForm',
605 "click button.crm-designer-edit-custom": 'doEditCustomField',
606 "click .crm-designer-action-remove": 'doRemove'
607 },
608 modelEvents: {
609 "destroy": 'remove',
610 "change:is_duplicate": 'onChangeIsDuplicate'
611 },
612 onRender: function() {
613 this.summary.show(new CRM.Designer.UFFieldSummaryView({
614 model: this.model,
615 fieldSchema: this.model.getFieldSchema(),
616 paletteFieldModel: this.options.paletteFieldModel
617 }));
618 this.detail.show(new CRM.Designer.UFFieldDetailView({
619 model: this.model,
620 fieldSchema: this.model.getFieldSchema()
621 }));
622 this.onChangeIsDuplicate(this.model, this.model.get('is_duplicate'))
623 if (!this.expanded) {
624 this.detail.$el.hide();
625 }
626 var that = this;
627 CRM.designerApp.vent.on('formOpened', function(event) {
628 if (that.expanded && event != that.cid) {
629 that.doToggleForm(false);
630 }
631 });
632 },
633 doToggleForm: function(event) {
634 this.expanded = !this.expanded;
635 if (this.expanded && event !== false) {
636 CRM.designerApp.vent.trigger('formOpened', this.cid);
637 }
638 this.$el.toggleClass('crm-designer-open', this.expanded);
639 var $detail = this.detail.$el;
640 if (!this.expanded) {
641 $detail.toggle('blind', 250);
642 this.$('button.crm-designer-edit-custom').remove();
643 }
644 else {
645 var $canvas = $('.crm-designer-canvas');
646 var top = $canvas.offset().top;
647 $detail.slideDown({
648 duration: 250,
649 step: function(num, effect) {
650 // Scroll canvas to keep field details visible
651 if (effect.prop == 'height') {
652 if (effect.now + $detail.offset().top - top > $canvas.height() - 9) {
653 $canvas.scrollTop($canvas.scrollTop() + effect.now + $detail.offset().top - top - $canvas.height() + 9);
654 }
655 }
656 }
657 });
658 if (this.model.get('field_name').split('_')[0] == 'custom') {
659 this.$('.crm-designer-field-summary > div').append('<button class="crm-designer-edit-custom">&raquo; ' + ts('Edit Custom Field') + '</button>');
660 this.$('button.crm-designer-edit-custom').button().attr('title', ts('Edit global settings for this custom field.'));
661 }
662 }
663 },
664 doEditCustomField: function() {
665 var url = CRM.url('civicrm/admin/custom/group/field/update', {
666 action: 'update',
667 reset: 1,
668 id: this.model.get('field_name').split('_')[1]
669 });
670 var form1 = CRM.loadForm(url, {openInline: '.crm-custom-field-form-block-data_type a'})
671 .on('crmFormLoad', function() {
672 $(this).prepend('<div class="messages status"><div class="icon inform-icon"></div>' + ts('Note: This will modify the field system-wide, not just in this profile form.') + '</div>');
673 var $link = $('.action-link a', this);
674 if ($link.length) {
675 $link.detach();
676 var buttons = {};
677 buttons[$link.text()] = function() {
678 var form2 = CRM.loadForm($link.attr('href'), {
679 openInline: 'a.action-item:not([href="#"])',
680 dialog: {
681 width: '60%',
682 height: '70%'
683 }
684 });
685 }
686 $(this).dialog('option', 'buttons', buttons);
687 }
688 })
689 return false;
690 },
691 onChangeIsDuplicate: function(model, value, options) {
692 this.$el.toggleClass('crm-designer-duplicate', value);
693 },
694 doRemove: function(event) {
695 var that = this;
696 this.$el.hide(250, function() {
697 that.model.destroyLocal();
698 });
699 }
700 });
701
702 /**
703 * options:
704 * - model: CRM.UF.UFFieldModel
705 * - fieldSchema: (Backbone.Form schema element)
706 * - paletteFieldModel: CRM.Designer.PaletteFieldModel
707 */
708 CRM.Designer.UFFieldSummaryView = Backbone.Marionette.ItemView.extend({
709 serializeData: extendedSerializeData,
710 template: '#field_summary_template',
711 modelEvents: {
712 'change': 'render'
713 },
714
715 /**
716 * Compose a printable string which describes the binding of this UFField to the data model
717 * @return {String}
718 */
719 getBindingLabel: function() {
720 var result = this.options.paletteFieldModel.getSection().title + ": " + this.options.paletteFieldModel.getLabel();
721 if (this.options.fieldSchema.civiIsPhone) {
722 result = result + '-' + CRM.PseudoConstant.phoneType[this.model.get('phone_type_id')];
723 }
724 if (this.options.fieldSchema.civiIsLocation) {
725 var locType = this.model.get('location_type_id') ? CRM.PseudoConstant.locationType[this.model.get('location_type_id')] : ts('Primary');
726 result = result + ' (' + locType + ')';
727 }
728 return result;
729 },
730
731 /**
732 * Return a string marking if the field is required
733 * @return {String}
734 */
735 getRequiredMarker: function() {
736 if (this.model.get('is_required') == 1) {
737 return ' <span class="crm-marker">*</span> ';
738 }
739 return '';
740 },
741
742 onRender: function() {
743 this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
744 if (this.model.get("is_reserved") == 1) {
745 this.$('.crm-designer-buttons').hide();
746 }
747 }
748 });
749
750 /**
751 * options:
752 * - model: CRM.UF.UFFieldModel
753 * - fieldSchema: (Backbone.Form schema element)
754 */
755 CRM.Designer.UFFieldDetailView = Backbone.View.extend({
756 initialize: function() {
757 // FIXME: hide/display 'in_selector' if 'visibility' is one of the public options
758 var fields = ['location_type_id', 'phone_type_id', 'label', 'is_multi_summary', 'is_required', 'is_view', 'visibility', 'in_selector', 'is_searchable', 'help_pre', 'help_post', 'is_active'];
759 if (! this.options.fieldSchema.civiIsLocation) {
760 fields = _.without(fields, 'location_type_id');
761 }
762 if (! this.options.fieldSchema.civiIsPhone) {
763 fields = _.without(fields, 'phone_type_id');
764 }
765 if (!this.options.fieldSchema.civiIsMultiple) {
766 fields = _.without(fields, 'is_multi_summary');
767 }
768
769 this.form = new Backbone.Form({
770 model: this.model,
771 fields: fields
772 });
773 this.form.on('change', this.onFormChange, this);
774 this.model.on('change', this.onModelChange, this);
775 },
776 render: function() {
777 this.$el.html(this.form.render().el);
778 this.onFormChange();
779 },
780 onModelChange: function() {
781 $.each(this.form.fields, function(i, field) {
782 this.form.setValue(field.key, this.model.get(field.key));
783 });
784 },
785 onFormChange: function() {
786 this.form.commit();
787 this.$('.field-is_multi_summary').toggle(this.options.fieldSchema.civiIsMultiple ? true : false);
788 this.$('.field-in_selector').toggle(this.model.isInSelectorAllowed());
789
790 if (!this.model.isInSelectorAllowed() && this.model.get('in_selector') != "0") {
791 this.model.set('in_selector', "0");
792 this.form.setValue('in_selector', "0");
793 // TODO: It might be nicer if we didn't completely discard in_selector -- e.g.
794 // if the value could be restored when the user isInSelectorAllowed becomes true
795 // again. However, I haven't found a simple way to do this.
796 }
797 }
798 });
799
800 /**
801 * options:
802 * - model: CRM.UF.UFGroupModel
803 */
804 CRM.Designer.UFGroupView = Backbone.Marionette.Layout.extend({
805 serializeData: extendedSerializeData,
806 template: '#form_row_template',
807 expanded: false,
808 regions: {
809 summary: '.crm-designer-form-summary',
810 detail: '.crm-designer-form-detail'
811 },
812 events: {
813 "click .crm-designer-action-settings": 'doToggleForm'
814 },
815 onRender: function() {
816 this.summary.show(new CRM.Designer.UFGroupSummaryView({
817 model: this.model
818 }));
819 this.detail.show(new CRM.Designer.UFGroupDetailView({
820 model: this.model
821 }));
822 if (!this.expanded) {
823 this.detail.$el.hide();
824 }
825 var that = this;
826 CRM.designerApp.vent.on('formOpened', function(event) {
827 if (that.expanded && event !== 0) {
828 that.doToggleForm(false);
829 }
830 });
831 },
832 doToggleForm: function(event) {
833 this.expanded = !this.expanded;
834 if (this.expanded && event !== false) {
835 CRM.designerApp.vent.trigger('formOpened', 0);
836 }
837 this.$el.toggleClass('crm-designer-open', this.expanded);
838 this.detail.$el.toggle('blind', 250);
839 }
840 });
841
842 /**
843 * options:
844 * - model: CRM.UF.UFGroupModel
845 */
846 CRM.Designer.UFGroupSummaryView = Backbone.Marionette.ItemView.extend({
847 serializeData: extendedSerializeData,
848 template: '#form_summary_template',
849 modelEvents: {
850 'change': 'render'
851 },
852 onRender: function() {
853 this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
854 if (this.model.get("is_reserved") == 1) {
855 this.$('.crm-designer-buttons').hide();
856 }
857 }
858 });
859
860 /**
861 * options:
862 * - model: CRM.UF.UFGroupModel
863 */
864 CRM.Designer.UFGroupDetailView = Backbone.View.extend({
865 initialize: function() {
866 this.form = new Backbone.Form({
867 model: this.model,
868 fields: ['title', 'help_pre', 'help_post', 'is_active']
869 });
870 this.form.on('change', this.form.commit, this.form);
871 },
872 render: function() {
873 this.$el.html(this.form.render().el);
874 }
875 });
876
877 })(cj);