// https://civicrm.org/licensing /* global CRM:true */ var CRM = CRM || {}; var cj = CRM.$ = jQuery; CRM._ = _; /** * Short-named function for string translation, defined in global scope so it's available everywhere. * * @param text string for translating * @param params object key:value of additional parameters * * @return string */ function ts(text, params) { "use strict"; 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') { // sprintf emulation: escape % characters in the replacements to avoid conflicts text = text.replace(new RegExp('%' + i, 'g'), String(params[i]).replace(/%/g, '%-crmescaped-')); } } return text.replace(/%-crmescaped-/g, '%'); } return text; } // Legacy code - ignore warnings /* jshint ignore:start */ /** * This function is called by default at the bottom of template files which have forms that have * conditionally displayed/hidden sections and elements. The PHP is responsible for generating * a list of 'blocks to show' and 'blocks to hide' and the template passes these parameters to * this function. * * @deprecated * @param showBlocks Array of element Id's to be displayed * @param hideBlocks Array of element Id's to be hidden * @param elementType Value to set display style to for showBlocks (e.g. 'block' or 'table-row' or ...) */ function on_load_init_blocks(showBlocks, hideBlocks, elementType) { if (elementType == null) { elementType = 'block'; } var myElement, i; /* This loop is used to display the blocks whose IDs are present within the showBlocks array */ 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; } else { alert('showBlocks array item not in .tpl = ' + showBlocks[i]); } } /* This loop is used to hide the blocks whose IDs are present within the hideBlocks array */ 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'; } else { alert('showBlocks array item not in .tpl = ' + hideBlocks[i]); } } } /** * This function is called when we need to show or hide a related form element (target_element) * based on the value (trigger_value) of another form field (trigger_field). * * @deprecated * @param trigger_field_id HTML id of field whose onchange is the trigger * @param trigger_value List of integers - option value(s) which trigger show-element action for target_field * @param target_element_id HTML id of element to be shown or hidden * @param target_element_type Type of element to be shown or hidden ('block' or 'table-row') * @param field_type Type of element radio/select * @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(); target = target_element_id.split("|"); for (j = 0; j < target.length; j++) { if (invert) { cj('#' + target[j]).show(); } else { cj('#' + target[j]).hide(); } for (var i = 0; i < trigger.length; i++) { if (selectedOptionValue == trigger[i]) { if (invert) { cj('#' + target[j]).hide(); } else { cj('#' + target[j]).show(); } } } } } else { if (field_type == 'radio') { 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(); } else { cj('#' + target[j]).show(); } } else { if (invert) { cj('#' + target[j]).show(); } else { cj('#' + target[j]).hide(); } } } } } } /** * Function to change button text and disable one it is clicked * @deprecated * @param obj object - the button clicked * @param formID string - the id of the form being submitted * @param string procText - button text after user clicks it * @return bool */ var submitcount = 0; /* Changes button label on submit, and disables button after submit for newer browsers. Puts up alert for older browsers. */ function submitOnce(obj, formId, procText) { // if named button clicked, change text if (obj.value != null) { cj('input[name=' + obj.name + ']').val(procText + " ..."); } cj(obj).closest('form').attr('data-warn-changes', 'false'); if (document.getElementById) { // disable submit button for newer browsers cj('input[name=' + obj.name + ']').attr("disabled", true); document.getElementById(formId).submit(); return true; } else { // for older browsers if (submitcount == 0) { submitcount++; return true; } else { alert("Your request is currently being processed ... Please wait."); return false; } } } /** * Function to show / hide the row in optionFields * @deprecated * @param index string, element whose innerHTML is to hide else will show the hidden row. */ function showHideRow(index) { if (index) { cj('tr#optionField_' + index).hide(); if (cj('table#optionField tr:hidden:first').length) { cj('div#optionFieldLink').show(); } } else { cj('table#optionField tr:hidden:first').show(); if (!cj('table#optionField tr:hidden:last').length) { cj('div#optionFieldLink').hide(); } } return false; } /* jshint ignore:end */ if (!CRM.utils) CRM.utils = {}; if (!CRM.strings) CRM.strings = {}; if (!CRM.vars) CRM.vars = {}; (function ($, _, undefined) { "use strict"; /* jshint validthis: true */ // Theme classes for unattached elements $.fn.select2.defaults.dropdownCssClass = $.ui.dialog.prototype.options.dialogClass = 'crm-container'; // https://github.com/ivaynberg/select2/pull/2090 $.fn.select2.defaults.width = 'resolve'; // Workaround for https://github.com/ivaynberg/select2/issues/1246 $.ui.dialog.prototype._allowInteraction = function(e) { return !!$(e.target).closest('.ui-dialog, .ui-datepicker, .select2-drop, .cke_dialog, #civicrm-menu').length; }; // Implements jQuery hook.prop $.propHooks.disabled = { set: function (el, value, name) { // Sync button enabled status with wrapper css if ($(el).is('span.crm-button > input.crm-form-submit')) { $(el).parent().toggleClass('crm-button-disabled', !!value); } // Sync button enabled status with dialog button if ($(el).is('.ui-dialog input.crm-form-submit')) { $(el).closest('.ui-dialog').find('.ui-dialog-buttonset button[data-identifier='+ $(el).attr('name') +']').prop('disabled', value); } if ($(el).is('.crm-form-date-wrapper .crm-hidden-date')) { $(el).siblings().prop('disabled', value); } } }; var scriptsLoaded = {}; CRM.loadScript = function(url) { if (!scriptsLoaded[url]) { var script = document.createElement('script'); scriptsLoaded[url] = $.Deferred(); script.onload = function () { // Give the script time to execute window.setTimeout(function () { if (window.jQuery === CRM.$ && CRM.CMSjQuery) { window.jQuery = CRM.CMSjQuery; } scriptsLoaded[url].resolve(); }, 100); }; // Make jQuery global available while script is loading if (window.jQuery !== CRM.$) { CRM.CMSjQuery = window.jQuery; window.jQuery = CRM.$; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script); } return scriptsLoaded[url]; }; /** * 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|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, value) { $(select).each(function() { var $elect = $(this), val = value || $elect.val() || [], opts = placeholder || placeholder === '' ? '' : '[value!=""]'; $elect.find('option' + opts).remove(); var newOptions = CRM.utils.renderOptions(options, val); if (typeof placeholder === 'string') { if ($elect.is('[multiple]')) { select.attr('placeholder', placeholder); } else { newOptions = '' + newOptions; } } $elect.append(newOptions); 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() { var $form = $(this).closest('form'), $target = $('select[data-name="' + $(this).data('target') + '"]', $form), data = $target.data(), val = $(this).val(); $target.prop('disabled', true); if ($target.is('select.crm-chain-select-control')) { $('select[data-name="' + $target.data('target') + '"]', $form).prop('disabled', true).blur(); } if (!(val && val.length)) { CRM.utils.setOptions($target.blur(), [], data.emptyPrompt); } else { $target.addClass('loading'); $.getJSON(CRM.url(data.callback), {_value: val}, function(vals) { $target.prop('disabled', false).removeClass('loading'); CRM.utils.setOptions($target, vals || [], (vals && vals.length ? data.selectPrompt : data.nonePrompt)); }); } } /** * Compare Form Input values against cached initial value. * * @return {Boolean} true if changes have been made. */ CRM.utils.initialValueChanged = function(el) { var isDirty = false; $(':input:visible, .select2-container:visible+:input:hidden', el).not('[type=submit], [type=button], .crm-action-menu, :disabled').each(function () { var initialValue = $(this).data('crm-initial-value'), currentValue = $(this).is(':checkbox, :radio') ? $(this).prop('checked') : $(this).val(); // skip change of value for submit buttons if (initialValue !== undefined && !_.isEqual(initialValue, currentValue)) { isDirty = true; } }); return isDirty; }; /** * This provides defaults for ui.dialog which either need to be calculated or are different from global defaults * * @param settings * @returns {*} */ CRM.utils.adjustDialogDefaults = function(settings) { settings = $.extend({width: '65%', height: '65%', modal: true}, settings || {}); // Support relative height if (typeof settings.height === 'string' && settings.height.indexOf('%') > 0) { settings.height = parseInt($(window).height() * (parseFloat(settings.height)/100), 10); } // Responsive adjustment - increase percent width on small screens if (typeof settings.width === 'string' && settings.width.indexOf('%') > 0) { var screenWidth = $(window).width(), percentage = parseInt(settings.width.replace('%', ''), 10), gap = 100-percentage; if (screenWidth < 701) { settings.width = '100%'; } else if (screenWidth < 1400) { settings.width = '' + parseInt(percentage+gap-((screenWidth - 700)/7*(gap)/100), 10) + '%'; } } return settings; }; function formatCrmSelect2(row) { var icon = row.icon || $(row.element).data('icon'), color = row.color || $(row.element).data('color'), description = row.description || $(row.element).data('description'), ret = ''; if (icon) { ret += ' '; } if (color) { ret += ' '; } return ret + _.escape(row.text) + (description ? '

