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