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; | |
6a488035 | 32 | } |
6a488035 | 33 | } |
d4838efc CW |
34 | }); |
35 | return ret; | |
36 | }; | |
6a488035 | 37 | |
b7cf988c CW |
38 | /** |
39 | * @see http://wiki.civicrm.org/confluence/display/CRMDOC/Structure+convention+for+automagic+edit+in+place | |
40 | */ | |
99d24adf | 41 | $.fn.crmEditable = function(options) { |
9d0d4076 CW |
42 | function checkable() { |
43 | $(this).off('.crmEditable').on('change.crmEditable', function() { | |
b7cf988c CW |
44 | var $el = $(this), |
45 | info = $el.crmEditableEntity(); | |
99d24adf CW |
46 | if (!info.field) { |
47 | return false; | |
48 | } | |
99d24adf CW |
49 | var params = { |
50 | sequential: 1, | |
51 | id: info.id, | |
52 | field: info.field, | |
b7cf988c | 53 | value: $el.is(':checked') ? 1 : 0 |
99d24adf | 54 | }; |
47737104 | 55 | CRM.api3(info.entity, info.action, params, true); |
99d24adf | 56 | }); |
9d0d4076 | 57 | } |
99d24adf | 58 | |
99d24adf | 59 | return this.each(function() { |
b7cf988c | 60 | var $i, |
47737104 CW |
61 | fieldName = "", |
62 | defaults = { | |
63 | error: function(entity, field, value, data) { | |
64 | restoreContainer(); | |
65 | $(this).html(originalValue || settings.placeholder).click(); | |
66 | var msg = $.isPlainObject(data) && data.error_message; | |
67 | errorMsg = $(':input', this).first().crmError(msg || ts('Sorry an error occurred and your information was not saved'), ts('Error')); | |
68 | }, | |
69 | success: function(entity, field, value, data, settings) { | |
70 | restoreContainer(); | |
71 | if ($i.data('refresh')) { | |
72 | CRM.refreshParent($i); | |
73 | } else { | |
bb8d953e | 74 | value = value === '' ? settings.placeholder : _.escape(value); |
47737104 CW |
75 | $i.html(value); |
76 | } | |
77 | } | |
78 | }, | |
79 | originalValue = '', | |
80 | errorMsg, | |
81 | editableSettings = $.extend({}, defaults, options); | |
6a488035 | 82 | |
9d0d4076 CW |
83 | if ($(this).hasClass('crm-editable-enabled')) { |
84 | return; | |
85 | } | |
86 | ||
99d24adf CW |
87 | if (this.nodeName == "INPUT" && this.type == "checkbox") { |
88 | checkable.call(this, this); | |
89 | return; | |
90 | } | |
6a488035 | 91 | |
b7cf988c CW |
92 | // Table cell needs something inside it to look right |
93 | if ($(this).is('td')) { | |
94 | $(this) | |
95 | .removeClass('crm-editable') | |
96 | .wrapInner('<div class="crm-editable" />'); | |
97 | $i = $('div.crm-editable', this) | |
98 | .data($(this).data()); | |
99 | var field = this.className.match(/crmf-(\S*)/); | |
100 | if (field) { | |
101 | $i.data('field', field[1]); | |
102 | } | |
103 | } | |
104 | else { | |
105 | $i = $(this); | |
106 | } | |
107 | ||
99d24adf | 108 | var settings = { |
b7cf988c CW |
109 | tooltip: $i.data('tooltip') || ts('Click to edit'), |
110 | placeholder: $i.data('placeholder') || '<span class="crm-editable-placeholder">' + ts('Click to edit') + '</span>', | |
111 | onblur: 'cancel', | |
112 | cancel: '<button type="cancel"><span class="ui-icon ui-icon-closethick"></span></button>', | |
113 | submit: '<button type="submit"><span class="ui-icon ui-icon-check"></span></button>', | |
114 | cssclass: 'crm-editable-form', | |
b48d66ce CW |
115 | data: getData, |
116 | onreset: restoreContainer | |
99d24adf | 117 | }; |
99d24adf CW |
118 | if ($i.data('type')) { |
119 | settings.type = $i.data('type'); | |
9d0d4076 CW |
120 | if (settings.type == 'boolean') { |
121 | settings.type = 'select'; | |
122 | $i.data('options', {'0': ts('No'), '1': ts('Yes')}); | |
123 | } | |
99d24adf | 124 | } |
99d24adf CW |
125 | if (settings.type == 'textarea') { |
126 | $i.addClass('crm-editable-textarea-enabled'); | |
127 | } | |
9d0d4076 | 128 | $i.addClass('crm-editable-enabled'); |
99d24adf CW |
129 | |
130 | $i.editable(function(value, settings) { | |
131 | $i.addClass('crm-editable-saving'); | |
132 | var | |
133 | info = $i.crmEditableEntity(), | |
b7cf988c | 134 | $el = $($i), |
99d24adf | 135 | params = {}, |
2d221ef7 | 136 | action = $i.data('action') || info.action; |
99d24adf CW |
137 | if (!info.field) { |
138 | return false; | |
6a488035 | 139 | } |
99d24adf CW |
140 | if (info.id && info.id !== 'new') { |
141 | params.id = info.id; | |
6a488035 | 142 | } |
99d24adf CW |
143 | if (action === 'setvalue') { |
144 | params.field = info.field; | |
145 | params.value = value; | |
6a488035 | 146 | } |
99d24adf CW |
147 | else { |
148 | params[info.field] = value; | |
6a488035 | 149 | } |
47737104 | 150 | CRM.api3(info.entity, action, params, {error: null}) |
b7cf988c | 151 | .done(function(data) { |
47737104 CW |
152 | if (data.is_error) { |
153 | return editableSettings.error.call($el[0], info.entity, info.field, value, data); | |
154 | } | |
b7cf988c | 155 | if ($el.data('options')) { |
b48d66ce | 156 | value = $el.data('options')[value] || ''; |
6a488035 | 157 | } |
9d0d4076 CW |
158 | else if ($el.data('optionsHashKey')) { |
159 | var options = optionsCache[$el.data('optionsHashKey')]; | |
b48d66ce | 160 | value = options && options[value] ? options[value] : ''; |
9d0d4076 | 161 | } |
b7cf988c CW |
162 | $el.trigger('crmFormSuccess'); |
163 | editableSettings.success.call($el[0], info.entity, info.field, value, data, settings); | |
164 | }) | |
165 | .fail(function(data) { | |
166 | editableSettings.error.call($el[0], info.entity, info.field, value, data); | |
167 | }); | |
99d24adf | 168 | }, settings); |
b7cf988c CW |
169 | |
170 | // CRM-15759 - Workaround broken textarea handling in jeditable 1.7.1 | |
171 | $i.click(function() { | |
172 | $('textarea', this).off() | |
2361c2e8 CW |
173 | // Fix cancel-on-blur |
174 | .on('blur', function(e) { | |
175 | if (!e.relatedTarget || !$(e.relatedTarget).is('.crm-editable-form button')) { | |
176 | $i.find('button[type=cancel]').click(); | |
177 | } | |
b7cf988c | 178 | }) |
2361c2e8 | 179 | // Add support for ctrl-enter shortcut key |
b7cf988c CW |
180 | .on('keydown', function (e) { |
181 | if (e.ctrlKey && e.keyCode == 13) { | |
b7cf988c CW |
182 | $i.find('button[type=submit]').click(); |
183 | e.preventDefault(); | |
184 | } | |
185 | }); | |
186 | }); | |
b48d66ce CW |
187 | |
188 | function getData(value, settings) { | |
189 | // Add css class to wrapper | |
190 | // 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 :( | |
191 | $i.addClass('crm-editable-editing'); | |
192 | ||
47737104 CW |
193 | originalValue = value; |
194 | ||
b48d66ce CW |
195 | if ($i.data('type') == 'select' || $i.data('type') == 'boolean') { |
196 | if ($i.data('options')) { | |
197 | return formatOptions($i.data('options')); | |
198 | } | |
199 | var result, | |
200 | info = $i.crmEditableEntity(), | |
a5eff6ef CW |
201 | // Strip extra id from multivalued custom fields |
202 | custom = info.field.match(/(custom_\d+)_\d+/), | |
203 | field = custom ? custom[1] : info.field, | |
204 | hash = info.entity + '.' + field, | |
b48d66ce | 205 | params = { |
a5eff6ef | 206 | field: field, |
b48d66ce CW |
207 | context: 'create' |
208 | }; | |
209 | $i.data('optionsHashKey', hash); | |
741b3bfb CW |
210 | if (!optionsCache[hash]) { |
211 | $.ajax({ | |
212 | url: CRM.url('civicrm/ajax/rest'), | |
213 | data: {entity: info.entity, action: 'getoptions', json: JSON.stringify(params)}, | |
214 | async: false, // jeditable lacks support for async options lookup | |
215 | success: function(data) {optionsCache[hash] = data.values;} | |
216 | }); | |
b48d66ce | 217 | } |
b48d66ce | 218 | return formatOptions(optionsCache[hash]); |
b48d66ce | 219 | } |
bb8d953e CW |
220 | // Unwrap contents then replace html special characters with plain text |
221 | return _.unescape(value.replace(/<(?:.|\n)*?>/gm, '')); | |
b48d66ce CW |
222 | } |
223 | ||
224 | function formatOptions(options) { | |
225 | if (typeof $i.data('emptyOption') === 'string') { | |
226 | // Using 'null' because '' is broken in jeditable 1.7.1 | |
227 | return $.extend({'null': $i.data('emptyOption')}, options); | |
228 | } | |
229 | return options; | |
230 | } | |
231 | ||
232 | function restoreContainer() { | |
731a8f87 | 233 | if (errorMsg && errorMsg.close) errorMsg.close(); |
47737104 | 234 | $i.removeClass('crm-editable-saving crm-editable-editing'); |
b48d66ce CW |
235 | } |
236 | ||
6a488035 TO |
237 | }); |
238 | }; | |
239 | ||
18afc30c | 240 | $(document).on('crmLoad', function(e) { |
21d4ed8c | 241 | $('.crm-editable', e.target).not('thead *').crmEditable(); |
1a40fe56 | 242 | }); |
18afc30c | 243 | |
bb8d953e | 244 | })(jQuery, CRM._); |