*/
function ts(text, params) {
"use strict";
- text = CRM.strings[text] || text;
+ var d = (params && params.domain) ? ('strings::' + params.domain) : null;
+ if (d && CRM[d] && CRM[d][text]) {
+ text = CRM[d][text];
+ }
+ else if (CRM['strings'][text]) {
+ text = CRM['strings'][text];
+ }
if (typeof(params) === 'object') {
for (var i in params) {
if (typeof(params[i]) === 'string' || typeof(params[i]) === 'number') {
*/
function on_load_init_blocks(showBlocks, hideBlocks, elementType) {
if (elementType == null) {
- var elementType = 'block';
+ elementType = 'block';
}
+ var myElement, i;
+
/* This loop is used to display the blocks whose IDs are present within the showBlocks array */
- for (var i = 0; i < showBlocks.length; i++) {
- var myElement = document.getElementById(showBlocks[i]);
+ for (i = 0; i < showBlocks.length; i++) {
+ myElement = document.getElementById(showBlocks[i]);
/* getElementById returns null if element id doesn't exist in the document */
if (myElement != null) {
myElement.style.display = elementType;
}
/* This loop is used to hide the blocks whose IDs are present within the hideBlocks array */
- for (var i = 0; i < hideBlocks.length; i++) {
- var myElement = document.getElementById(hideBlocks[i]);
+ for (i = 0; i < hideBlocks.length; i++) {
+ myElement = document.getElementById(hideBlocks[i]);
/* getElementById returns null if element id doesn't exist in the document */
if (myElement != null) {
myElement.style.display = 'none';
* @param invert Boolean - if true, we HIDE target on value match; if false, we SHOW target on value match
*/
function showHideByValue(trigger_field_id, trigger_value, target_element_id, target_element_type, field_type, invert) {
+ var target, j;
if (field_type == 'select') {
var trigger = trigger_value.split("|");
var selectedOptionValue = cj('#' + trigger_field_id).val();
- var target = target_element_id.split("|");
- for (var j = 0; j < target.length; j++) {
+ target = target_element_id.split("|");
+ for (j = 0; j < target.length; j++) {
if (invert) {
cj('#' + target[j]).show();
}
}
else {
if (field_type == 'radio') {
- var target = target_element_id.split("|");
- for (var j = 0; j < target.length; j++) {
+ target = target_element_id.split("|");
+ for (j = 0; j < target.length; j++) {
if (cj('[name="' + trigger_field_id + '"]:first').is(':checked')) {
if (invert) {
cj('#' + target[j]).hide();
* 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, context: 'search', 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)
var opts = $.extend({
start: ts('Saving...'),
success: ts('Saved'),
- error: function() {
- CRM.alert(ts('Sorry an error occurred and your information was not saved'), ts('Error'));
+ error: function(data) {
+ var msg = $.isPlainObject(data) && data.error_message;
+ CRM.alert(msg || ts('Sorry an error occurred and your information was not saved'), ts('Error'), 'error');
}
}, options || {});
var $msg = $('<div class="crm-status-box-outer status-start"><div class="crm-status-box-inner"><div class="crm-status-box-msg">' + opts.start + '</div></div></div>')
if (endMsg) {
$msg.removeClass('status-start').addClass('status-' + status).find('.crm-status-box-msg').html(endMsg);
window.setTimeout(function() {
- $msg.fadeOut('slow', function() {$msg.remove()});
+ $msg.fadeOut('slow', function() {
+ $msg.remove();
+ });
}, 2000);
} else {
$msg.remove();
});
};
+ // 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
*/
};
};
+ CRM.addStrings = function(domain, strings) {
+ var bucket = (domain == 'civicrm' ? 'strings' : 'strings::' + domain);
+ CRM[bucket] = CRM[bucket] || {};
+ _.extend(CRM[bucket], strings);
+ };
+
/**
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/Notification+Reference
*/
title = $label.text();
}
}
- $(this).addClass('error');
+ $(this).addClass('crm-error');
}
var msg = CRM.alert(text, title, 'error', $.extend(extra, options));
if ($(this).length) {
var ele = $(this);
setTimeout(function () {
ele.one('change', function () {
- msg && msg.close && msg.close();
+ if (msg && msg.close) msg.close();
ele.removeClass('error');
label.removeClass('crm-error');
});
if (typeof(response.crmMessages) == 'object') {
$.each(response.crmMessages, function(n, msg) {
CRM.alert(msg.text, msg.title, msg.type, msg.options);
- })
+ });
}
if (response.backtrace) {
CRM.console('log', response.backtrace);