1 // https://civicrm.org/licensing
4 /* jshint validthis: true */
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
12 * Helper fn to retrieve semantic data from markup
14 $.fn
.crmEditableEntity = function() {
18 $row
= this.first().closest('.crm-entity');
19 ret
.entity
= $row
.data('entity') || $row
[0].id
.split('-')[0];
20 ret
.id
= $row
.data('id') || $row
[0].id
.split('-')[1];
21 ret
.action
= $row
.data('action') || 'create';
23 if (!ret
.entity
|| !ret
.id
) {
26 $('.crm-editable, [data-field]', $row
).each(function() {
27 var fieldName
= $(this).data('field') || this.className
.match(/crmf-(\S*)/)[1];
29 ret
[fieldName
] = $(this).text();
31 ret
.field
= fieldName
;
32 ret
.params
= $(this).data('params');
40 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/Structure+convention+for+automagic+edit+in+place
42 $.fn
.crmEditable = function(options
) {
43 function checkable() {
44 $(this).off('.crmEditable').on('change.crmEditable', function() {
46 info
= $el
.crmEditableEntity();
54 value
: $el
.is(':checked') ? 1 : 0
56 CRM
.api3(info
.entity
, info
.action
, params
, true);
60 return this.each(function() {
64 error: function(entity
, field
, value
, data
) {
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'));
70 success: function(entity
, field
, value
, data
, settings
) {
72 if ($i
.data('refresh')) {
73 CRM
.refreshParent($i
);
75 value
= value
=== '' ? settings
.placeholder
: _
.escape(value
);
82 editableSettings
= $.extend({}, defaults
, options
);
84 if ($(this).hasClass('crm-editable-enabled')) {
88 if (this.nodeName
== "INPUT" && this.type
== "checkbox") {
89 checkable
.call(this, this);
93 // Table cell needs something inside it to look right
94 if ($(this).is('td')) {
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*)/);
102 $i
.data('field', field
[1]);
110 tooltip
: $i
.data('tooltip') || ts('Click to edit'),
111 placeholder
: $i
.data('placeholder') || '<i class="crm-i fa-pencil crm-editable-placeholder"></i>',
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>',
115 cssclass
: 'crm-editable-form',
117 onreset
: restoreContainer
119 if ($i
.data('type')) {
120 settings
.type
= $i
.data('type');
121 if (settings
.type
== 'boolean') {
122 settings
.type
= 'select';
123 $i
.data('options', {'0': ts('No'), '1': ts('Yes')});
126 if (settings
.type
== 'textarea') {
127 $i
.addClass('crm-editable-textarea-enabled');
129 $i
.addClass('crm-editable-enabled');
131 function callback(value
, settings
) {
132 $i
.addClass('crm-editable-saving');
134 info
= $i
.crmEditableEntity(),
136 params
= info
.params
|| {},
137 action
= $i
.data('action') || info
.action
;
141 if (info
.id
&& info
.id
!== 'new') {
144 if (action
=== 'setvalue') {
145 params
.field
= info
.field
;
146 params
.value
= value
;
149 params
[info
.field
] = value
;
151 CRM
.api3(info
.entity
, action
, params
, {error
: null})
152 .done(function(data
) {
154 return editableSettings
.error
.call($el
[0], info
.entity
, info
.field
, value
, data
);
156 if ($el
.data('options')) {
157 value
= $el
.data('options')[value
] || '';
159 else if ($el
.data('optionsHashKey')) {
160 var options
= optionsCache
[$el
.data('optionsHashKey')];
161 value
= options
&& options
[value
] ? options
[value
] : '';
163 $el
.trigger('crmFormSuccess', [value
]);
164 editableSettings
.success
.call($el
[0], info
.entity
, info
.field
, value
, data
, settings
);
166 .fail(function(data
) {
167 editableSettings
.error
.call($el
[0], info
.entity
, info
.field
, value
, data
);
171 CRM
.loadScript(CRM
.config
.packagesBase
+ 'jquery/plugins/jquery.jeditable.min.js').done(function() {
172 $i
.editable(callback
, settings
);
175 // CRM-15759 - Workaround broken textarea handling in jeditable 1.7.1
176 $i
.click(function() {
177 $('textarea', this).off()
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();
184 // Add support for ctrl-enter shortcut key
185 .on('keydown', function (e
) {
186 if (e
.ctrlKey
&& e
.keyCode
== 13) {
187 $i
.find('button[type=submit]').click();
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');
198 originalValue
= value
;
200 if ($i
.data('type') == 'select' || $i
.data('type') == 'boolean') {
201 if ($i
.data('options')) {
202 return formatOptions($i
.data('options'));
205 info
= $i
.crmEditableEntity(),
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
,
214 $i
.data('optionsHashKey', hash
);
215 if (!optionsCache
[hash
]) {
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
;}
223 return formatOptions(optionsCache
[hash
]);
225 // Unwrap contents then replace html special characters with plain text
226 return _
.unescape(value
.replace(/<(?:.|\n)*?>/gm, ''));
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
);
237 function restoreContainer() {
238 if (errorMsg
&& errorMsg
.close
) errorMsg
.close();
239 $i
.removeClass('crm-editable-saving crm-editable-editing');