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