1// https://civicrm.org/licensing 2/** 3 * @see https://docs.civicrm.org/dev/en/latest/api/interfaces/#ajax 4 * @see https://docs.civicrm.org/dev/en/latest/framework/ajax/ 5 */ 6(function($, CRM, _, undefined) { 7 /** 8 * @param string path 9 * @param string|object query 10 * @param string mode - optionally specify "front" or "back" 11 */ 12 var tplURL; 13 CRM.url = function (path, query, mode) { 14 if (path === null) { 15 throw new Error('null passed to CRM.url'); 16 } 17 if (typeof path === 'object') { 18 tplURL = path; 19 return path; 20 } 21 if (!tplURL) { 22 CRM.console('error', 'Error: CRM.url called before initialization'); 23 } 24 if (!mode) { 25 mode = CRM.config && CRM.config.isFrontend ? 'front' : 'back'; 26 } 27 query = query || ''; 28 var url, frag, hash = ''; 29 if (path.indexOf('#') > -1) { 30 hash = '#' + path.split('#')[1]; 31 path = path.split('#')[0]; 32 } 33 frag = path.split('?'); 34 // Remove basepage as it can be changed on some CMS eg. WordPress frontend. 35 frag[0] = frag[0].replace('civicrm/', '/'); 36 // Encode url path only if slashes in placeholder were also encoded 37 if (tplURL[mode].indexOf('/crmajax-placeholder-url-path') >= 0) { 38 url = tplURL[mode].replace('/crmajax-placeholder-url-path', frag[0]); 39 } else { 40 url = tplURL[mode].replace('%2Fcrmajax-placeholder-url-path', encodeURIComponent(frag[0])); 41 } 42 43 if (_.isEmpty(query)) { 44 url = url.replace(/[?&]civicrm-placeholder-url-query=1/, ''); 45 } else { 46 url = url.replace('civicrm-placeholder-url-query=1', typeof query === 'string' ? query : $.param(query)); 47 } 48 if (frag[1]) { 49 url += (url.indexOf('?') < 0 ? '?' : '&') + frag[1]; 50 } 51 return url + hash; 52 }; 53 54 $.fn.crmURL = function () { 55 return this.each(function() { 56 if (this.href) { 57 this.href = CRM.url(this.href); 58 } 59 }); 60 }; 61 62 // result is an array, but in js, an array is also an object 63 // Assign all the metadata properties to it, mirroring the results arrayObject in php 64 function arrayObject(data) { 65 const result = data?.values || []; 66 if (Array.isArray(result) && data?.constructor === Object) { 67 Object.keys(data).forEach(key => { 68 if (key !== 'values') { 69 result[key] = data[key]; 70 } 71 }); 72 } 73 return result; 74 } 75 76 // https://docs.civicrm.org/dev/en/latest/api/interfaces/#ajax 77 CRM.api4 = function(entity, action, params, index) { 78 return new Promise(function(resolve, reject) { 79 if (typeof entity === 'string') { 80 $.post(CRM.url('civicrm/ajax/api4/' + entity + '/' + action), { 81 params: JSON.stringify(params), 82 index: index 83 }) 84 .done(function (data) { 85 resolve(arrayObject(data)); 86 }) 87 .fail(function (data) { 88 reject(data.responseJSON); 89 }); 90 } else { 91 $.post(CRM.url('civicrm/ajax/api4'), { 92 calls: JSON.stringify(entity) 93 }) 94 .done(function(data) { 95 _.each(data, function(item, key) { 96 data[key] = arrayObject(item); 97 }); 98 resolve(data); 99 }) 100 .fail(function (data) { 101 reject(data.responseJSON); 102 }); 103 } 104 }); 105 }; 106 107 /** 108 * AJAX api 109 * @link https://docs.civicrm.org/dev/en/latest/api/interfaces/#ajax 110 */ 111 CRM.api3 = function(entity, action, params, status) { 112 if (typeof(entity) === 'string') { 113 params = { 114 entity: entity, 115 action: action.toLowerCase(), 116 json: JSON.stringify(params || {}) 117 }; 118 } else { 119 params = { 120 entity: 'api3', 121 action: 'call', 122 json: JSON.stringify(entity) 123 }; 124 status = action; 125 } 126 var ajax = $.ajax({ 127 url: CRM.url('civicrm/ajax/rest'), 128 dataType: 'json', 129 data: params, 130 type: params.action.indexOf('get') === 0 ? 'GET' : 'POST' 131 }); 132 if (status) { 133 // Default status messages 134 if (status === true) { 135 status = {success: params.action === 'delete' ? ts('Removed') : ts('Saved')}; 136 if (params.action.indexOf('get') === 0) { 137 status.start = ts('Loading...'); 138 status.success = null; 139 } 140 } 141 var messages = status === true ? {} : status;
142 CRM.status(status, ajax); 143 } 144 return ajax; 145 }; 146 147 /** 148 * @deprecated 149 * AJAX api 150 */ 151 CRM.api = function(entity, action, params, options) { 152 // Default settings 153 var settings = { 154 context: null, 155 success: function(result, settings) { 156 return true; 157 }, 158 error: function(result, settings) { 159 $().crmError(result.error_message, ts('Error')); 160 return false; 161 }, 162 callBack: function(result, settings) { 163 if (result.is_error == 1) { 164 return settings.error.call(this, result, settings); 165 } 166 return settings.success.call(this, result, settings); 167 }, 168 ajaxURL: 'civicrm/ajax/rest' 169 }; 170 action = action.toLowerCase(); 171 // Default success handler 172 switch (action) { 173 case "update": 174 case "create": 175 case "setvalue": 176 case "replace": 177 settings.success = function() { 178 CRM.status(ts('Saved')); 179 return true; 180 }; 181 break; 182 case "delete": 183 settings.success = function() { 184 CRM.status(ts('Removed')); 185 return true; 186 }; 187 } 188 params = { 189 entity: entity, 190 action: action, 191 json: JSON.stringify(params) 192 }; 193 // Pass copy of settings into closure to preserve its value during multiple requests 194 (function(stg) { 195 $.ajax({ 196 url: stg.ajaxURL.indexOf('http') === 0 ? stg.ajaxURL : CRM.url(stg.ajaxURL), 197 dataType: 'json', 198 data: params, 199 type: action.indexOf('get') < 0 ? 'POST' : 'GET', 200 success: function(result) { 201 stg.callBack.call(stg.context, result, stg); 202 } 203 }); 204 })($.extend({}, settings, options)); 205 }; 206 207 $.widget('civi.crmSnippet', { 208 options: { 209 url: null, 210 block: true, 211 post: null, 212 crmForm: null 213 }, 214 _originalContent: null, 215 _originalUrl: null, 216 isOriginalUrl: function() { 217 var 218 args = {}, 219 same = true, 220 newUrl = this._formatUrl(this.options.url), 221 oldUrl = this._formatUrl(this._originalUrl); 222 // Compare path 223 if (newUrl.split('?')[0] !== oldUrl.split('?')[0]) { 224 return false; 225 } 226 // Compare arguments 227 $.each((newUrl.split('?')[1] || '').split('&'), function(k, v) { 228 var arg = v.split('='); 229 args[arg[0]] = arg[1]; 230 }); 231 $.each((oldUrl.split('?')[1] || '').split('&'), function(k, v) { 232 var arg = v.split('='); 233 if (args[arg[0]] !== undefined && arg[1] !== args[arg[0]]) { 234 same = false; 235 } 236 }); 237 return same; 238 }, 239 resetUrl: function() { 240 this.options.url = this._originalUrl; 241 }, 242 _create: function() { 243 this.element.addClass('crm-ajax-container'); 244 if (!this.element.is('.crm-container *')) { 245 this.element.addClass('crm-container'); 246 } 247 this._handleOrderLinks(); 248 // Set default if not supplied 249 this.options.url = this.options.url || document.location.href; 250 this._originalUrl = this.options.url; 251 }, 252 _onFailure: function(data, status) { 253 var msg, title = ts('Network Error'); 254 if (this.options.block) this.element.unblock(); 255 this.element.trigger('crmAjaxFail', data); 256 switch (status) { 257 case 'Forbidden': 258 title = ts('Access Denied'); 259 msg = ts('Ensure you are still logged in and have permission to access this feature.'); 260 break; 261 default: 262 msg = ts('Unable to reach the server. Please refresh this page in your browser and try again.'); 263 } 264 CRM.alert(msg, title, 'error'); 265 }, 266 _onError: function(data) { 267 this.element.attr('data-unsaved-changes', 'false').trigger('crmAjaxError', data); 268 if (this.options.crmForm && this.options.crmForm.autoClose && this.element.data('uiDialog')) { 269 this.element.dialog('close'); 270 } 271 }, 272 _formatUrl: function(url, snippetType) { 273 // Strip hash 274 url = url.split('#')[0]; 275 // Add snippet argument to url 276 if (snippetType) { 277 if (url.search(/[&?]snippet=/) < 0) { 278 url += (url.indexOf('?') < 0 ? '?' : '&') + 'snippet=' + snippetType; 279 } else { 280 url = url.replace(/([&?])snippet=[^&]*/, '$1snippet=' + snippetType); 281 } 282 // See Civi\Angular\AngularLoader 283 if (snippetType === 'json' && CRM.angular) { 284 url += '&crmAngularModules=' + CRM.angular.modules.join(); 285 } 286 } 287 return url; 288 }, 289 // Hack to deal with civicrm legacy sort functionality 290 _handleOrderLinks: function() { 291 var that = this; 292 $('a.crm-weight-arrow', that.element).click(function(e) { 293 if (that.options.block) that.element.block(); 294 $.getJSON(that._formatUrl(this.href, 'json')).done(function() { 295 that.refresh(); 296 }); 297 e.stopImmediatePropagation(); 298 return false;
299 }); 300 }, 301 _ajax: function(url) { 302 if (!this.options.post || !this.isOriginalUrl()) { 303 return $.getJSON(url); 304 } 305 return $.post({ 306 url: url, 307 dataType: 'json', 308 data: this.options.post 309 }); 310 }, 311 refresh: function() { 312 var that = this, 313 hash = this.options.url.split('#')[1], 314 url = this._formatUrl(this.options.url, 'json'); 315 $(this.element).data('urlHash', hash); 316 if (this.options.crmForm) $('form', this.element).ajaxFormUnbind(); 317 if (this.options.block) this.element.block(); 318 this._ajax(url).then(function(data) { 319 if (data.status === 'redirect') { 320 that.options.url = data.userContext; 321 return that.refresh(); 322 } 323 if (that.options.block) that.element.unblock(); 324 if (!$.isPlainObject(data)) { 325 that._onFailure(data); 326 return; 327 } 328 if (data.status === 'error') { 329 that._onError(data); 330 return; 331 } 332 if (data.settings) { 333 $.extend(true, CRM, data.settings); 334 } 335 if (data.scriptUrls) { 336 data.scriptUrls.forEach(function(scriptUrl) { 337 if ($('script[src="' + scriptUrl + '"]').length === 0) { 338 $('<script type="text/javascript" src="' + scriptUrl + '"></script>').appendTo('head'); 339 } 340 }); 341 } 342 if (data.styleUrls) { 343 data.styleUrls.forEach(function(styleUrl) { 344 if ($('link[href="' + styleUrl + '"]').length === 0) { 345 $('<link rel="stylesheet" href="' + styleUrl + '">').appendTo('head'); 346 } 347 }); 348 } 349 data.url = url; 350 that.element.trigger('crmUnload').trigger('crmBeforeLoad', data); 351 that._beforeRemovingContent(); 352 that.element.html(data.content); 353 that._handleOrderLinks(); 354 that.element.trigger('crmLoad', data); 355 if (that.options.crmForm) that.element.trigger('crmFormLoad', data); 356 // This is only needed by forms that load via ajax but submit without ajax, e.g. configure contribution page tabs 357 // TODO: remove this when those forms have been converted to use ajax submit 358 if (data.status === 'form_error' && $.isPlainObject(data.errors)) { 359 that.element.trigger('crmFormError', data); 360 $.each(data.errors, function(formElement, msg) { 361 $('[name="'+formElement+'"]', that.element).crmError(msg); 362 }); 363 } 364 }, function(data, msg, status) { 365 that._onFailure(data, status); 366 }); 367 }, 368 // Perform any cleanup needed before removing/replacing content 369 _beforeRemovingContent: function() { 370 var that = this; 371 // Save original content to be restored if widget is destroyed 372 if (this._originalContent === null) { 373 $('.blockUI', this.element).remove(); 374 this._originalContent = this.element.contents().detach(); 375 } 376 if (this.options.crmForm) $('form', this.element).ajaxFormUnbind(); 377 }, 378 _destroy: function() { 379 this.element.removeClass('crm-ajax-container').trigger('crmUnload'); 380 this._beforeRemovingContent(); 381 if (this._originalContent !== null) { 382 this.element.empty().append(this._originalContent); 383 } 384 } 385 }); 386 387 var dialogCount = 0, 388 exclude = '[href^="#"], [href^=javascript], [onclick], .no-popup, .cancel'; 389 390 CRM.loadPage = function(url, options) { 391 var settings = { 392 target: '#crm-ajax-dialog-' + (dialogCount++), 393 dialog: (options && options.target) ? false : {} 394 }; 395 if (options) $.extend(true, settings, options); 396 settings.url = url; 397 // Create new dialog 398 if (settings.dialog) { 399 settings.dialog = CRM.utils.adjustDialogDefaults(settings.dialog); 400 $('<div id="' + settings.target.substring(1) + '"></div>') 401 .dialog(settings.dialog) 402 .parent().find('.ui-dialog-titlebar') 403 .append($('<a class="crm-dialog-titlebar-print ui-dialog-titlebar-close" title="'+ts('Print window')+'" target="_blank" style="right:3.8em;"/>') 404 .button({icons: {primary: 'fa-print'}, text: false})); 405 } 406 // Add handlers to new or existing dialog 407 if ($(settings.target).data('uiDialog')) { 408 $(settings.target) 409 .on('dialogclose', function() { 410 if (settings.dialog && $(this).attr('data-unsaved-changes') !== 'true') { 411 $(this).crmSnippet('destroy').dialog('destroy').remove(); 412 } 413 })
414 .on('crmLoad', function(e, data) { 415 // Set title 416 if (e.target === $(settings.target)[0] && data && !settings.dialog.title && data.title) { 417 $(this).dialog('option', 'title', data.title); 418 } 419 // Update print url 420 $(this).parent().find('a.crm-dialog-titlebar-print').attr('href', $(this).data('civiCrmSnippet')._formatUrl($(this).crmSnippet('option', 'url'), '1')); 421 }); 422 } 423 $(settings.target).crmSnippet(settings).crmSnippet('refresh'); 424 return $(settings.target); 425 }; 426 CRM.loadForm = function(url, options) { 427 var formErrors = [], settings = { 428 crmForm: { 429 ajaxForm: {}, 430 autoClose: true, 431 validate: true, 432 refreshAction: ['next_new', 'upload_new'], 433 cancelButton: '.cancel', 434 openInline: 'a.open-inline, a.button, a.action-item, a.open-inline-noreturn', 435 onCancel: function(event) {} 436 } 437 }; 438 // Move options that belong to crmForm. Others will be passed through to crmSnippet 439 if (options) $.each(options, function(key, value) {
440 if (typeof(settings.crmForm[key]) !== 'undefined') { 441 settings.crmForm[key] = value; 442 } 443 else { 444 settings[key] = value; 445 } 446 }); 447 448 var widget = CRM.loadPage(url, settings).off('.crmForm'); 449 450 // CRM-14353 - Warn of unsaved changes for all forms except those which have opted out 451 function cancelAction() { 452 var dirty = CRM.utils.initialValueChanged($('form:not([data-warn-changes=false])', widget)); 453 widget.attr('data-unsaved-changes', dirty ? 'true' : 'false'); 454 if (dirty) { 455 var id = widget.attr('id') + '-unsaved-alert', 456 title = widget.dialog('option', 'title'), 457 alert = CRM.alert('<p>' + ts('%1 has not been saved.', {1: title}) + '</p><p><a href="#" id="' + id + '">' + ts('Restore') + '</a></p>', ts('Unsaved Changes'), 'alert unsaved-dialog', {expires: 60000}); 458 $('#' + id).button({icons: {primary: 'fa-undo'}}).click(function(e) { 459 widget.attr('data-unsaved-changes', 'false').dialog('open'); 460 e.preventDefault(); 461 }); 462 } 463 } 464 465 if (widget.data('uiDialog')) widget.on('dialogbeforeclose', function(e) { 466 // CRM-14353 - Warn unsaved changes if user clicks close button or presses "esc" 467 if (e.originalEvent) { 468 cancelAction(); 469 } 470 }); 471 472 widget.on('crmFormLoad.crmForm', function(event, data) { 473 var $el = $(this).attr('data-unsaved-changes', 'false'), 474 settings = $el.crmSnippet('option', 'crmForm'); 475 if (settings.cancelButton) $(settings.cancelButton, this).click(function(e) { 476 e.preventDefault(); 477 var returnVal = settings.onCancel.call($el, e); 478 if (returnVal !== false) { 479 $el.trigger('crmFormCancel', e); 480 if ($el.data('uiDialog') && settings.autoClose) { 481 cancelAction(); 482 $el.dialog('close'); 483 } 484 else if (!settings.autoClose) { 485 $el.crmSnippet('resetUrl').crmSnippet('refresh'); 486 } 487 } 488 }); 489 if (settings.validate) { 490 // Validate all forms that are not angular-based 491 $("form", this).not('crm-angular-js *').crmValidate(); 492 } 493 $("form:not('[data-no-ajax-submit=true]')", this).ajaxForm($.extend({ 494 url: data.url.replace(/reset=1[&]?/, ''), 495 dataType: 'json', 496 success: function(response) { 497 if (response.content === undefined) { 498 $el.trigger('crmFormSuccess', response); 499 // Reset form for e.g. "save and new" 500 if (response.userContext && (response.status === 'redirect' || (settings.refreshAction && $.inArray(response.buttonName, settings.refreshAction) >= 0))) { 501 // Force reset of original url 502 $el.data('civiCrmSnippet')._originalUrl = response.userContext; 503 $el.crmSnippet('resetUrl').crmSnippet('refresh'); 504 } 505 // Close if we are on the original url or the action was "delete" (in which case returning to view may be inappropriate) 506 else if ($el.data('uiDialog') && (settings.autoClose || response.action === 8)) { 507 $el.dialog('close'); 508 } 509 else if (settings.autoClose === false) { 510 $el.crmSnippet('resetUrl').crmSnippet('refresh'); 511 } 512 } 513 else { 514 if ($el.crmSnippet('option', 'block')) $el.unblock(); 515 response.url = data.url; 516 $el.html(response.content).trigger('crmLoad', response).trigger('crmFormLoad', response); 517 if (response.status === 'form_error') { 518 formErrors = []; 519 $el.trigger('crmFormError', response); 520 $.each(response.errors || [], function(formElement, msg) { 521 formErrors.push($('[name="'+formElement+'"]', $el).crmError(msg)); 522 }); 523 } 524 } 525 }, 526 beforeSubmit: function(submission) { 527 $.each(formErrors, function() { 528 if (this && this.close) this.close(); 529 }); 530 if ($el.crmSnippet('option', 'block')) $el.block(); 531 $el.trigger('crmFormSubmit', submission); 532 } 533 }, settings.ajaxForm)); 534 if (settings.openInline) { 535 settings.autoClose = $el.crmSnippet('isOriginalUrl'); 536 $(this).off('.openInline').on('click.openInline', settings.openInline, function(e) { 537 if ($(this).is(exclude + ', .crm-popup, [target=crm-popup]')) { 538 return; 539 } 540 if ($(this).hasClass('open-inline-noreturn')) { 541 // Force reset of original url 542 $el.data('civiCrmSnippet')._originalUrl = $(this).attr('href'); 543 } 544 $el.crmSnippet('option', 'url', $(this).attr('href')).crmSnippet('refresh'); 545 e.preventDefault(); 546 }); 547 } 548 if ($el.data('uiDialog')) { 549 // Show form buttons as part of the dialog 550 var buttonContainers = '.crm-submit-buttons, .action-link', 551 buttons = [], 552 added = []; 553 $(buttonContainers, $el).find('.crm-form-submit, .crm-form-xbutton, a.button, button').each(function() { 554 var $el = $(this), 555 label = $el.is('input') ? $el.attr('value') : $el.text(), 556 identifier = $el.attr('name') || $el.attr('href'); 557 $el.attr('tabindex', '-1'); 558 if (!identifier || identifier === '#' || $.inArray(identifier, added) < 0) { 559 var $icon = $el.find('.icon, .crm-i'), 560 button = {'data-identifier': identifier, text: label, click: function() { 561 $el[0].click(); 562 }}; 563 if ($icon.length) { 564 button.icons = {primary: $icon.attr('class')}; 565 } else { 566 var action = $el.attr('crm-icon') || ($el.hasClass('cancel') ? 'fa-times' : 'fa-check'); 567 button.icons = {primary: action}; 568 } 569 buttons.push(button); 570 added.push(identifier); 571 } 572 // display:none causes the form to not submit when pressing "enter"
573 $el.parents(buttonContainers).css({height: 0, padding: 0, margin: 0, overflow: 'hidden'}).attr('aria-hidden', 'true'); 574 }); 575 $el.dialog('option', 'buttons', buttons); 576 } 577 // Allow a button to prevent ajax submit 578 $('input[data-no-ajax-submit=true], button[data-no-ajax-submit=true]').click(function() { 579 $(this).closest('form').ajaxFormUnbind(); 580 }); 581 // For convenience, focus the first field 582 $('input[type=text], textarea, select', this).filter(':visible').first().not('.dateplugin').focus(); 583 }); 584 return widget; 585 }; 586 /** 587 * Handler for jQuery click event e.g. $('a').click(CRM.popup); 588 */ 589 CRM.popup = function(e) { 590 var $el = $(this).first(), 591 url = $el.attr('href'), 592 popup = $el.data('popup-type') === 'page' ? CRM.loadPage : CRM.loadForm, 593 settings = $el.data('popup-settings') || {}, 594 formData = false; 595 settings.dialog = settings.dialog || {}; 596 if (e.isDefaultPrevented() || !CRM.config.ajaxPopupsEnabled || !url || $el.is(exclude + ', .open-inline, .open-inline-noreturn')) { 597 return; 598 } 599 // Sized based on css class 600 if ($el.hasClass('small-popup')) { 601 settings.dialog.width = 400; 602 settings.dialog.height = 300; 603 } 604 else if ($el.hasClass('medium-popup')) { 605 settings.dialog.width = '50%'; 606 settings.dialog.height = '30%'; 607 } 608 var dialog = popup(url, settings); 609 // Trigger events from the dialog on the original link element 610 $el.trigger('crmPopupOpen', [dialog]); 611 // Listen for success events and buffer them so we only trigger once 612 dialog.on('crmFormSuccess.crmPopup crmPopupFormSuccess.crmPopup', function(e, data) { 613 formData = data; 614 }); 615 dialog.on('dialogclose.crmPopup', function(e, data) { 616 if (formData) { 617 $el.trigger('crmPopupFormSuccess', [dialog, formData]); 618 } 619 $el.trigger('crmPopupClose', [dialog, data]); 620 }); 621 e.preventDefault(); 622 }; 623 /** 624 * An event callback for CRM.popup or a standalone function to refresh the content around a given element 625 * @param e {event|selector} 626 */ 627 CRM.refreshParent = function(e) { 628 // Use e.target if input smells like an event, otherwise assume it's a jQuery selector 629 var $el = (e.stopPropagation && e.target) ? $(e.target) : $(e), 630 $table = $el.closest('.dataTable'); 631 // Call native refresh method on ajax datatables 632 if ($table.length && $.fn.DataTable.fnIsDataTable($table[0]) && $table.dataTable().fnSettings().sAjaxSource) {
633 // Refresh ALL datatables - needed for contact relationship tab 634 $.each($.fn.dataTable.fnTables(), function() { 635 if ($(this).dataTable().fnSettings().sAjaxSource) $(this).unblock().dataTable().fnDraw(); 636 }); 637 } 638 // Otherwise refresh the nearest crmSnippet 639 else { 640 $el.closest('.crm-ajax-container, #crm-main-content-wrapper').crmSnippet().crmSnippet('refresh'); 641 } 642 }; 643 644 $(function($) { 645 $('body') 646 .on('click', 'a.crm-popup, a[target=crm-popup]', CRM.popup) 647 // Close unsaved dialog messages 648 .on('dialogopen', function(e) { 649 $('.alert.unsaved-dialog .ui-notify-cross', '#crm-notification-container').click(); 650 }) 651 // Destroy old unsaved dialog 652 .on('dialogcreate', function(e) { 653 $('.ui-dialog-content.crm-ajax-container:hidden[data-unsaved-changes=true]').crmSnippet('destroy').dialog('destroy').remove(); 654 }) 655 // Ensure wysiwyg content is updated prior to ajax submit 656 .on('form-pre-serialize', function(e) { 657 $('.crm-wysiwyg-enabled', e.target).each(function() { 658 CRM.wysiwyg.updateElement(this); 659 }); 660 }) 661 // Auto-resize dialogs when loading content 662 .on('crmLoad dialogopen', 'div.ui-dialog.ui-resizable.crm-container', function(e) { 663 var 664 $wrapper = $(this), 665 $dialog = $wrapper.children('.ui-dialog-content'); 666 // small delay to allow contents to render 667 window.setTimeout(function() { 668 var currentHeight = $wrapper.outerHeight(), 669 padding = currentHeight - $dialog.height(), 670 newHeight = $dialog.prop('scrollHeight') + padding, 671 menuHeight = $('#civicrm-menu').outerHeight(); 672 if ($('body').hasClass('crm-menubar-below-cms-menu')) { 673 menuHeight += $('#civicrm-menu').offset().top; 674 } 675 var maxHeight = $(window).height() - menuHeight; 676 newHeight = newHeight > maxHeight ? maxHeight : newHeight; 677 if (newHeight > (currentHeight + 15)) { 678 $dialog.dialog('option', { 679 position: {my: 'center', at: 'center center+' + (menuHe
679ight / 2), of: window}, 680 height: newHeight 681 }); 682 } 683 }, 500); 684 }); 685 }); 686 687}(jQuery, CRM, _));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.