' + _.escape(description) + '

' : ''); } /** * Wrapper for select2 initialization function; supplies defaults * @param options object */ $.fn.crmSelect2 = function(options) { if (options === 'destroy') { return $(this).each(function() { $(this) .removeClass('crm-ajax-select') .select2('destroy'); }); } return $(this).each(function () { var $el = $(this), iconClass, settings = { allowClear: !$el.hasClass('required'), formatResult: formatCrmSelect2, formatSelection: formatCrmSelect2 }; // quickform doesn't support optgroups so here's a hack :( $('option[value^=crm_optgroup]', this).each(function () { $(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'); // Placeholder icon - total hack hikacking the escapeMarkup function but select2 3.5 dosn't have any other callbacks for this :( if ($el.is('[class*=fa-]')) { settings.escapeMarkup = function (m) { var out = _.escape(m), placeholder = settings.placeholder || $el.data('placeholder') || $el.attr('placeholder') || $('option[value=""]', $el).text(); if (m.length && placeholder === m) { iconClass = $el.attr('class').match(/(fa-\S*)/)[1]; out = ' ' + out; } return out; }; } // Defaults for single-selects if ($el.is('select:not([multiple])')) { settings.minimumResultsForSearch = 10; if ($('option:first', this).val() === '') { settings.placeholderOption = 'first'; } } $.extend(settings, $el.data('select-params') || {}, options || {}); if (settings.ajax) { $el.addClass('crm-ajax-select'); } $el.select2(settings); }); }; /** * @see CRM_Core_Form::addEntityRef for docs * @param options object */ $.fn.crmEntityRef = function(options) { if (options === 'destroy') { return $(this).each(function() { var entity = $(this).data('api-entity') || ''; $(this) .off('.crmEntity') .removeClass('crm-form-entityref crm-' + entity.toLowerCase() + '-ref') .crmSelect2('destroy'); }); } options = options || {}; options.select = options.select || {}; return $(this).each(function() { var $el = $(this).off('.crmEntity'), entity = options.entity || $el.data('api-entity') || 'contact', selectParams = {}; $el.data('api-entity', entity); $el.data('select-params', $.extend({}, $el.data('select-params') || {}, options.select)); $el.data('api-params', $.extend(true, {}, $el.data('api-params') || {}, options.api)); $el.data('create-links', options.create || $el.data('create-links')); $el.addClass('crm-form-entityref crm-' + entity.toLowerCase() + '-ref'); var settings = { // Use select2 ajax helper instead of CRM.api3 because it provides more value ajax: { url: CRM.url('civicrm/ajax/rest'), quietMillis: 300, data: function (input, page_num) { var params = getEntityRefApiParams($el); params.input = input; params.page_num = page_num; return { entity: $el.data('api-entity'), action: 'getlist', json: JSON.stringify(params) }; }, results: function(data) { return {more: data.more_results, results: data.values || []}; } }, minimumInputLength: 1, formatResult: CRM.utils.formatSelect2Result, formatSelection: formatEntityRefSelection, escapeMarkup: _.identity, initSelection: function($el, callback) { var multiple = !!$el.data('select-params').multiple, val = $el.val(), stored = $el.data('entity-value') || []; if (val === '') { return; } // If we already have this data, just return it if (!_.xor(val.split(','), _.pluck(stored, 'id')).length) { callback(multiple ? stored : stored[0]); } else { var params = $.extend({}, $el.data('api-params') || {}, {id: val}); CRM.api3($el.data('api-entity'), 'getlist', params).done(function(result) { callback(multiple ? result.values : result.values[0]); // Trigger change (store data to avoid an infinite loop of lookups) $el.data('entity-value', result.values).trigger('change'); }); } } }; // Create new items inline - works for tags 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.on('select2-selecting.crmEntity', function(e) { if (e.val === "0") { // Create a new term e.object.label = e.object.term; CRM.api3(entity, 'create', $.extend({name: e.object.term}, $el.data('api-params').params || {})) .done(function(created) { var val = $el.select2('val'), data = $el.select2('data'), item = {id: created.id, label: e.object.term}; if (val === "0") { $el.select2('data', item, true); } else if ($.isArray(val) && $.inArray("0", val) > -1) { _.remove(data, {id: "0"}); data.push(item); $el.select2('data', data, true); } }); } }); } else { selectParams.formatInputTooShort = function() { var txt = $el.data('select-params').formatInputTooShort || $.fn.select2.defaults.formatInputTooShort.call(this); txt += entityRefFiltersMarkup($el) + renderEntityRefCreateLinks($el); return txt; }; selectParams.formatNoMatches = function() { var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches; txt += entityRefFiltersMarkup($el) + renderEntityRefCreateLinks($el); return txt; }; $el.on('select2-open.crmEntity', function() { var $el = $(this); renderEntityRefFilterValue($el); $('#select2-drop') .off('.crmEntity') .on('click.crmEntity', 'a.crm-add-entity', function(e) { var extra = $el.data('api-params').extra, formUrl = $(this).attr('href') + '&returnExtra=display_name,sort_name' + (extra ? (',' + extra) : ''); $el.select2('close'); CRM.loadForm(formUrl, { dialog: {width: 500, height: 220} }).on('crmFormSuccess', function(e, data) { if (data.status === 'success' && data.id) { data.label = data.extra.sort_name; CRM.status(ts('%1 Created', {1: data.extra.display_name})); 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', '.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'); } else { $('.crm-entityref-links', '#select2-drop').replaceWith(renderEntityRefCreateLinks($el)); } }) .on('change.crmEntity', 'select.crm-entityref-filter-key', function() { var filter = {key: $(this).val()}; $(this).toggleClass('active', !!filter.key); $el.data('user-filter', filter); renderEntityRefFilterValue($el); $('.crm-entityref-filter-key', '#select2-drop').focus(); }); }); } $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) { // Fieldname may be prefixed with joins var fieldName = _.last(filter.key.split('.')); // Special case for contact type/sub-type combo if (fieldName === 'contact_type' && (filter.value.indexOf('__') > 0)) { combined.params[filter.key] = filter.value.split('__')[0]; combined.params[filter.key.replace('contact_type', '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; } function copyAttributes($source, $target, attributes) { _.each(attributes, function(name) { if ($source.attr(name) !== undefined) { $target.attr(name, $source.attr(name)); } }); } /** * @see http://wiki.civicrm.org/confluence/display/CRMDOC/crmDatepicker */ $.fn.crmDatepicker = function(options) { return $(this).each(function() { if ($(this).is('.crm-form-date-wrapper .crm-hidden-date')) { // Already initialized - destroy $(this) .off('.crmDatepicker') .css('display', '') .removeClass('crm-hidden-date') .siblings().remove(); $(this).unwrap(); } if (options === 'destroy') { return; } var $dataField = $(this).wrap(''), settings = _.cloneDeep(options || {}), $dateField = $(), $timeField = $(), $clearLink = $(), hasDatepicker = settings.date !== false && settings.date !== 'yy', type = hasDatepicker ? 'text' : 'number'; if (settings.allowClear !== undefined ? settings.allowClear : !$dataField.is('.required, [required]')) { $clearLink = $('') .insertAfter($dataField); } if (settings.time !== false) { $timeField = $('').insertAfter($dataField); copyAttributes($dataField, $timeField, ['class', 'disabled']); $timeField .addClass('crm-form-text crm-form-time') .attr('placeholder', $dataField.attr('time-placeholder') === undefined ? ts('Time') : $dataField.attr('time-placeholder')) .change(updateDataField) .timeEntry({ spinnerImage: '', show24Hours: settings.time === true || settings.time === undefined ? CRM.config.timeIs24Hr : settings.time == '24' }); } if (settings.date !== false) { // Render "number" field for year-only format, calendar popup for all other formats $dateField = $('').insertAfter($dataField); copyAttributes($dataField, $dateField, ['placeholder', 'style', 'class', 'disabled']); $dateField.addClass('crm-form-' + type); if (hasDatepicker) { settings.minDate = settings.minDate ? CRM.utils.makeDate(settings.minDate) : null; settings.maxDate = settings.maxDate ? CRM.utils.makeDate(settings.maxDate) : null; settings.dateFormat = typeof settings.date === 'string' ? settings.date : CRM.config.dateInputFormat; settings.changeMonth = _.includes(settings.dateFormat, 'm'); settings.changeYear = _.includes(settings.dateFormat, 'y'); if (!settings.yearRange && settings.minDate !== null && settings.maxDate !== null) { settings.yearRange = '' + CRM.utils.formatDate(settings.minDate, 'yy') + ':' + CRM.utils.formatDate(settings.maxDate, 'yy'); } $dateField.addClass('crm-form-date').datepicker(settings); } else { $dateField.attr('min', settings.minDate ? CRM.utils.formatDate(settings.minDate, 'yy') : '1000'); $dateField.attr('max', settings.maxDate ? CRM.utils.formatDate(settings.maxDate, 'yy') : '4000'); } $dateField.change(updateDataField); } // Rudimentary validation. TODO: Roll into use of jQUery validate and ui.datepicker.validation function isValidDate() { // FIXME: parseDate doesn't work with incomplete date formats; skip validation if no month, day or year in format var lowerFormat = settings.dateFormat.toLowerCase(); if (lowerFormat.indexOf('y') < 0 || lowerFormat.indexOf('m') < 0 || !dateHasDay()) { return true; } try { $.datepicker.parseDate(settings.dateFormat, $dateField.val()); return true; } catch (e) { return false; } } /** * Does the date format contain the day. * * @returns {boolean} */ function dateHasDay() { var lowerFormat = settings.dateFormat.toLowerCase(); if (lowerFormat.indexOf('d') < 0) { return false; } return true; } function updateInputFields(e, context) { var val = $dataField.val(), time = null; if (context !== 'userInput' && context !== 'crmClear') { if (hasDatepicker) { $dateField.datepicker('setDate', _.includes(val, '-') ? $.datepicker.parseDate('yy-mm-dd', val) : null); } else if ($dateField.length) { $dateField.val(val.slice(0, 4)); } if ($timeField.length) { if (val.length === 8) { time = val; } else if (val.length === 19) { time = val.split(' ')[1]; } $timeField.timeEntry('setTime', time); } } $clearLink.css('visibility', val ? 'visible' : 'hidden'); } function updateDataField(e, context) { // The crmClear event wipes all the field values anyway, so no need to respond if (context !== 'crmClear') { var val = ''; if ($dateField.val()) { if (hasDatepicker && isValidDate() && dateHasDay()) { val = $.datepicker.formatDate('yy-mm-dd', $dateField.datepicker('getDate')); $dateField.removeClass('crm-error'); } else if (!hasDatepicker) { val = $dateField.val() + '-01-01'; } else if (!dateHasDay()) { // This would be a Year-month date (yyyy-mm) // it could be argued it should not use a datepicker.... val = $dateField.val() + '-01'; } else { $dateField.addClass('crm-error'); } } if ($timeField.val()) { val += (val ? ' ' : '') + $timeField.timeEntry('getTime').toTimeString().substr(0, 8); } $dataField.val(val).trigger('change', ['userInput']); } } $dataField.hide().addClass('crm-hidden-date').on('change.crmDatepicker', updateInputFields); updateInputFields(); }); }; CRM.utils.formatSelect2Result = function (row) { var markup = '
'; if (row.image !== undefined) { markup += '
'; } else if (row.icon_class) { markup += '
'; } markup += '
' + (row.color ? ' ' : '') + _.escape((row.prefix !== undefined ? row.prefix + ' ' : '') + row.label + (row.suffix !== undefined ? ' ' + row.suffix : '')) + '
' + '
'; $.each(row.description || [], function(k, text) { markup += '

