Merge pull request #13051 from mlutfy/customvalue-checkbox-display
[civicrm-core.git] / js / jquery / jquery.crmEditable.js
CommitLineData
b7cf988c 1// https://civicrm.org/licensing
bb8d953e 2(function($, _) {
731a8f87
CW
3 "use strict";
4 /* jshint validthis: true */
5
9d0d4076
CW
6 // TODO: We'll need a way to clear this cache if options are edited.
7 // Maybe it should be stored in the CRM object so other parts of the app can use it.
8 // Note that if we do move it, we should also change the format of option lists to our standard sequential arrays
9 var optionsCache = {};
10
b7cf988c
CW
11 /**
12 * Helper fn to retrieve semantic data from markup
13 */
d4838efc
CW
14 $.fn.crmEditableEntity = function() {
15 var
16 el = this[0],
17 ret = {},
18 $row = this.first().closest('.crm-entity');
4f1c81da 19 ret.entity = $row.data('entity') || $row[0].id.split('-')[0];
20 ret.id = $row.data('id') || $row[0].id.split('-')[1];
1726c7d2 21 ret.action = $row.data('action') || 'create';
4f1c81da 22
d4838efc
CW
23 if (!ret.entity || !ret.id) {
24 return false;
25 }
60158442 26 $('.crm-editable, [data-field]', $row).each(function() {
d4838efc
CW
27 var fieldName = $(this).data('field') || this.className.match(/crmf-(\S*)/)[1];
28 if (fieldName) {
29 ret[fieldName] = $(this).text();
30 if (this === el) {
31 ret.field = fieldName;
d6f68831 32 ret.params = $(this).data('params');
6a488035 33 }
6a488035 34 }
d4838efc
CW
35 });
36 return ret;
37 };
6a488035 38
b7cf988c
CW
39 /**
40 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/Structure+convention+for+automagic+edit+in+place
41 */
99d24adf 42 $.fn.crmEditable = function(options) {
9d0d4076
CW
43 function checkable() {
44 $(this).off('.crmEditable').on('change.crmEditable', function() {
b7cf988c
CW
45 var $el = $(this),
46 info = $el.crmEditableEntity();
99d24adf
CW
47 if (!info.field) {
48 return false;
49 }
99d24adf
CW
50 var params = {
51 sequential: 1,
52 id: info.id,
53 field: info.field,
b7cf988c 54 value: $el.is(':checked') ? 1 : 0
99d24adf 55 };
47737104 56 CRM.api3(info.entity, info.action, params, true);
99d24adf 57 });
9d0d4076 58 }
99d24adf 59
99d24adf 60 return this.each(function() {
b7cf988c 61 var $i,
47737104
CW
62 fieldName = "",
63 defaults = {
64 error: function(entity, field, value, data) {
65 restoreContainer();
66 $(this).html(originalValue || settings.placeholder).click();
67 var msg = $.isPlainObject(data) && data.error_message;
68 errorMsg = $(':input', this).first().crmError(msg || ts('Sorry an error occurred and your information was not saved'), ts('Error'));
69 },
70 success: function(entity, field, value, data, settings) {
71 restoreContainer();
72 if ($i.data('refresh')) {
73 CRM.refreshParent($i);
74 } else {
bb8d953e 75 value = value === '' ? settings.placeholder : _.escape(value);
47737104
CW
76 $i.html(value);
77 }
78 }
79 },
80 originalValue = '',
81 errorMsg,
82 editableSettings = $.extend({}, defaults, options);
6a488035 83
9d0d4076
CW
84 if ($(this).hasClass('crm-editable-enabled')) {
85 return;
86 }
87
99d24adf
CW
88 if (this.nodeName == "INPUT" && this.type == "checkbox") {
89 checkable.call(this, this);
90 return;
91 }
6a488035 92
b7cf988c
CW
93 // Table cell needs something inside it to look right
94 if ($(this).is('td')) {
95 $(this)
96 .removeClass('crm-editable')
97 .wrapInner('<div class="crm-editable" />');
98 $i = $('div.crm-editable', this)
99 .data($(this).data());
100 var field = this.className.match(/crmf-(\S*)/);
101 if (field) {
102 $i.data('field', field[1]);
103 }
104 }
105 else {
106 $i = $(this);
107 }
108
99d24adf 109 var settings = {
b7cf988c 110 tooltip: $i.data('tooltip') || ts('Click to edit'),
10e8c74f 111 placeholder: $i.data('placeholder') || '<i class="crm-i fa-pencil crm-editable-placeholder"></i>',
b7cf988c 112 onblur: 'cancel',
972bd897
AH
113 cancel: '<button type="cancel"><i class="crm-i fa-times"></i></button>',
114 submit: '<button type="submit"><i class="crm-i fa-check"></i></button>',
b7cf988c 115 cssclass: 'crm-editable-form',
b48d66ce
CW
116 data: getData,
117 onreset: restoreContainer
99d24adf 118 };
99d24adf
CW
119 if ($i.data('type')) {
120 settings.type = $i.data('type');
9d0d4076
CW
121 if (settings.type == 'boolean') {
122 settings.type = 'select';
123 $i.data('options', {'0': ts('No'), '1': ts('Yes')});
124 }
99d24adf 125 }
99d24adf
CW
126 if (settings.type == 'textarea') {
127 $i.addClass('crm-editable-textarea-enabled');
128 }
9d0d4076 129 $i.addClass('crm-editable-enabled');
99d24adf 130
30b7642c 131 function callback(value, settings) {
99d24adf
CW
132 $i.addClass('crm-editable-saving');
133 var
134 info = $i.crmEditableEntity(),
b7cf988c 135 $el = $($i),
a93e98fc 136 params = info.params || {},
2d221ef7 137 action = $i.data('action') || info.action;
99d24adf
CW
138 if (!info.field) {
139 return false;
6a488035 140 }
99d24adf
CW
141 if (info.id && info.id !== 'new') {
142 params.id = info.id;
6a488035 143 }
99d24adf
CW
144 if (action === 'setvalue') {
145 params.field = info.field;
146 params.value = value;
6a488035 147 }
99d24adf
CW
148 else {
149 params[info.field] = value;
6a488035 150 }
47737104 151 CRM.api3(info.entity, action, params, {error: null})
b7cf988c 152 .done(function(data) {
47737104
CW
153 if (data.is_error) {
154 return editableSettings.error.call($el[0], info.entity, info.field, value, data);
155 }
b7cf988c 156 if ($el.data('options')) {
b48d66ce 157 value = $el.data('options')[value] || '';
6a488035 158 }
9d0d4076
CW
159 else if ($el.data('optionsHashKey')) {
160 var options = optionsCache[$el.data('optionsHashKey')];
b48d66ce 161 value = options && options[value] ? options[value] : '';
9d0d4076 162 }
3aafea46 163 $el.trigger('crmFormSuccess', [value]);
b7cf988c
CW
164 editableSettings.success.call($el[0], info.entity, info.field, value, data, settings);
165 })
166 .fail(function(data) {
167 editableSettings.error.call($el[0], info.entity, info.field, value, data);
168 });
30b7642c
CW
169 }
170
171 CRM.loadScript(CRM.config.resourceBase + 'packages/jquery/plugins/jquery.jeditable.min.js').done(function() {
172 $i.editable(callback, settings);
173 });
b7cf988c
CW
174
175 // CRM-15759 - Workaround broken textarea handling in jeditable 1.7.1
176 $i.click(function() {
177 $('textarea', this).off()
2361c2e8
CW
178 // Fix cancel-on-blur
179 .on('blur', function(e) {
180 if (!e.relatedTarget || !$(e.relatedTarget).is('.crm-editable-form button')) {
181 $i.find('button[type=cancel]').click();
182 }
b7cf988c 183 })
2361c2e8 184 // Add support for ctrl-enter shortcut key
b7cf988c
CW
185 .on('keydown', function (e) {
186 if (e.ctrlKey && e.keyCode == 13) {
b7cf988c
CW
187 $i.find('button[type=submit]').click();
188 e.preventDefault();
189 }
190 });
191 });
b48d66ce
CW
192
193 function getData(value, settings) {
194 // Add css class to wrapper
195 // FIXME: This should be a response to an event instead of coupled with this function but jeditable 1.7.1 doesn't trigger any events :(
196 $i.addClass('crm-editable-editing');
197
47737104
CW
198 originalValue = value;
199
b48d66ce
CW
200 if ($i.data('type') == 'select' || $i.data('type') == 'boolean') {
201 if ($i.data('options')) {
202 return formatOptions($i.data('options'));
203 }
204 var result,
205 info = $i.crmEditableEntity(),
a5eff6ef
CW
206 // Strip extra id from multivalued custom fields
207 custom = info.field.match(/(custom_\d+)_\d+/),
208 field = custom ? custom[1] : info.field,
209 hash = info.entity + '.' + field,
b48d66ce 210 params = {
a5eff6ef 211 field: field,
b48d66ce
CW
212 context: 'create'
213 };
214 $i.data('optionsHashKey', hash);
741b3bfb
CW
215 if (!optionsCache[hash]) {
216 $.ajax({
217 url: CRM.url('civicrm/ajax/rest'),
218 data: {entity: info.entity, action: 'getoptions', json: JSON.stringify(params)},
219 async: false, // jeditable lacks support for async options lookup
220 success: function(data) {optionsCache[hash] = data.values;}
221 });
b48d66ce 222 }
b48d66ce 223 return formatOptions(optionsCache[hash]);
b48d66ce 224 }
bb8d953e
CW
225 // Unwrap contents then replace html special characters with plain text
226 return _.unescape(value.replace(/<(?:.|\n)*?>/gm, ''));
b48d66ce
CW
227 }
228
229 function formatOptions(options) {
230 if (typeof $i.data('emptyOption') === 'string') {
231 // Using 'null' because '' is broken in jeditable 1.7.1
232 return $.extend({'null': $i.data('emptyOption')}, options);
233 }
234 return options;
235 }
236
237 function restoreContainer() {
731a8f87 238 if (errorMsg && errorMsg.close) errorMsg.close();
47737104 239 $i.removeClass('crm-editable-saving crm-editable-editing');
b48d66ce
CW
240 }
241
6a488035
TO
242 });
243 };
244
bb8d953e 245})(jQuery, CRM._);