Commit | Line | Data |
---|---|---|
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._); |