' + _.escape(text) + '

'; }); markup += '
'; return markup; }; function formatEntityRefSelection(row) { return (row.color ? ' ' : '') + _.escape((row.prefix !== undefined ? row.prefix + ' ' : '') + row.label + (row.suffix !== undefined ? ' ' + row.suffix : '')); } function renderEntityRefCreateLinks($el) { var createLinks = $el.data('create-links'), params = getEntityRefApiParams($el).params, markup = ''; return markup; } function getEntityRefFilters($el) { var entity = $el.data('api-entity').toLowerCase(), filters = $.extend([], CRM.config.entityRef.filters[entity] || []), params = $.extend({params: {}}, $el.data('api-params') || {}).params, result = []; $.each(filters, function() { var filter = $.extend({type: 'select', 'attributes': {}, entity: entity}, this); if (typeof params[filter.key] === 'undefined') { result.push(filter); } else if (filter.key == 'contact_type' && typeof params.contact_sub_type === 'undefined') { filter.options = _.remove(filter.options, function(option) { return option.key.indexOf(params.contact_type + '__') === 0; }); result.push(filter); } }); return result; } /** * Provide markup for entity ref filters */ function entityRefFiltersMarkup($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 = '
' + '' + entityRefFilterValueMarkup(filter, filterSpec) + '
'; return markup; } /** * Provide markup for entity ref filter value field */ function entityRefFilterValueMarkup(filter, filterSpec) { var markup = ''; if (filterSpec) { var attrs = '', attributes = _.cloneDeep(filterSpec.attributes); if (filterSpec.type !== 'select') { attributes.type = filterSpec.type; attributes.value = typeof filter.value !== 'undefined' ? filter.value : ''; } attributes.class = 'crm-entityref-filter-value' + (filter.value ? ' active' : ''); $.each(attributes, function (attr, val) { attrs += ' ' + attr + '="' + val + '"'; }); if (filterSpec.type === 'select') { markup = ''; if (filterSpec.options) { markup += CRM.utils.renderOptions(filterSpec.options, filter.value); } markup += ''; } else { markup = ''; } } return markup; } /** * Render the entity ref filter value field */ function renderEntityRefFilterValue($el) { var filter = $el.data('user-filter') || {}, filterSpec = filter.key ? _.find(getEntityRefFilters($el), {key: filter.key}) : null, $keyField = $('.crm-entityref-filter-key', '#select2-drop'), $valField = null; if (filterSpec) { $('.crm-entityref-filter-value', '#select2-drop').remove(); $valField = $(entityRefFilterValueMarkup(filter, filterSpec)); $keyField.after($valField); if (filterSpec.type === 'select' && !filterSpec.options) { loadEntityRefFilterOptions(filter, filterSpec, $valField, $el); } } else { $('.crm-entityref-filter-value', '#select2-drop').hide().val('').change(); } } /** * Fetch options for a filter via ajax api */ function loadEntityRefFilterOptions(filter, filterSpec, $valField, $el) { $valField.prop('disabled', true); // Fieldname may be prefixed with joins - strip those out var fieldName = _.last(filter.key.split('.')); CRM.api3(filterSpec.entity, 'getoptions', {field: fieldName, 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 || ''); }); } //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 */ $.fn.crmValidate = function(params) { return $(this).each(function () { var that = this, settings = $.extend({}, CRM.validate._defaults, CRM.validate.params); $(this).validate(settings); // Call any post-initialization callbacks if (CRM.validate.functions && CRM.validate.functions.length) { $.each(CRM.validate.functions, function(i, func) { func.call(that); }); } }); }; // Initialize widgets $(document) .on('crmLoad', function(e) { $('table.row-highlight', e.target) .off('.rowHighlight') .on('change.rowHighlight', 'input.select-row, input.select-rows', function (e, data) { var filter, $table = $(this).closest('table'); if ($(this).hasClass('select-rows')) { filter = $(this).prop('checked') ? ':not(:checked)' : ':checked'; $('input.select-row' + filter, $table).prop('checked', $(this).prop('checked')).trigger('change', 'master-selected'); } else { $(this).closest('tr').toggleClass('crm-row-selected', $(this).prop('checked')); if (data !== 'master-selected') { $('input.select-rows', $table).prop('checked', $(".select-row:not(':checked')", $table).length < 1); } } }) .find('input.select-row:checked').parents('tr').addClass('crm-row-selected'); $('table.crm-sortable', e.target).DataTable(); $('table.crm-ajax-table', e.target).each(function() { var $table = $(this), script = CRM.config.resourceBase + 'js/jquery/jquery.crmAjaxTable.js', $accordion = $table.closest('.crm-accordion-wrapper.collapsed, .crm-collapsible.collapsed'); // For tables hidden by collapsed accordions, wait. if ($accordion.length) { $accordion.one('crmAccordion:open', function() { CRM.loadScript(script).done(function() { $table.crmAjaxTable(); }); }); } else { CRM.loadScript(script).done(function() { $table.crmAjaxTable(); }); } }); if ($("input:radio[name=radio_ts]").size() == 1) { $("input:radio[name=radio_ts]").prop("checked", true); } $('.crm-select2:not(.select2-offscreen, .select2-container)', e.target).crmSelect2(); $('.crm-form-entityref:not(.select2-offscreen, .select2-container)', e.target).crmEntityRef(); $('select.crm-chain-select-control', e.target).off('.chainSelect').on('change.chainSelect', chainSelect); $('.crm-form-text[data-crm-datepicker]', e.target).each(function() { $(this).crmDatepicker($(this).data('crmDatepicker')); }); $('.crm-editable', e.target).not('thead *').each(function() { var $el = $(this); CRM.loadScript(CRM.config.resourceBase + 'js/jquery/jquery.crmEditable.js').done(function() { $el.crmEditable(); }); }); // Cache Form Input initial values $('form[data-warn-changes] :input', e.target).each(function() { $(this).data('crm-initial-value', $(this).is(':checkbox, :radio') ? $(this).prop('checked') : $(this).val()); }); $('textarea.crm-form-wysiwyg', e.target).each(function() { if ($(this).hasClass("collapsed")) { CRM.wysiwyg.createCollapsed(this); } else { CRM.wysiwyg.create(this); } }); }) .on('dialogopen', function(e) { var $el = $(e.target); // Modal dialogs should disable scrollbars if ($el.dialog('option', 'modal')) { $el.addClass('modal-dialog'); $('body').css({overflow: 'hidden'}); } $el.parent().find('.ui-dialog-titlebar .ui-icon-closethick').removeClass('ui-icon-closethick').addClass('fa-times'); // Add resize button if ($el.parent().hasClass('crm-container') && $el.dialog('option', 'resizable')) { $el.parent().find('.ui-dialog-titlebar').append($('