* 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() {
$(this).nextUntil('option[value^=crm_optgroup]').wrapAll('<optgroup label="' + $(this).text() + '" />');
$(this).remove();
});
+
+ // quickform does not support disabled option, so yet another hack to
+ // add disabled property for option values
+ $('option[value^=crm_disabled_opt]', this).attr('disabled', 'disabled');
+
// Defaults for single-selects
if ($el.is('select:not([multiple])')) {
settings.minimumResultsForSearch = 10;
$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();
+ }
+ }
+
+ //CRM-15598 - Override url validator method to allow relative url's (e.g. /index.htm)
+ $.validator.addMethod("url", function(value, element) {
+ if (/^\//.test(value)) {
+ // Relative url: prepend dummy path for validation.
+ value = 'http://domain.tld' + value;
+ }
+ // From jQuery Validation Plugin v1.12.0
+ return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
+ });
+
/**
* Wrapper for jQuery validate initialization function; supplies defaults
- * @param options object
*/
$.fn.crmValidate = function(params) {
return $(this).each(function () {
});
}
});
- }
+ };
// Initialize widgets
$(document)
});
};
+ // Convert an Angular promise to a jQuery promise
+ CRM.toJqPromise = function(aPromise) {
+ var jqDeferred = $.Deferred();
+ aPromise.then(
+ function(data) { jqDeferred.resolve(data); },
+ function(data) { jqDeferred.reject(data); }
+ // should we also handle progress events?
+ );
+ return jqDeferred.promise();
+ };
+
+ CRM.toAPromise = function($q, jqPromise) {
+ var aDeferred = $q.defer();
+ jqPromise.then(
+ function(data) { aDeferred.resolve(data); },
+ function(data) { aDeferred.reject(data); }
+ // should we also handle progress events?
+ );
+ return aDeferred.promise;
+ };
+
/**
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/Notification+Reference
*/
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, _);