X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=js%2FCommon.js;h=945be34461af700fdf5b74a42d651139c580f504;hb=a29e89a5104a7722a48951e16e710dc87f1822d5;hp=26ae4cfaf18c5e2be58409bc7dcadb59ee368c97;hpb=1ec76f38e5f2d5dfba95faa2cc7a5e736fd455ff;p=civicrm-core.git diff --git a/js/Common.js b/js/Common.js index 26ae4cfaf1..945be34461 100644 --- a/js/Common.js +++ b/js/Common.js @@ -207,32 +207,17 @@ CRM.strings = CRM.strings || {}; * 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 += ''; - theme(option.children); - newOptions += ''; - } else { - var selected = ($.inArray('' + option.key, val) > -1) ? 'selected="selected"' : ''; - newOptions += ''; - } - }); - }; - 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); @@ -241,8 +226,36 @@ CRM.strings = CRM.strings || {}; } } $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 += '' + + CRM.utils.renderOptions(option.children, val) + + ''; + } else { + var selected = ($.inArray('' + option.key, val) > -1) ? 'selected="selected"' : ''; + rendered += ''; + } }); + return rendered; }; function chainSelect() { @@ -296,6 +309,11 @@ CRM.strings = CRM.strings || {}; $(this).nextUntil('option[value^=crm_optgroup]').wrapAll(''); $(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; @@ -327,13 +345,13 @@ CRM.strings = CRM.strings || {}; $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 { @@ -373,42 +391,8 @@ CRM.strings = CRM.strings || {}; } } }; - 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') + '
' + formatSelect2CreateLinks($el); - } - return txt; - }; - selectParams.formatNoMatches = function() { - var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches; - return txt + (CRM.profileCreate ? ('
' + 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') + ')'}; @@ -416,9 +400,7 @@ CRM.strings = CRM.strings || {}; }; 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; @@ -439,9 +421,89 @@ CRM.strings = CRM.strings || {}; }); } }); + } + 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 = '
'; if (row.image !== undefined) { @@ -459,15 +521,17 @@ CRM.strings = CRM.strings || {}; 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 = ''; + 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 = '
' + + '   ' + + '
'; 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 () { @@ -494,7 +643,7 @@ CRM.strings = CRM.strings || {}; }); } }); - } + }; // Initialize widgets $(document) @@ -721,6 +870,27 @@ CRM.strings = CRM.strings || {}; }); }; + // 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 */