245 lines
8.2 KiB
JavaScript
245 lines
8.2 KiB
JavaScript
// https://civicrm.org/licensing
|
|
(function($, _) {
|
|
"use strict";
|
|
/* jshint validthis: true */
|
|
|
|
// TODO: We'll need a way to clear this cache if options are edited.
|
|
// Maybe it should be stored in the CRM object so other parts of the app can use it.
|
|
// Note that if we do move it, we should also change the format of option lists to our standard sequential arrays
|
|
var optionsCache = {};
|
|
|
|
/**
|
|
* Helper fn to retrieve semantic data from markup
|
|
*/
|
|
$.fn.crmEditableEntity = function() {
|
|
var
|
|
el = this[0],
|
|
ret = {},
|
|
$row = this.first().closest('.crm-entity');
|
|
ret.entity = $row.data('entity') || $row[0].id.split('-')[0];
|
|
ret.id = $row.data('id') || $row[0].id.split('-')[1];
|
|
ret.action = $row.data('action') || 'create';
|
|
|
|
if (!ret.entity || !ret.id) {
|
|
return false;
|
|
}
|
|
$('.crm-editable, [data-field]', $row).each(function() {
|
|
var fieldName = $(this).data('field') || this.className.match(/crmf-(\S*)/)[1];
|
|
if (fieldName) {
|
|
ret[fieldName] = $(this).text();
|
|
if (this === el) {
|
|
ret.field = fieldName;
|
|
}
|
|
}
|
|
});
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* @see http://wiki.civicrm.org/confluence/display/CRMDOC/Structure+convention+for+automagic+edit+in+place
|
|
*/
|
|
$.fn.crmEditable = function(options) {
|
|
function checkable() {
|
|
$(this).off('.crmEditable').on('change.crmEditable', function() {
|
|
var $el = $(this),
|
|
info = $el.crmEditableEntity();
|
|
if (!info.field) {
|
|
return false;
|
|
}
|
|
var params = {
|
|
sequential: 1,
|
|
id: info.id,
|
|
field: info.field,
|
|
value: $el.is(':checked') ? 1 : 0
|
|
};
|
|
CRM.api3(info.entity, info.action, params, true);
|
|
});
|
|
}
|
|
|
|
return this.each(function() {
|
|
var $i,
|
|
fieldName = "",
|
|
defaults = {
|
|
error: function(entity, field, value, data) {
|
|
restoreContainer();
|
|
$(this).html(originalValue || settings.placeholder).click();
|
|
var msg = $.isPlainObject(data) && data.error_message;
|
|
errorMsg = $(':input', this).first().crmError(msg || ts('Sorry an error occurred and your information was not saved'), ts('Error'));
|
|
},
|
|
success: function(entity, field, value, data, settings) {
|
|
restoreContainer();
|
|
if ($i.data('refresh')) {
|
|
CRM.refreshParent($i);
|
|
} else {
|
|
value = value === '' ? settings.placeholder : _.escape(value);
|
|
$i.html(value);
|
|
}
|
|
}
|
|
},
|
|
originalValue = '',
|
|
errorMsg,
|
|
editableSettings = $.extend({}, defaults, options);
|
|
|
|
if ($(this).hasClass('crm-editable-enabled')) {
|
|
return;
|
|
}
|
|
|
|
if (this.nodeName == "INPUT" && this.type == "checkbox") {
|
|
checkable.call(this, this);
|
|
return;
|
|
}
|
|
|
|
// Table cell needs something inside it to look right
|
|
if ($(this).is('td')) {
|
|
$(this)
|
|
.removeClass('crm-editable')
|
|
.wrapInner('<div class="crm-editable" />');
|
|
$i = $('div.crm-editable', this)
|
|
.data($(this).data());
|
|
var field = this.className.match(/crmf-(\S*)/);
|
|
if (field) {
|
|
$i.data('field', field[1]);
|
|
}
|
|
}
|
|
else {
|
|
$i = $(this);
|
|
}
|
|
|
|
var settings = {
|
|
tooltip: $i.data('tooltip') || ts('Click to edit'),
|
|
placeholder: $i.data('placeholder') || '<i class="crm-i fa-pencil crm-editable-placeholder"></i>',
|
|
onblur: 'cancel',
|
|
cancel: '<button type="cancel"><i class="crm-i fa-times"></i></button>',
|
|
submit: '<button type="submit"><i class="crm-i fa-check"></i></button>',
|
|
cssclass: 'crm-editable-form',
|
|
data: getData,
|
|
onreset: restoreContainer
|
|
};
|
|
if ($i.data('type')) {
|
|
settings.type = $i.data('type');
|
|
if (settings.type == 'boolean') {
|
|
settings.type = 'select';
|
|
$i.data('options', {'0': ts('No'), '1': ts('Yes')});
|
|
}
|
|
}
|
|
if (settings.type == 'textarea') {
|
|
$i.addClass('crm-editable-textarea-enabled');
|
|
}
|
|
$i.addClass('crm-editable-enabled');
|
|
|
|
function callback(value, settings) {
|
|
$i.addClass('crm-editable-saving');
|
|
var
|
|
info = $i.crmEditableEntity(),
|
|
$el = $($i),
|
|
params = {},
|
|
action = $i.data('action') || info.action;
|
|
if (!info.field) {
|
|
return false;
|
|
}
|
|
if (info.id && info.id !== 'new') {
|
|
params.id = info.id;
|
|
}
|
|
if (action === 'setvalue') {
|
|
params.field = info.field;
|
|
params.value = value;
|
|
}
|
|
else {
|
|
params[info.field] = value;
|
|
}
|
|
CRM.api3(info.entity, action, params, {error: null})
|
|
.done(function(data) {
|
|
if (data.is_error) {
|
|
return editableSettings.error.call($el[0], info.entity, info.field, value, data);
|
|
}
|
|
if ($el.data('options')) {
|
|
value = $el.data('options')[value] || '';
|
|
}
|
|
else if ($el.data('optionsHashKey')) {
|
|
var options = optionsCache[$el.data('optionsHashKey')];
|
|
value = options && options[value] ? options[value] : '';
|
|
}
|
|
$el.trigger('crmFormSuccess', [value]);
|
|
editableSettings.success.call($el[0], info.entity, info.field, value, data, settings);
|
|
})
|
|
.fail(function(data) {
|
|
editableSettings.error.call($el[0], info.entity, info.field, value, data);
|
|
});
|
|
}
|
|
|
|
CRM.loadScript(CRM.config.resourceBase + 'packages/jquery/plugins/jquery.jeditable.min.js').done(function() {
|
|
$i.editable(callback, settings);
|
|
});
|
|
|
|
// CRM-15759 - Workaround broken textarea handling in jeditable 1.7.1
|
|
$i.click(function() {
|
|
$('textarea', this).off()
|
|
// Fix cancel-on-blur
|
|
.on('blur', function(e) {
|
|
if (!e.relatedTarget || !$(e.relatedTarget).is('.crm-editable-form button')) {
|
|
$i.find('button[type=cancel]').click();
|
|
}
|
|
})
|
|
// Add support for ctrl-enter shortcut key
|
|
.on('keydown', function (e) {
|
|
if (e.ctrlKey && e.keyCode == 13) {
|
|
$i.find('button[type=submit]').click();
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
|
|
function getData(value, settings) {
|
|
// Add css class to wrapper
|
|
// 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 :(
|
|
$i.addClass('crm-editable-editing');
|
|
|
|
originalValue = value;
|
|
|
|
if ($i.data('type') == 'select' || $i.data('type') == 'boolean') {
|
|
if ($i.data('options')) {
|
|
return formatOptions($i.data('options'));
|
|
}
|
|
var result,
|
|
info = $i.crmEditableEntity(),
|
|
// Strip extra id from multivalued custom fields
|
|
custom = info.field.match(/(custom_\d+)_\d+/),
|
|
field = custom ? custom[1] : info.field,
|
|
hash = info.entity + '.' + field,
|
|
params = {
|
|
field: field,
|
|
context: 'create'
|
|
};
|
|
$i.data('optionsHashKey', hash);
|
|
if (!optionsCache[hash]) {
|
|
$.ajax({
|
|
url: CRM.url('civicrm/ajax/rest'),
|
|
data: {entity: info.entity, action: 'getoptions', json: JSON.stringify(params)},
|
|
async: false, // jeditable lacks support for async options lookup
|
|
success: function(data) {optionsCache[hash] = data.values;}
|
|
});
|
|
}
|
|
return formatOptions(optionsCache[hash]);
|
|
}
|
|
// Unwrap contents then replace html special characters with plain text
|
|
return _.unescape(value.replace(/<(?:.|\n)*?>/gm, ''));
|
|
}
|
|
|
|
function formatOptions(options) {
|
|
if (typeof $i.data('emptyOption') === 'string') {
|
|
// Using 'null' because '' is broken in jeditable 1.7.1
|
|
return $.extend({'null': $i.data('emptyOption')}, options);
|
|
}
|
|
return options;
|
|
}
|
|
|
|
function restoreContainer() {
|
|
if (errorMsg && errorMsg.close) errorMsg.close();
|
|
$i.removeClass('crm-editable-saving crm-editable-editing');
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
})(jQuery, CRM._);
|