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