* Populate a select list, overwriting the existing options except for the placeholder.
* @param select jquery selector - 1 or more select elements
* @param options array in format returned by api.getoptions
- * @param placeholder string
+ * @param placeholder string|bool - new placeholder or false (default) to keep the old one
+ * @param value string|array - will silently update the element with new value without triggering change
*/
- CRM.utils.setOptions = function(select, options, placeholder) {
+ CRM.utils.setOptions = function(select, options, placeholder, value) {
$(select).each(function() {
var
$elect = $(this),
- val = $elect.val() || [],
- opts = placeholder || placeholder === '' ? '' : '[value!=""]',
- newOptions = '',
- theme = function(options) {
- _.each(options, function(option) {
- if (option.children) {
- newOptions += '<optgroup label="' + option.value + '">';
- theme(option.children);
- newOptions += '</optgroup>';
- } else {
- var selected = ($.inArray('' + option.key, val) > -1) ? 'selected="selected"' : '';
- newOptions += '<option value="' + option.key + '"' + selected + '>' + option.value + '</option>';
- }
- });
- };
- if (!$.isArray(val)) {
- val = [val];
- }
+ val = value || $elect.val() || [],
+ opts = placeholder || placeholder === '' ? '' : '[value!=""]';
$elect.find('option' + opts).remove();
- theme(options);
+ var newOptions = CRM.utils.renderOptions(options, val);
if (typeof placeholder === 'string') {
if ($elect.is('[multiple]')) {
select.attr('placeholder', placeholder);
}
}
$elect.append(newOptions);
- $elect.trigger('crmOptionsUpdated', $.extend({}, options)).trigger('change');
+ if (!value) {
+ $elect.trigger('crmOptionsUpdated', $.extend({}, options)).trigger('change');
+ }
+ });
+ };
+
+ /**
+ * Render an option list
+ * @param options {array}
+ * @param val {string} default value
+ * @param escapeHtml {bool}
+ * @return string
+ */
+ CRM.utils.renderOptions = function(options, val, escapeHtml) {
+ var rendered = '',
+ esc = escapeHtml === false ? _.identity : _.escape;
+ if (!$.isArray(val)) {
+ val = [val];
+ }
+ _.each(options, function(option) {
+ if (option.children) {
+ rendered += '<optgroup label="' + esc(option.value) + '">' +
+ CRM.utils.renderOptions(option.children, val) +
+ '</optgroup>';
+ } else {
+ var selected = ($.inArray('' + option.key, val) > -1) ? 'selected="selected"' : '';
+ rendered += '<option value="' + esc(option.key) + '"' + selected + '>' + esc(option.value) + '</option>';
+ }
});
+ return rendered;
};
function chainSelect() {
$el.data('select-params', $.extend({}, $el.data('select-params') || {}, options.select));
$el.data('api-params', $.extend({}, $el.data('api-params') || {}, options.api));
$el.data('create-links', options.create || $el.data('create-links'));
- $el.addClass('crm-form-entityref crm-' + entity + '-ref');
+ $el.addClass('crm-form-entityref crm-' + entity.toLowerCase() + '-ref');
var settings = {
- // Use select2 ajax helper instead of CRM.api because it provides more value
+ // Use select2 ajax helper instead of CRM.api3 because it provides more value
ajax: {
url: CRM.url('civicrm/ajax/rest'),
data: function (input, page_num) {
- var params = $el.data('api-params') || {};
+ var params = getEntityRefApiParams($el);
params.input = input;
params.page_num = page_num;
return {
}
}
};
- if ($el.data('create-links') && entity.toLowerCase() === 'contact') {
- selectParams.formatInputTooShort = function() {
- var txt = $el.data('select-params').formatInputTooShort || $.fn.select2.defaults.formatInputTooShort.call(this);
- if ($el.data('create-links') && CRM.profileCreate && CRM.profileCreate.length) {
- txt += ' ' + ts('or') + '<br />' + formatSelect2CreateLinks($el);
- }
- return txt;
- };
- selectParams.formatNoMatches = function() {
- var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches;
- return txt + (CRM.profileCreate ? ('<br />' + formatSelect2CreateLinks($el)) : '');
- };
- $el.on('select2-open.crmEntity', function() {
- var $el = $(this);
- $('#select2-drop').off('.crmEntity').on('click.crmEntity', 'a.crm-add-entity', function(e) {
- $el.select2('close');
- CRM.loadForm($(this).attr('href'), {
- dialog: {width: 500, height: 'auto'}
- }).on('crmFormSuccess', function(e, data) {
- if (data.status === 'success' && data.id) {
- CRM.status(ts('%1 Created', {1: data.label}));
- if ($el.select2('container').hasClass('select2-container-multi')) {
- var selection = $el.select2('data');
- selection.push(data);
- $el.select2('data', selection, true);
- } else {
- $el.select2('data', data, true);
- }
- }
- });
- return false;
- });
- });
- }
// Create new items inline - works for tags
- else if ($el.data('create-links')) {
+ if ($el.data('create-links') && entity.toLowerCase() === 'tag') {
selectParams.createSearchChoice = function(term, data) {
if (!_.findKey(data, {label: term})) {
return {id: "0", term: term, label: term + ' (' + ts('new tag') + ')'};
};
selectParams.tokenSeparators = [','];
selectParams.createSearchChoicePosition = 'bottom';
- }
- $el.crmSelect2($.extend(settings, $el.data('select-params'), selectParams))
- .on('select2-selecting.crmEntity', function(e) {
+ $el.on('select2-selecting.crmEntity', function(e) {
if (e.val === "0") {
// Create a new term
e.object.label = e.object.term;
});
}
});
+ }
+ else {
+ selectParams.formatInputTooShort = function() {
+ var txt = $el.data('select-params').formatInputTooShort || $.fn.select2.defaults.formatInputTooShort.call(this);
+ txt += renderEntityRefFilters($el) + renderEntityRefCreateLinks($el);
+ return txt;
+ };
+ selectParams.formatNoMatches = function() {
+ var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches;
+ txt += renderEntityRefFilters($el) + renderEntityRefCreateLinks($el);
+ return txt;
+ };
+ $el.on('select2-open.crmEntity', function() {
+ var $el = $(this);
+ loadEntityRefFilterOptions($el);
+ $('#select2-drop')
+ .off('.crmEntity')
+ .on('click.crmEntity', 'a.crm-add-entity', function(e) {
+ $el.select2('close');
+ CRM.loadForm($(this).attr('href'), {
+ dialog: {width: 500, height: 'auto'}
+ }).on('crmFormSuccess', function(e, data) {
+ if (data.status === 'success' && data.id) {
+ CRM.status(ts('%1 Created', {1: data.label}));
+ if ($el.select2('container').hasClass('select2-container-multi')) {
+ var selection = $el.select2('data');
+ selection.push(data);
+ $el.select2('data', selection, true);
+ } else {
+ $el.select2('data', data, true);
+ }
+ }
+ });
+ return false;
+ })
+ .on('change.crmEntity', 'select.crm-entityref-filter-value', function() {
+ var filter = $el.data('user-filter') || {};
+ filter.value = $(this).val();
+ $(this).toggleClass('active', !!filter.value);
+ $el.data('user-filter', filter);
+ if (filter.value) {
+ // Once a filter has been chosen, rerender create links and refocus the search box
+ $el.select2('close');
+ $el.select2('open');
+ }
+ })
+ .on('change.crmEntity', 'select.crm-entityref-filter-key', function() {
+ var filter = $el.data('user-filter') || {};
+ filter.key = $(this).val();
+ $(this).toggleClass('active', !!filter.key);
+ $el.data('user-filter', filter);
+ loadEntityRefFilterOptions($el);
+ });
+ });
+ }
+ $el.crmSelect2($.extend(settings, $el.data('select-params'), selectParams));
});
};
+ /**
+ * Combine api-params with user-filter
+ * @param $el
+ * @returns {*}
+ */
+ function getEntityRefApiParams($el) {
+ var
+ params = $.extend({params: {}}, $el.data('api-params') || {}),
+ // Prevent original data from being modified - $.extend and _.clone don't cut it, they pass nested objects by reference!
+ combined = _.cloneDeep(params),
+ filter = $.extend({}, $el.data('user-filter') || {});
+ if (filter.key && filter.value) {
+ // Special case for contact type/sub-type combo
+ if (filter.key === 'contact_type' && (filter.value.indexOf('.') > 0)) {
+ combined.params.contact_type = filter.value.split('.')[0];
+ combined.params.contact_sub_type = filter.value.split('.')[1];
+ } else {
+ // Allow json-encoded api filters e.g. {"BETWEEN":[123,456]}
+ combined.params[filter.key] = filter.value.charAt(0) === '{' ? $.parseJSON(filter.value) : filter.value;
+ }
+ }
+ return combined;
+ }
+
CRM.utils.formatSelect2Result = function (row) {
var markup = '<div class="crm-select2-row">';
if (row.image !== undefined) {
return markup;
};
- function formatSelect2CreateLinks($el) {
+ function renderEntityRefCreateLinks($el) {
var
createLinks = $el.data('create-links'),
- api = $el.data('api-params') || {},
- type = api.params ? api.params.contact_type : null;
+ params = getEntityRefApiParams($el).params,
+ markup = '<div class="crm-entityref-links">';
+ if (!createLinks || $el.data('api-entity').toLowerCase() !== 'contact') {
+ return '';
+ }
if (createLinks === true) {
- createLinks = type ? _.where(CRM.profileCreate, {type: type}) : CRM.profileCreate;
+ createLinks = params.contact_type ? _.where(CRM.config.entityRef.contactCreate, {type: params.contact_type}) : CRM.config.entityRef.contactCreate;
}
- var markup = '';
_.each(createLinks, function(link) {
markup += ' <a class="crm-add-entity crm-hover-button" href="' + link.url + '">';
if (link.type) {
}
markup += link.label + '</a>';
});
+ markup += '</div>';
+ return markup;
+ }
+
+ function getEntityRefFilters($el) {
+ var
+ entity = $el.data('api-entity').toLowerCase(),
+ filters = $.extend([], CRM.config.entityRef.filters[entity] || []),
+ filter = $el.data('user-filter') || {},
+ params = $.extend({params: {}}, $el.data('api-params') || {}).params,
+ result = [];
+ $.each(filters, function() {
+ if (typeof params[this.key] === 'undefined') {
+ result.push(this);
+ }
+ else if (this.key == 'contact_type' && typeof params.contact_sub_type === 'undefined') {
+ this.options = _.remove(this.options, function(option) {
+ return option.key.indexOf(params.contact_type + '.') === 0;
+ });
+ result.push(this);
+ }
+ });
+ return result;
+ }
+
+ function renderEntityRefFilters($el) {
+ var
+ filters = getEntityRefFilters($el),
+ filter = $el.data('user-filter') || {},
+ filterSpec = filter.key ? _.find(filters, {key: filter.key}) : null;
+ if (!filters.length) {
+ return '';
+ }
+ var markup = '<div class="crm-entityref-filters">' +
+ '<select class="crm-entityref-filter-key' + (filter.key ? ' active' : '') + '">' +
+ '<option value="">' + ts('Refine search...') + '</option>' +
+ CRM.utils.renderOptions(filters, filter.key) +
+ '</select> ' +
+ '<select class="crm-entityref-filter-value' + (filter.key ? ' active"' : '"') + (filter.key ? '' : ' style="display:none;"') + '>' +
+ '<option value="">' + ts('- select -') + '</option>';
+ if (filterSpec && filterSpec.options) {
+ markup += CRM.utils.renderOptions(filterSpec.options, filter.value);
+ }
+ markup += '</select></div>';
return markup;
}
+ /**
+ * Fetch options for a filter (via ajax if necessary) and populate the appropriate select list
+ * @param $el
+ */
+ function loadEntityRefFilterOptions($el) {
+ var
+ filters = getEntityRefFilters($el),
+ filter = $el.data('user-filter') || {},
+ filterSpec = filter.key ? _.find(filters, {key: filter.key}) : null,
+ $valField = $('.crm-entityref-filter-value', '#select2-drop');
+ if (filterSpec) {
+ $valField.show().val('');
+ if (filterSpec.options) {
+ CRM.utils.setOptions($valField, filterSpec.options, false, filter.value);
+ } else {
+ $valField.prop('disabled', true);
+ CRM.api3(filterSpec.entity || $el.data('api-entity'), 'getoptions', {field: filter.key, sequential: 1})
+ .done(function(result) {
+ var entity = $el.data('api-entity').toLowerCase(),
+ globalFilterSpec = _.find(CRM.config.entityRef.filters[entity], {key: filter.key}) || {};
+ // Store options globally so we don't have to look them up again
+ globalFilterSpec.options = result.values;
+ $valField.prop('disabled', false);
+ CRM.utils.setOptions($valField, result.values);
+ $valField.val(filter.value || '');
+ });
+ }
+ } else {
+ $valField.hide();
+ }
+ }
+
/**
* Wrapper for jQuery validate initialization function; supplies defaults
- * @param options object
*/
$.fn.crmValidate = function(params) {
return $(this).each(function () {
});
}
});
- }
+ };
// Initialize widgets
$(document)
$el.addClass('modal-dialog');
$('body').css({overflow: 'hidden'});
}
- $el.parent().find('.ui-dialog-titlebar-close').attr('title', ts('Close'));
// Add resize button
if ($el.parent().hasClass('crm-container') && $el.dialog('option', 'resizable')) {
$el.parent().find('.ui-dialog-titlebar').append($('<button class="crm-dialog-titlebar-resize ui-dialog-titlebar-close" title="'+ts('Toggle fullscreen')+'" style="right:2em;"/>').button({icons: {primary: 'ui-icon-newwin'}, text: false}));
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/Notification+Reference
*/
CRM.confirm = function (options) {
- var dialog, url, msg, settings = {
+ var dialog, url, msg, buttons = [], settings = {
title: ts('Confirm'),
message: ts('Are you sure you want to continue?'),
url: null,
};
$.extend(settings, ($.isFunction(options) ? arguments[1] : options) || {});
if (!settings.buttons && $.isPlainObject(settings.options)) {
- settings.buttons = [];
- $.each(settings.options, function(key, label) {
- settings.buttons.push({
+ $.each(settings.options, function(op, label) {
+ buttons.push({
text: label,
- icons: {primary: key === 'no' ? 'ui-icon-close' : 'ui-icon-check'},
+ 'data-op': op,
+ icons: {primary: op === 'no' ? 'ui-icon-close' : 'ui-icon-check'},
click: function() {
- var event = $.Event('crmConfirm:' + key);
+ var event = $.Event('crmConfirm:' + op);
$(this).trigger(event);
if (!event.isDefaultPrevented()) {
dialog.dialog('close');
}
});
});
+ // Order buttons so that "no" goes on the right-hand side
+ settings.buttons = _.sortBy(buttons, 'data-op').reverse();
}
url = settings.url;
- msg = settings.message;
+ msg = url ? '' : settings.message;
delete settings.options;
delete settings.message;
delete settings.url;
- dialog = $('<div class="crm-confirm-dialog"></div>').dialog(settings);
+ dialog = $('<div class="crm-confirm-dialog"></div>').html(msg || '').dialog(settings);
if ($.isFunction(options)) {
dialog.on('crmConfirm:yes', options);
}
if (url) {
CRM.loadPage(url, {target: dialog});
}
- else if (msg && msg.length) {
- dialog.html(msg).trigger('crmLoad');
+ else {
+ dialog.trigger('crmLoad');
}
return dialog;
};
});
}
+ /**
+ * Improve blockUI when used with jQuery dialog
+ */
var originalBlock = $.fn.block,
originalUnblock = $.fn.unblock;
return $(this);
}
return originalBlock.call(this, opts);
- }
-
+ };
$.fn.unblock = function(opts) {
if ($(this).is('.ui-dialog-content')) {
originalUnblock.call($(this).parents('.ui-dialog'), opts);
return $(this);
}
return originalUnblock.call(this, opts);
- }
+ };
- // Preprocess all cj ajax calls to display messages
+ // Preprocess all CRM ajax calls to display messages
$(document).ajaxSuccess(function(event, xhr, settings) {
try {
if ((!settings.dataType || settings.dataType == 'json') && xhr.responseText) {
if (response.backtrace) {
CRM.console('log', response.backtrace);
}
+ if (typeof response.deprecated === 'string') {
+ CRM.console('warn', response.deprecated);
+ }
}
}
- // Suppress errors
+ // Ignore errors thrown by parseJSON
catch (e) {}
});
CRM.confirm({
title: ts('Preview'),
resizable: true,
- message: '<div class="crm-custom-image-popup"><img src=' + $(this).attr('href') + '></div>',
+ message: '<div class="crm-custom-image-popup"><img style="max-width: 100%" src="' + $(this).attr('href') + '"></div>',
options: null
});
e.preventDefault();
/**
* Clientside currency formatting
- * @param value
- * @param format - currency representation of the number 1234.56
+ * @param number value
+ * @param [optional] string format - currency representation of the number 1234.56
* @return string
- * @see CRM_Core_Resources::addCoreResources
*/
var currencyTemplate;
CRM.formatMoney = function(value, format) {
return console[method](title, msg);
}
}
- }
+ };
})(jQuery, _);