Merge pull request #238 from colemanw/CRM-12198
[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 },
103 close: function() {
104 window.onbeforeunload = designerDialog.oldOnBeforeUnload;
105 designerDialog.isDialogOpen = false;
106
107 designerDialog.undoAlert && designerDialog.undoAlert.close && designerDialog.undoAlert.close();
108 if (designerDialog.isUfUnsaved) {
109 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});
110 $('.ui-notify-message a.crm-undo').click(function() {
111 designerDialog.undoState = true;
112 designerDialog.$el.dialog('open');
113 return false;
114 });
115 }
116 },
117 resize: function() {
118 CRM.designerApp.vent.trigger('resize');
119 }
120 });
121 }
122 });
123
124 /**
125 * Display a complete form-editing UI, including canvas, palette, and
126 * buttons.
127 *
128 * options:
129 * - model: CRM.UF.UFGroupModel
130 */
131 CRM.Designer.DesignerLayout = Backbone.Marionette.Layout.extend({
132 serializeData: extendedSerializeData,
133 template: '#designer_template',
134 regions: {
135 buttons: '.crm-designer-buttonset-region',
136 palette: '.crm-designer-palette-region',
137 form: '.crm-designer-form-region',
138 fields: '.crm-designer-fields-region'
139 },
140 initialize: function() {
141 CRM.designerApp.vent.on('resize', this.onResize, this);
142 },
143 onClose: function() {
144 CRM.designerApp.vent.off('resize', this.onResize, this);
145 },
146 onRender: function() {
147 this.buttons.show(new CRM.Designer.ToolbarView({
148 model: this.model
149 }));
150 this.palette.show(new CRM.Designer.PaletteView({
151 model: this.model
152 }));
153 this.form.show(new CRM.Designer.UFGroupView({
154 model: this.model
155 }));
156 this.fields.show(new CRM.Designer.UFFieldCanvasView({
157 model: this.model
158 }));
159 },
160 onResize: function() {
161 if (! this.hasResizedBefore) {
162 this.hasResizedBefore = true;
163 this.$('.crm-designer-toolbar').resizable({
164 handles: 'w',
165 maxWidth: 400,
166 minWidth: 150,
167 resize: function(event, ui) {
168 $('.crm-designer-canvas').css('margin-right', (ui.size.width + 10) + 'px');
169 $(this).css({left: '', height: ''});
170 }
171 }).css({left: '', height: ''});
172 }
173 }
174 });
175
176 /**
177 * Display toolbar with working button
178 *
179 * options:
180 * - model: CRM.UF.UFGroupModel
181 */
182 CRM.Designer.ToolbarView = Backbone.Marionette.ItemView.extend({
183 serializeData: extendedSerializeData,
184 template: '#designer_buttons_template',
185 previewMode: false,
186 events: {
187 'click .crm-designer-save': 'doSave',
188 'click .crm-designer-preview': 'doPreview'
189 },
190 onRender: function() {
191 this.$('.crm-designer-save').button().attr({
192 disabled: 'disabled',
193 style: 'opacity:.5; box-shadow:none; cursor:default;'
194 });
195 this.$('.crm-designer-preview').button();
196 },
197 initialize: function(options) {
198 CRM.designerApp.vent.on('ufUnsaved', this.onUfChanged, this);
199 },
200 onUfChanged: function(isUfUnsaved) {
201 if (isUfUnsaved) {
202 this.$('.crm-designer-save').removeAttr('style').removeAttr('disabled');
203 }
204 },
205 doSave: function(event) {
206 var ufGroupModel = this.model;
207 if (ufGroupModel.getRel('ufFieldCollection').hasDuplicates()) {
208 CRM.alert(ts('Please correct errors before saving.'), '', 'alert');
209 return;
210 }
211 var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
212 $dialog.block({message: 'Saving...', theme: true});
213 var profile = ufGroupModel.toStrictJSON();
214 profile["api.UFField.replace"] = {values: ufGroupModel.getRel('ufFieldCollection').toSortedJSON(), 'option.autoweight': 0};
215 CRM.api('UFGroup', 'create', profile, {
216 success: function(data) {
217 $dialog.unblock();
218 var error = false;
219 if (data.is_error) {
220 CRM.alert(data.error_message);
221 error = true;
222 }
223 _.each(data.values, function(ufGroupResponse) {
224 if (ufGroupResponse['api.UFField.replace'].is_error) {
225 CRM.alert(ufGroupResponse['api.UFField.replace'].error_message);
226 error = true;
227 }
228 });
229 if (!error) {
230 if (!ufGroupModel.get('id')) {
231 ufGroupModel.set('id', data.id);
232 }
233 CRM.designerApp.vent.trigger('ufUnsaved', false);
234 CRM.designerApp.vent.trigger('ufSaved');
235 $dialog.dialog('close');
236 }
237 }
238 });
239 return false;
240 },
241 doPreview: function(event) {
242 this.previewMode = !this.previewMode;
243 if (!this.previewMode) {
244 $('.crm-designer-preview-canvas').html('');
245 $('.crm-designer-canvas > *, .crm-designer-palette-region').show();
246 $('.crm-designer-preview span').html(ts('Preview'));
247 return;
248 }
249 if (this.model.getRel('ufFieldCollection').hasDuplicates()) {
250 CRM.alert(ts('Please correct errors before previewing.'), '', 'alert');
251 return;
252 }
253 var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
254 $dialog.block({message: 'Loading...', theme: true});
255 $.ajax({
256 url: CRM.url("civicrm/ajax/inline"),
257 type: 'POST',
258 data: {
259 'qfKey': CRM.profilePreviewKey,
260 'class_name': 'CRM_UF_Form_Inline_Preview',
261 'snippet': 1,
262 'ufData': JSON.stringify({
263 ufGroup: this.model.toStrictJSON(),
264 ufFieldCollection: this.model.getRel('ufFieldCollection').toSortedJSON()
265 })
266 }
267 }).done(function(data) {
268 $dialog.unblock();
269 $('.crm-designer-canvas > *, .crm-designer-palette-region').hide();
270 $('.crm-designer-preview-canvas').html(data).show();
271 $('.crm-designer-preview span').html(ts('Edit'));
272 });
273 return false;
274 }
275 });
276
277 /**
278 * Display a selection of available fields
279 *
280 * options:
281 * - model: CRM.UF.UFGroupModel
282 */
283 CRM.Designer.PaletteView = Backbone.Marionette.ItemView.extend({
284 serializeData: extendedSerializeData,
285 template: '#palette_template',
286 el: '<div class="full-height"></div>',
287 events: {
288 'keyup .crm-designer-palette-search input': 'doSearch',
289 'click .crm-designer-palette-clear-search': 'clearSearch',
290 'click .crm-designer-palette-refresh': 'doRefresh',
291 'click .crm-designer-palette-toggle': 'toggleAll'
292 },
293 initialize: function() {
294 this.model.getRel('ufFieldCollection')
295 .on('add', this.toggleActive, this)
296 .on('remove', this.toggleActive, this);
297 this.model.getRel('paletteFieldCollection')
298 .on('reset', this.render, this);
299 CRM.designerApp.vent.on('resize', this.onResize, this);
300 },
301 onClose: function() {
302 this.model.getRel('ufFieldCollection')
303 .off('add', this.toggleActive, this)
304 .off('remove', this.toggleActive, this);
305 this.model.getRel('paletteFieldCollection')
306 .off('reset', this.render, this);
307 CRM.designerApp.vent.off('resize', this.onResize, this);
308 },
309 onRender: function() {
310 var paletteView = this;
311
312 // Prepare data for jstree
313 var treeData = [];
314 var paletteFieldsByEntitySection = this.model.getRel('paletteFieldCollection').getFieldsByEntitySection();
315
316 paletteView.model.getRel('ufEntityCollection').each(function(ufEntityModel){
317 _.each(ufEntityModel.getSections(), function(section, sectionKey){
318 var entitySection = ufEntityModel.get('entity_name') + '-' + sectionKey;
319 var items = [];
320 if (paletteFieldsByEntitySection[entitySection]) {
321 _.each(paletteFieldsByEntitySection[entitySection], function(paletteFieldModel, k) {
322 items.push({data: paletteFieldModel.getLabel(), attr: {'class': 'crm-designer-palette-field', 'data-plm-cid': paletteFieldModel.cid}});
323 });
324 }
325 if (section.is_addable) {
326 items.push({data: 'placeholder', attr: {'class': 'crm-designer-palette-add', 'data-entity': ufEntityModel.get('entity_name'), 'data-section': sectionKey}});
327 }
328 if (items.length > 0) {
329 treeData.push({data: section.title, children: items});
330 }
331 })
332 });
333
334 this.$('.crm-designer-palette-tree').jstree({
335 'json_data': {data: treeData},
336 'search': {
337 'case_insensitive' : true,
338 'show_only_matches': true
339 },
340 themes: {
341 "theme": 'classic',
342 "dots": false,
343 "icons": false,
344 "url": CRM.config.resourceBase + 'packages/jquery/plugins/jstree/themes/classic/style.css'
345 },
346 'plugins': ['themes', 'json_data', 'ui', 'search']
347 }).bind('loaded.jstree', function () {
348 $('.crm-designer-palette-field', this).draggable({
349 appendTo: '.crm-designer',
350 zIndex: $(this.$el).zIndex() + 5000,
351 helper: 'clone',
352 connectToSortable: '.crm-designer-fields' // FIXME: tight canvas/palette coupling
353 });
354 $('.crm-designer-palette-field', this).dblclick(function(event){
355 var paletteFieldModel = paletteView.model.getRel('paletteFieldCollection').get($(event.currentTarget).attr('data-plm-cid'));
356 paletteFieldModel.addToUFCollection(paletteView.model.getRel('ufFieldCollection'));
357 event.stopPropagation();
358 });
359 paletteView.model.getRel('ufFieldCollection').each(function(ufFieldModel) {
360 paletteView.toggleActive(ufFieldModel, paletteView.model.getRel('ufFieldCollection'))
361 });
362 paletteView.$('.crm-designer-palette-add a').remove();
363 paletteView.$('.crm-designer-palette-add').append('<button>'+ts('Add Field')+'</button>');
364 paletteView.$('.crm-designer-palette-add button').button()
365 .click(function(event){
366 var entityKey = $(event.currentTarget).closest('.crm-designer-palette-add').attr('data-entity');
367 var sectionKey = $(event.currentTarget).closest('.crm-designer-palette-add').attr('data-section');
368 var ufEntityModel = paletteView.model.getRel('ufEntityCollection').getByName(entityKey);
369 var sections = ufEntityModel.getSections();
370 paletteView.doAddField(sections[sectionKey]);
371 event.stopPropagation();
372 })
373 ;
374 }).bind("select_node.jstree", function (e, data) {
375 $(this).jstree("toggle_node", data.rslt.obj);
376 $(this).jstree("deselect_node", data.rslt.obj);
377 });
378
379 // FIXME: tight canvas/palette coupling
380 this.$(".crm-designer-fields").droppable({
381 activeClass: "ui-state-default",
382 hoverClass: "ui-state-hover",
383 accept: ":not(.ui-sortable-helper)"
384 });
385
386 this.onResize();
387 },
388 onResize: function() {
389 var pos = this.$('.crm-designer-palette-tree').position();
390 var div = this.$('.crm-designer-palette-tree').closest('.crm-container').height();
391 this.$('.crm-designer-palette-tree').css({height: div - pos.top});
392 },
393 doSearch: function(event) {
394 $('.crm-designer-palette-tree').jstree("search", $(event.target).val());
395 },
396 doAddField: function(section) {
397 var paletteView = this;
398 var openAddNewWindow = function() {
399 var url = CRM.url('civicrm/admin/custom/group/field/add', {
400 reset: 1,
401 action: 'add',
402 gid: section.custom_group_id
403 });
404 window.open(url, '_blank');
405 };
406
407 if (paletteView.hideAddFieldAlert) {
408 openAddNewWindow();
409 } else {
410 CRM.confirm(function() {
411 paletteView.hideAddFieldAlert = true;
412 openAddNewWindow();
413 }, {
414 title: ts('Add Field'),
415 message: ts('A new window or tab will open. Use the new window to add your field, and then return to this window and click "Refresh."')
416 }
417 );
418 }
419 return false;
420 },
421 doRefresh: function(event) {
422 var ufGroupModel = this.model;
423 CRM.Schema.reloadModels()
424 .done(function(data){
425 ufGroupModel.resetEntities();
426 })
427 .fail(function() {
428 CRM.alert(ts('Failed to retrieve schema'), ts('Error'), 'error');
429 });
430 return false;
431 },
432 clearSearch: function(event) {
433 $('.crm-designer-palette-search input').val('').keyup();
434 return false;
435 },
436 toggleActive: function(ufFieldModel, ufFieldCollection, options) {
437 var paletteFieldCollection = this.model.getRel('paletteFieldCollection');
438 var paletteFieldModel = paletteFieldCollection.getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
439 var isAddable = ufFieldCollection.isAddable(ufFieldModel);
440 this.$('[data-plm-cid='+paletteFieldModel.cid+']').toggleClass('disabled', !isAddable);
441 },
442 toggleAll: function(event) {
443 if ($('.crm-designer-palette-search input').val() == '') {
444 $('.crm-designer-palette-tree').jstree($(event.target).attr('rel'));
445 }
446 return false;
447 }
448 });
449
450 /**
451 * Display all UFFieldModel objects in a UFGroupModel.
452 *
453 * options:
454 * - model: CRM.UF.UFGroupModel
455 */
456 CRM.Designer.UFFieldCanvasView = Backbone.Marionette.View.extend({
457 initialize: function() {
458 this.model.getRel('ufFieldCollection')
459 .on('add', this.updatePlaceholder, this)
460 .on('remove', this.updatePlaceholder, this)
461 .on('add', this.addUFFieldView, this);
462 },
463 onClose: function() {
464 this.model.getRel('ufFieldCollection')
465 .off('add', this.updatePlaceholder, this)
466 .off('remove', this.updatePlaceholder, this)
467 .off('add', this.addUFFieldView, this);
468 },
469 render: function() {
470 var ufFieldCanvasView = this;
471 this.$el.html(_.template($('#field_canvas_view_template').html()));
472
473 // BOTTOM: Setup field-level editing
474 var $fields = this.$('.crm-designer-fields');
475 this.updatePlaceholder();
476 var ufFieldModels = this.model.getRel('ufFieldCollection').sortBy(function(ufFieldModel) {
477 return parseInt(ufFieldModel.get('weight'));
478 });
479 _.each(ufFieldModels, function(ufFieldModel) {
480 ufFieldCanvasView.addUFFieldView(ufFieldModel, ufFieldCanvasView.model.getRel('ufFieldCollection'), {skipWeights: true});
481 });
482 this.$(".crm-designer-fields").sortable({
483 placeholder: 'crm-designer-row-placeholder',
484 forcePlaceholderSize: true,
485 receive: function(event, ui) {
486 var paletteFieldModel = ufFieldCanvasView.model.getRel('paletteFieldCollection').get(ui.item.attr('data-plm-cid'));
487 var ufFieldModel = paletteFieldModel.addToUFCollection(
488 ufFieldCanvasView.model.getRel('ufFieldCollection'),
489 {skipWeights: true}
490 );
491 if (null == ufFieldModel) {
492 ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').remove();
493 } else {
494 // Move from end to the 'dropped' position
495 var ufFieldViewEl = ufFieldCanvasView.$('div[data-field-cid='+ufFieldModel.cid+']').parent();
496 ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').replaceWith(ufFieldViewEl);
497 }
498 // note: the sortable() update callback will call updateWeight
499 },
500 update: function() {
501 ufFieldCanvasView.updateWeights();
502 }
503 });
504 },
505 /** Determine visual order of fields and set the model values for "weight" */
506 updateWeights: function() {
507 var ufFieldCanvasView = this;
508 var weight = 1;
509 var rows = this.$('.crm-designer-row').each(function(key, row) {
510 if ($(row).hasClass('placeholder')) {
511 return;
512 }
513 var ufFieldCid = $(row).attr('data-field-cid');
514 var ufFieldModel = ufFieldCanvasView.model.getRel('ufFieldCollection').get(ufFieldCid);
515 ufFieldModel.set('weight', weight);
516 weight++;
517 });
518 },
519 addUFFieldView: function(ufFieldModel, ufFieldCollection, options) {
520 var paletteFieldModel = this.model.getRel('paletteFieldCollection').getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
521 var ufFieldView = new CRM.Designer.UFFieldView({
522 el: $("<div></div>"),
523 model: ufFieldModel,
524 paletteFieldModel: paletteFieldModel
525 });
526 ufFieldView.render();
527 this.$('.crm-designer-fields').append(ufFieldView.$el);
528 if (! (options && options.skipWeights)) {
529 this.updateWeights();
530 }
531 },
532 updatePlaceholder: function() {
533 if (this.model.getRel('ufFieldCollection').isEmpty()) {
534 this.$('.placeholder').css({display: 'block', border: '0 none', cursor: 'default'});
535 } else {
536 this.$('.placeholder').hide();
537 }
538 }
539 });
540
541 /**
542 * options:
543 * - model: CRM.UF.UFFieldModel
544 * - paletteFieldModel: CRM.Designer.PaletteFieldModel
545 */
546 CRM.Designer.UFFieldView = Backbone.Marionette.Layout.extend({
547 serializeData: extendedSerializeData,
548 template: '#field_row_template',
549 expanded: false,
550 regions: {
551 summary: '.crm-designer-field-summary',
552 detail: '.crm-designer-field-detail'
553 },
554 events: {
555 "click .crm-designer-action-settings": 'doToggleForm',
556 "click .crm-designer-action-remove": 'doRemove'
557 },
558 modelEvents: {
559 "destroy": 'remove',
560 "change:is_duplicate": 'onChangeIsDuplicate'
561 },
562 onRender: function() {
563 this.summary.show(new CRM.Designer.UFFieldSummaryView({
564 model: this.model,
565 fieldSchema: this.model.getFieldSchema(),
566 paletteFieldModel: this.options.paletteFieldModel
567 }));
568 this.detail.show(new CRM.Designer.UFFieldDetailView({
569 model: this.model,
570 fieldSchema: this.model.getFieldSchema()
571 }));
572 this.onChangeIsDuplicate(this.model, this.model.get('is_duplicate'))
573 if (!this.expanded) {
574 this.detail.$el.hide();
575 }
576 var that = this;
577 CRM.designerApp.vent.on('formOpened', function(event) {
578 if (that.expanded && event != that.cid) {
579 that.doToggleForm(false);
580 }
581 });
582 },
583 doToggleForm: function(event) {
584 this.expanded = !this.expanded;
585 if (this.expanded && event !== false) {
586 CRM.designerApp.vent.trigger('formOpened', this.cid);
587 }
588 this.$el.toggleClass('crm-designer-open', this.expanded);
589 var $detail = this.detail.$el;
590 if (!this.expanded) {
591 $detail.toggle('blind', 250);
592 }
593 else {
594 var $canvas = $('.crm-designer-canvas');
595 var top = $canvas.offset().top;
596 $detail.slideDown({
597 duration: 250,
598 step: function(num, effect) {
599 // Scroll canvas to keep field details visible
600 if (effect.prop == 'height') {
601 if (effect.now + $detail.offset().top - top > $canvas.height() - 9) {
602 $canvas.scrollTop($canvas.scrollTop() + effect.now + $detail.offset().top - top - $canvas.height() + 9);
603 }
604 }
605 }
606 });
607 }
608 },
609 onChangeIsDuplicate: function(model, value, options) {
610 this.$el.toggleClass('crm-designer-duplicate', value);
611 },
612 doRemove: function(event) {
613 var that = this;
614 this.$el.hide(250, function() {
615 that.model.destroyLocal();
616 });
617 }
618 });
619
620 /**
621 * options:
622 * - model: CRM.UF.UFFieldModel
623 * - fieldSchema: (Backbone.Form schema element)
624 * - paletteFieldModel: CRM.Designer.PaletteFieldModel
625 */
626 CRM.Designer.UFFieldSummaryView = Backbone.Marionette.ItemView.extend({
627 serializeData: extendedSerializeData,
628 template: '#field_summary_template',
629 modelEvents: {
630 'change': 'render'
631 },
632
633 /**
634 * Compose a printable string which describes the binding of this UFField to the data model
635 * @return {String}
636 */
637 getBindingLabel: function() {
638 var result = this.options.paletteFieldModel.getSection().title + ": " + this.options.paletteFieldModel.getLabel();
639 if (this.options.fieldSchema.civiIsPhone) {
640 result = result + '-' + CRM.PseudoConstant.phoneType[this.model.get('phone_type_id')];
641 }
642 if (this.options.fieldSchema.civiIsLocation) {
643 var locType = this.model.get('location_type_id') ? CRM.PseudoConstant.locationType[this.model.get('location_type_id')] : ts('Primary');
644 result = result + ' (' + locType + ')';
645 }
646 return result;
647 },
648
649 /**
650 * Return a string marking if the field is required
651 * @return {String}
652 */
653 getRequiredMarker: function() {
654 if (this.model.get('is_required') == 1) {
655 return ' <span class="crm-marker">*</span> ';
656 }
657 return '';
658 },
659
660 onRender: function() {
661 this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
662 if (this.model.get("is_reserved") == 1) {
663 this.$('.crm-designer-buttons').hide();
664 }
665 }
666 });
667
668 /**
669 * options:
670 * - model: CRM.UF.UFFieldModel
671 * - fieldSchema: (Backbone.Form schema element)
672 */
673 CRM.Designer.UFFieldDetailView = Backbone.View.extend({
674 initialize: function() {
675 // FIXME: hide/display 'in_selector' if 'visibility' is one of the public options
676 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'];
677 if (! this.options.fieldSchema.civiIsLocation) {
678 fields = _.without(fields, 'location_type_id');
679 }
680 if (! this.options.fieldSchema.civiIsPhone) {
681 fields = _.without(fields, 'phone_type_id');
682 }
683 if (!this.options.fieldSchema.civiIsMultiple) {
684 fields = _.without(fields, 'is_multi_summary');
685 }
686
687 this.form = new Backbone.Form({
688 model: this.model,
689 fields: fields
690 });
691 this.form.on('change', this.onFormChange, this);
692 this.model.on('change', this.onModelChange, this);
693 },
694 render: function() {
695 this.$el.html(this.form.render().el);
696 this.onFormChange();
697 },
698 onModelChange: function() {
699 $.each(this.form.fields, function(i, field) {
700 this.form.setValue(field.key, this.model.get(field.key));
701 });
702 },
703 onFormChange: function() {
704 this.form.commit();
705 this.$('.field-is_multi_summary').toggle(this.options.fieldSchema.civiIsMultiple ? true : false);
706 this.$('.field-in_selector').toggle(this.model.isInSelectorAllowed());
707 // this.$(':input').attr('disabled', this.model.get("is_reserved") == 1);
708
709 if (!this.model.isInSelectorAllowed() && this.model.get('in_selector') != "0") {
710 this.model.set('in_selector', "0");
711 this.form.setValue('in_selector', "0");
712 // TODO: It might be nicer if we didn't completely discard in_selector -- e.g.
713 // if the value could be restored when the user isInSelectorAllowed becomes true
714 // again. However, I haven't found a simple way to do this.
715 }
716 }
717 });
718
719 /**
720 * options:
721 * - model: CRM.UF.UFGroupModel
722 */
723 CRM.Designer.UFGroupView = Backbone.Marionette.Layout.extend({
724 serializeData: extendedSerializeData,
725 template: '#form_row_template',
726 expanded: false,
727 regions: {
728 summary: '.crm-designer-form-summary',
729 detail: '.crm-designer-form-detail'
730 },
731 events: {
732 "click .crm-designer-action-settings": 'doToggleForm'
733 },
734 onRender: function() {
735 this.summary.show(new CRM.Designer.UFGroupSummaryView({
736 model: this.model
737 }));
738 this.detail.show(new CRM.Designer.UFGroupDetailView({
739 model: this.model
740 }));
741 if (!this.expanded) {
742 this.detail.$el.hide();
743 }
744 var that = this;
745 CRM.designerApp.vent.on('formOpened', function(event) {
746 if (that.expanded && event !== 0) {
747 that.doToggleForm(false);
748 }
749 });
750 },
751 doToggleForm: function(event) {
752 this.expanded = !this.expanded;
753 if (this.expanded && event !== false) {
754 CRM.designerApp.vent.trigger('formOpened', 0);
755 }
756 this.$el.toggleClass('crm-designer-open', this.expanded);
757 this.detail.$el.toggle('blind', 250);
758 }
759 });
760
761 /**
762 * options:
763 * - model: CRM.UF.UFGroupModel
764 */
765 CRM.Designer.UFGroupSummaryView = Backbone.Marionette.ItemView.extend({
766 serializeData: extendedSerializeData,
767 template: '#form_summary_template',
768 modelEvents: {
769 'change': 'render'
770 },
771 onRender: function() {
772 this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
773 if (this.model.get("is_reserved") == 1) {
774 this.$('.crm-designer-buttons').hide();
775 }
776 }
777 });
778
779 /**
780 * options:
781 * - model: CRM.UF.UFGroupModel
782 */
783 CRM.Designer.UFGroupDetailView = Backbone.View.extend({
784 initialize: function() {
785 this.form = new Backbone.Form({
786 model: this.model,
787 fields: ['title', 'help_pre', 'help_post', 'is_active']
788 });
789 this.form.on('change', this.form.commit, this.form);
790 },
791 render: function() {
792 this.$el.html(this.form.render().el);
793 }
794 });
795
796 })(cj);