1(function ($) { 2 'use strict'; 3 4 /** 5 * Class containing functionality for Facet API. 6 */ 7 Drupal.ajax_facets = {}; 8 // Store current query value. 9 Drupal.ajax_facets.queryState = null; 10 // State of each facet. 11 Drupal.ajax_facets.facetQueryState = null; 12 // Determine if it is first page load. Will be reset in Drupal.ajax_facets.initHistoryState. 13 Drupal.ajax_facets.firstLoad = true; 14 // Settings of each ajax facet. 15 Drupal.ajax_facets.facetsSettings = {}; 16 // Buttons for ajax facets. 17 Drupal.ajax_facets.ajax_facets_buttons = false; 18 // Force update of the results 19 Drupal.ajax_facets.force_update_results = false; 20 // Tooltip timeout handler. 21 Drupal.ajax_facets.tooltipTimeout; 22 // Name of the facet which was clicked. 23 Drupal.ajax_facets.current_facet_name = undefined; 24 25 // You can use it for freeze facet form elements while ajax is processing. 26 Drupal.ajax_facets.beforeAjaxCallbacks = {}; 27 28 // You can use it to make actions after the ajax has updated the content. 29 Drupal.ajax_facets.afterContentUpdateCallbacks = {}; 30 31 Drupal.ajax_facets.beforeAjax = function () { 32 $.each(Drupal.ajax_facets.beforeAjaxCallbacks, function () { 33 if ($.isFunction(this)) { 34 this(); 35 } 36 }); 37 }; 38 39 Drupal.ajax_facets.afterContentUpdate = function (ajax, response) { 40 Drupal.ajax_facets.force_update_results = false; 41 $.each(Drupal.ajax_facets.afterContentUpdateCallbacks, function () { 42 if ($.isFunction(this)) { 43 this(ajax, response); 44 } 45 }); 46 }; 47 48 Drupal.behaviors.ajax_facets = { 49 attach: function (context, settings) { 50 if (!Drupal.ajax_facets.queryState) { 51 // Add property for views and set data about facet values. 52 Drupal.ajax_facets.queryState = { 53 'views': new Array(), 54 'f': settings.facetapi.defaultQuery ? settings.facetapi.defaultQuery : [] 55 }; 56 57 // We will send original search path to server to get back proper reset links. 58 if (settings.facetapi.searchPath) { 59 Drupal.ajax_facets.queryState['searchPath'] = settings.facetapi.searchPath; 60 } 61 if (settings.facetapi.index_id) { 62 Drupal.ajax_facets.queryState['index_id'] = settings.facetapi.index_id; 63 } 64 // Check view name and display name for all views. 65 if (settings.facetapi.views) { 66 $.each(settings.facetapi.views, function(key, view) { 67 Drupal.ajax_facets.queryState['views'][key] = {}; 68 // Get view dom id. 69 let viewDomId = Drupal.ajax_facets.getViewDomId(settings, key); 70 if (viewDomId) { 71 // Take settings from view if it works by AJAX. 72 if (Drupal.views && Drupal.settings.views && Drupal.settings.views.ajaxViews && Drupal.settings.views.ajaxViews['views_dom_id:' + viewDomId]) { 73 $.extend(Drupal.ajax_facets.queryState['views'][key], Drupal.settings.views.ajaxViews['views_dom_id:' + viewDomId]); 74 } 75 // If this view doesn't use ajax. 76 else { 77 Drupal.ajax_facets.queryState['views'][key] = { 78 'view_dom_id': viewDomId, 79 'view_name': view.view_name, 80 'view_display_id': view.view_display_id 81 }; 82 // Respect view arguments. 83 let nameDisplay = view.view_name + ':' + view.view_display_id; 84 if (settings.facetapi.view_args[nameDisplay]) { 85 Drupal.ajax_facets.queryState['views'][key]['view_args'] = settings.facetapi.view_args[nameDisplay]; 86 } 87 // Respect view path. 88 if (settings.facetapi.view_path[nameDisplay]) { 89 Drupal.ajax_facets.queryState['views'][key]['view_path'] = settings.facetapi.view_path[nameDisplay]; 90 } 91 } 92 93 Drupal.ajax_facets.queryStateInitialized = 1; 94 } 95 96 // Respect arguments in exposed form. 97 if (view.view_name && view.view_display_id) { 98 let nameDisplay = view.view_name + ':' + view.view_display_id; 99 if (settings.facetapi.exposed_input[nameDisplay]) { 100 $.extend(Drupal.ajax_facets.queryState, settings.facetapi.exposed_input[nameDisplay]); 101 } 102 } 103 }); 104 } 105 106 if (settings.facetapi.facet_field) { 107 Drupal.ajax_facets.queryState['facet_field'] = settings.facetapi.facet_field; 108 } 109 } 110 111 // Iterates over facet settings, applies functionality like the "Show more" 112 // links for block realm facets. 113 // @todo We need some sort of JS API so we don't have to make decisions 114 // based on the realm. 115 if (settings.facetapi) { 116 for (var index in settings.facetapi.facets) { 117 Drupal.ajax_facets.bindResetLink(settings.facetapi.facets[index].id, index, settings); 118 119 // Checkboxes. 120 if (settings.facetapi.facets[index].widget === 'facetapi_ajax_checkboxes') { 121 $('#' + settings.facetapi.facets[index].id + '-wrapper input.facet-multiselect-checkbox:not(.processed)').change( 122 [settings.facetapi.facets[index]], 123 Drupal.ajax_facets.processCheckboxes 124 ).addClass('processed'); 125 } 126 127 // Selectboxes. 128 if (settings.facetapi.facets[index].widget === 'facetapi_ajax_select') { 129 $('#' + settings.facetapi.facets[index].id + '-wrapper select:not(.processed)').each(function () { 130 $(this).change( 131 [settings.facetapi.facets[index]], 132 Drupal.ajax_facets.processSelectbox 133 ).addClass('processed'); 134 }); 135 } 136 137 // Links. 138 if (settings.facetapi.facets[index].widget === 'facetapi_ajax_links') { 139 $('#' + settings.facetapi.facets[index].id + '-wrapper a:not(.processed)').each(function () { 140 $(this).click( 141 [settings.facetapi.facets[index]], 142 Drupal.ajax_facets.processLink 143 ).addClass('processed'); 144 }); 145 } 146 147 // Ranges. 148 if (settings.facetapi.facets[index].widget === 'facetapi_ajax_ranges') { 149 $('#' + settings.facetapi.facets[index].id + '-wrapper div.slider-wrapper:not(.processed)').each(function () { 150 let $sliderWrapper = $(this); 151 $sliderWrapper.slider({ 152 range: true, 153 min: parseFloat($sliderWrapper.data('min')), 154 max: parseFloat($sliderWrapper.data('max')), 155 values: [ parseFloat($sliderWrapper.data('min-val')), parseFloat($sliderWrapper.data('max-val')) ], 156 stop: function( event, ui ) { 157 Drupal.ajax_facets.processSlider($sliderWrapper, ui.values[0], ui.values[1]); 158 } 159 }).addClass('processed'); 160 161 // Bind input fields. 162 let $sliderWrapperParent = $sliderWrapper.parent(); 163 $sliderWrapperParent.find("input[type='text']").each(function() { 164 $(this).change(function() { 165 let min = parseFloat($sliderWrapperParent.find('.ajax-facets-slider-amount-min').val()); 166 let max = parseFloat($sliderWrapperParent.find('.ajax-facets-slider-amount-max').val()); 167 // If values are numeric and min less than max. 168 if (isFinite(min) && isFinite(max) && min < max) { 169 $sliderWrapper.slider('values', 0, min); 170 $sliderWrapper.slider('values', 1, max); 171 Drupal.ajax_facets.processSlider($sliderWrapper, min, max); 172 } 173 }); 174 }); 175 176 }); 177 } 178 179 if (null != settings.facetapi.facets[index].limit) { 180 // Applies soft limit to the list. 181 if (Drupal.facetapi) { 182 Drupal.facetapi.applyLimit(settings.facetapi.facets[index]); 183 } 184 } 185 186 // Save settings for each facet by name. 187 Drupal.ajax_facets.facetsSettings[settings.facetapi.facets[index].facetName] = settings.facetapi.facets[index]; 188 } 189 } 190 191 // Hide blocks with ajax-facets-empty-behavior. 192 $('.ajax-facets-empty-behavior').parents('.block--ajax_facets').hide(); 193 194 // Add facets tooltip. 195 $('body').once(function () { 196 $(this).append('<div id="ajax-facets-tooltip"><span></span></div>'); 197 }); 198 } 199 }; 200 201 Drupal.behaviors.ajax_facets_block = { 202 attach: function (context, settings) { 203 // Reset all link. 204 $('.ajax-facets-reset-all-link:not(.processed)').bind('click', function (e) { 205 e.preventDefault(); 206 207 // Clear all facets from queryState facets array. 208 Drupal.ajax_facets.queryState.f = []; 209 210 // Send ajax query with first found facet. 211 Drupal.ajax_facets.force_update_results = true; 212 Drupal.ajax_facets.sendAjaxQuery({ 213 pushStateNeeded: true, 214 searchResultsNeeded: true 215 }); 216 }).addClass('processed'); 217 218 let $submitLink = $('.ajax-facets-submit-all-link:not(.processed)'); 219 if ($submitLink.length) { 220 // Submit link found. 221 Drupal.ajax_facets.ajax_facets_buttons = true; 222 // Submit all link. 223 $submitLink.bind('click', function (e) { 224 e.preventDefault(); 225 // Send ajax query with first found facet. 226 Drupal.ajax_facets.force_update_results = true; 227 Drupal.ajax_facets.sendAjaxQuery({ 228 pushStateNeeded: true, 229 searchResultsNeeded: true 230 }); 231 }).addClass('processed'); 232 } 233 } 234 }; 235 236 237 Drupal.ajax_facets.bindResetLink = function (facetWrapperId, index, settings) { 238 let facet_values = Drupal.ajax_facets.getFacetValues(); 239 let $facetWrapper = $('#' + facetWrapperId + '-wrapper'); 240 if (facet_values[settings.facetapi.facets[index]['facetName']]) { 241 $facetWrapper.find('a.reset-link').show(); 242 } else { 243 $facetWrapper.find('a.reset-link').hide(); 244 } 245 246 $facetWrapper.find('a.reset-link:not(".processed")').addClass('processed').click(function (event) { 247 let $facet = $(this).parent().find('[data-facet-name]').first(); 248 let facetName = $facet.data('facet-name'); 249 Drupal.ajax_facets.excludeCurrentFacet(facetName); 250 Drupal.ajax_facets.sendAjaxQuery({ 251 pushStateNeeded: true, 252 searchResultsNeeded: true 253 }); 254 event.preventDefault(); 255 }); 256 }; 257 258 /** 259 * Callback for onClick event for widget selectbox. 260 */ 261 Drupal.ajax_facets.processSelectbox = function (event) {
262 let $this = $(this), 263 facetName = $this.data('facet-name'); 264 // Init history. 265 Drupal.ajax_facets.initHistoryState($this); 266 // If facets are already defined in queryState. 267 if (Drupal.ajax_facets.queryState['f']) { 268 // Exclude all values for this facet from query. 269 Drupal.ajax_facets.excludeCurrentFacet(facetName); 270 271 /* Default value. */ 272 if ($this.find(":selected").val() === '_none') { 273 delete Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length]; 274 } else { 275 Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = facetName + ':' + $this.find(":selected").val(); 276 } 277 } 278 279 Drupal.ajax_facets.sendAjaxQuery({ 280 pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons, 281 searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons 282 }, $this); 283 }; 284 285 /** 286 * Callback for onClick event for widget checkboxes. 287 */ 288 Drupal.ajax_facets.processCheckboxes = function (event) { 289 let $this = $(this), 290 facetName = $this.data('facet-name'), 291 name_value = facetName + ':' + $this.attr('data-facet-value'),// $.data can round decimal values like 4.0 to 4, avoid it. 292 rawFacetName = $this.data('raw-facet-name'); 293 // Init history. 294 Drupal.ajax_facets.initHistoryState($this); 295 // If facets are already defined in queryState. 296 if (Drupal.ajax_facets.queryState['f']) { 297 let queryNew = []; 298 if ($this.is(':checked')) { 299 let addCurrentParam = true; 300 for (var index in Drupal.ajax_facets.queryState['f']) { 301 if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) { 302 // If we already have this value in queryState. 303 if (Drupal.ajax_facets.queryState['f'][index] == name_value) { 304 addCurrentParam = false; 305 } 306 } 307 } 308 // Add new value if need. 309 if (addCurrentParam) { 310 // Exclude all other values of this facet from query. 311 if (Drupal.ajax_facets.facetsSettings[rawFacetName].limit_active_items) { 312 Drupal.ajax_facets.excludeCurrentFacet(facetName); 313 } 314 Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = name_value; 315 } 316 } 317 // If we unset filter, remove it from query. 318 else { 319 for (var index in Drupal.ajax_facets.queryState['f']) { 320 if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) { 321 if (Drupal.ajax_facets.queryState['f'][index] != name_value) { 322 queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index]; 323 } 324 } 325 } 326 Drupal.ajax_facets.queryState['f'] = queryNew; 327 } 328 } 329 330 Drupal.ajax_facets.sendAjaxQuery({ 331 pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons, 332 searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons 333 }, $this); 334 }; 335 336 /** 337 * Callback for onClick event for widget links. 338 */ 339 Drupal.ajax_facets.processLink = function (event) { 340 let $this = $(this), 341 facetName = $this.data('facet-name'), 342 name_value = facetName + ':' + $this.attr('data-facet-value'),// $.data can round decimal values like 4.0 to 4, avoid it. 343 rawFacetName = $this.data('raw-facet-name'); 344 // Init history. 345 Drupal.ajax_facets.initHistoryState($this); 346 // If facets are already defined in queryState. 347 if (Drupal.ajax_facets.queryState['f']) { 348 let queryNew = []; 349 /* Handle value - deactivate. */ 350 if ($this.hasClass('facetapi-active')) { 351 for (var index in Drupal.ajax_facets.queryState['f']) { 352 if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) { 353 if (Drupal.ajax_facets.queryState['f'][index] !== name_value) { 354 queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index]; 355 } 356 } 357 } 358 Drupal.ajax_facets.queryState['f'] = queryNew; 359 } 360 /* Handle value - activate. */ 361 else if ($this.hasClass('facetapi-inactive')) { 362 let addCurrentParam = true; 363 for (var index in Drupal.ajax_facets.queryState['f']) { 364 if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) { 365 if (Drupal.ajax_facets.queryState['f'][index] === name_value) { 366 addCurrentParam = false;
367 } 368 } 369 } 370 if (addCurrentParam) { 371 // Exclude all other values of this facet from query. 372 if (Drupal.ajax_facets.facetsSettings[rawFacetName].limit_active_items) { 373 Drupal.ajax_facets.excludeCurrentFacet(facetName); 374 } 375 Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = name_value; 376 } 377 } 378 } 379 380 Drupal.ajax_facets.sendAjaxQuery({ 381 pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons, 382 searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons 383 }, $this); 384 event.preventDefault(); 385 }; 386 387 /** 388 * Callback for slide event for widget ranges. 389 */ 390 Drupal.ajax_facets.processSlider = function($sliderWrapper, min, max) { 391 let $this = $(this), 392 facetName = $sliderWrapper.data('facet-name'); 393 // Init history. 394 Drupal.ajax_facets.initHistoryState($sliderWrapper); 395 // If facets are already defined in queryState. 396 if (Drupal.ajax_facets.queryState['f']) { 397 // Exclude all values for this facet from query. 398 Drupal.ajax_facets.excludeCurrentFacet(facetName); 399 Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = facetName + ':[' + min + ' TO ' + max + ']'; 400 } 401 402 Drupal.ajax_facets.sendAjaxQuery({ 403 pushStateNeeded: !Drupal.ajax_facets.ajax_facets_buttons, 404 searchResultsNeeded: !Drupal.ajax_facets.ajax_facets_buttons 405 }, $this); 406 }; 407 408 /** 409 * Exclude all values for this facet from query. 410 */ 411 Drupal.ajax_facets.excludeCurrentFacet = function (facetName) { 412 facetName = facetName + ':'; 413 let queryNew = []; 414 for (var index in Drupal.ajax_facets.queryState['f']) { 415 if (Drupal.ajax_facets.queryState['f'].hasOwnProperty(index)) { 416 if (Drupal.ajax_facets.queryState['f'][index].substring(0, facetName.length) != facetName) { 417 queryNew[queryNew.length] = Drupal.ajax_facets.queryState['f'][index]; 418 } 419 } 420 } 421 Drupal.ajax_facets.queryState['f'] = queryNew; 422 }; 423 424 /** 425 * Send ajax. 426 */ 427 Drupal.ajax_facets.sendAjaxQuery = function (options, $facet) { 428 Drupal.ajax_facets.beforeAjax(); 429 let data = Drupal.ajax_facets.queryState; 430 // Render the exposed filter data to send along with the ajax request 431 $.each(Drupal.ajax_facets.queryState.views, function (key, view) { 432 let exposedFormId = '#views-exposed-form-' + view.view_name + '-' + view.view_display_id; 433 exposedFormId = exposedFormId.replace(/\_/g, '-'); 434 $.each($(exposedFormId).serializeArray(), function (index, value) { 435 data[value.name] = value.value; 436 }); 437 }); 438 439 // Set or reset current facet. 440 Drupal.ajax_facets.current_facet_name = $facet ? $facet.data('raw-facet-name') : undefined; 441 442 // Do not auto update facets and counts if auto update is turned off in block settings. 443 if (!Drupal.settings.ajax_facets.auto_update_facets && !options.searchResultsNeeded && !options.pushStateNeeded) { 444 return; 445 } 446 447 // Notify Drupal do we need search results or not. 448 data.searchResultsNeeded = options.searchResultsNeeded; 449 450 let settings = { 451 url: encodeURI(Drupal.settings.basePath + Drupal.settings.pathPrefix + 'ajax/ajax_facets/refresh'), 452 submit: {'ajax_facets': data} 453 }; 454 let ajax = new Drupal.ajax(false, false, settings); 455 ajax.success = function (response, status) { 456 // Push new state only on successful ajax response. 457 if (options.pushStateNeeded) { 458 Drupal.ajax_facets.writeState(false); 459 } 460 // Pass back to original method. 461 Drupal.ajax.prototype.success.call(this, response, status); 462 }; 463 ajax.eventResponse(ajax, {}); 464 }; 465 466 Drupal.ajax_facets.getFacetValues = function () { 467 let f = Drupal.ajax_facets.queryState.f; 468 let facets_values = {}; 469 let symbol = ':'; 470 jQuery.each(f, function (k, v) { 471 let parts = v.split(symbol); 472 let value = parts[parts.length - 1]; 473 let appendix = symbol + value; 474 let key = decodeURIComponent(v.substring(0, v.length - appendix.length)); 475 facets_values[key] = value; 476 }); 477 return facets_values; 478 }; 479 480 /* Get view dom id for both modes of views - ajax/not ajax. */ 481 Drupal.ajax_facets.getViewDomId = function(settings, key) { 482 // We use facetapi_view_display_id to find data in settings settings.facetapi.view_dom_id, 483 // because it can has there values like view_dom_id + numeric suffix 484 // if the same views display used several times on the page. 485 let nameDisplay = settings.facetapi.views[key].view_name + ':' + settings.facetapi.views[key].facetapi_view_display_id; 486 if (settings.facetapi.view_dom_id[nameDisplay]) { 487 return settings.facetapi.view_dom_id[nameDisplay] 488 } 489 return false;
490 }; 491 492 /** 493 * Returns query string with selected facets. 494 */ 495 Drupal.ajax_facets.getFacetsQueryUrl = function (baseUrl) { 496 let query = { 497 'f': [] 498 }; 499 500 // Clone variable. 501 $.extend(true, query.f, Drupal.ajax_facets.queryState.f); 502 503 // Facetapi module has a bug when facet name encodes twice. 504 // For example to get this facet work 'category:name:pineapple' it should be 'category%253Aname%3Apineapple'. 505 // It means that first ':' was encoded twice. Why we don't patch facetapi? Because a lot of sites have already 506 // used facetapi module and they links (with wrong encoded facets names) has been indexed by search engines 507 // like Bing or Google. So we just bring this behaviour to ajax_facets module. 508 // Encode each facet filter name (it have already encoded once in FacetapiAjaxWidgetCheckboxes::buildListItems()). 509 for (var filter in query.f) { 510 query.f[filter] = encodeURI(query.f[filter]); 511 } 512 513 if (Drupal.ajax_facets.queryState.query) { 514 query.query = Drupal.ajax_facets.queryState.query; 515 } 516 517 if (Drupal.ajax_facets.queryState.order) { 518 query.order = Drupal.ajax_facets.queryState.order; 519 query.sort = Drupal.ajax_facets.queryState.sort; 520 } 521 522 if (Drupal.ajax_facets.queryState.pages) { 523 query.pages = Drupal.ajax_facets.queryState.pages; 524 } 525 526 // Respect existing query parameters in url. 527 // Merge respected parameters with facet parameters recursively without duplicates. 528 $.extend(true, query, Drupal.ajax_facets.simplifyObject(Drupal.ajax_facets.getAdditionalQueryParameters())); 529 530 // Add query string to base url. 531 if (!$.isEmptyObject(query)) { 532 // Clear empty items. 533 for (var item in query) { 534 if (typeof query[item] == 'undefined' || query[item].length === 0) { 535 delete query[item]; 536 } 537 } 538 let params = $.param(query); 539 // If we have params. 540 if (params.length) { 541 baseUrl += '?' + decodeURIComponent(params); 542 } 543 } 544 545 return baseUrl; 546 }; 547 548 /** 549 * Returns additional (not facet) query parameters from current url. 550 */ 551 Drupal.ajax_facets.getAdditionalQueryParameters = function () { 552 let result = {}; 553 554 // If we have GET params. 555 if (window.location.href.indexOf('?') !== -1) { 556 let respectedParameters = window.location.href.split('?')[1].split('&'); 557 558 // Get all query parameters in array. 559 for (var i = 0; i < respectedParameters.length; i++) { 560 let pair = respectedParameters[i].split('='); 561 562 // Remove brackets from multiple parameter. 563 pair[0] = pair[0].replace(/\[\d*\]/, ''); 564 565 // We interested only in additional parameters but not in facet parameters. 566 if (pair[0] !== 'f') { 567 if (!result[pair[0]]) { 568 result[pair[0]] = []; 569 result[pair[0]].push(pair[1]); 570 } else { 571 result[pair[0]].push(pair[1]); 572 } 573 } 574 } 575 } 576 577 return result; 578 }; 579 580 /** 581 * Returns simplified object. 582 * 583 * If object has array property with only one element then that array will be turn into simple value instead of array. 584 */ 585 Drupal.ajax_facets.simplifyObject = function (obj) { 586 for (var name in obj) { 587 if (obj[name].length === 1) { 588 obj[name] = obj[name][0]; 589 } 590 } 591 592 return obj; 593 }; 594 595 /** 596 * Initialize the history state. We only want to do this on the initial page 597 * load but we can't call it until after a facet has been clicked because we 598 * need to communicate which one is being "deactivated" for our ajax success 599 * handler. 600 */ 601 Drupal.ajax_facets.initHistoryState = function ($facet) { 602 // Set the initial state only initial page load. 603 if (Drupal.ajax_facets.firstLoad) { 604 Drupal.ajax_facets.firstLoad = false; 605 Drupal.ajax_facets.writeState(true); 606 } 607 }; 608 609 /** 610 * Writes new state to browser history, either as a push or replace call. 611 * 612 * History.js library fires "statechange" event even on API push/replace calls. 613 * So before pushing new state to history we should unbind from this event and after bind again. 614 * 615 * @param {boolean} replace Set to true to replace state, rather than push it. 616 */ 617 Drupal.ajax_facets.writeState = function (replace) { 618 let stateUrl = null, 619 state = { 620 facets: Drupal.ajax_facets.queryState['f'] 621 }, 622 title = document.title, 623 method = 'replaceState'; 624 625 if (replace !== true) { 626 stateUrl = Drupal.ajax_facets.getFacetsQueryUrl(Drupal.settings.facetapi.searchUrl); 627 method = 'pushState'; 628 } 629 630 // Try to use HTML5 history object. 631 if (history[method]) { 632 history[method](state, title, stateUrl); 633 } 634 else { 635 // If history.js available - use it. 636 if (Drupal.settings.facetapi.isHistoryJsExists) { 637 let $window = $(window); 638 639 $window.unbind('statechange', Drupal.ajax_facets.reactOnStateChange); 640 History[method](state, title, stateUrl); 641 $window.bind('statechange', Drupal.ajax_facets.reactOnStateChange); 642 } 643 } 644 }; 645 646 /** 647 * Callback for back/forward browser buttons. 648 */ 649 Drupal.ajax_facets.reactOnStateChange = function () { 650 let state = null, 651 facets = []; 652 653 // Try to use HTML5 history object. 654 if (history.pushState) { 655 state = history.state; 656 if (state.length > 0) { 657 facets = state.facets; 658 } 659 } 660 else { 661 // If history.js available - use it. 662 if (Drupal.settings.facetapi.isHistoryJsExists) { 663 state = History.getState(); 664 if (state.length > 0) { 665 facets = state.data.facets; 666 } 667 } 668 } 669 670 // Do something only if paths are match and facets is defined. 671 if (window.location.pathname === Drupal.settings.facetapi.searchUrl && facets) { 672 Drupal.ajax_facets.queryState['f'] = facets; 673 Drupal.ajax_facets.force_update_results = true; 674 Drupal.ajax_facets.sendAjaxQuery({
675 pushStateNeeded: false, 676 searchResultsNeeded: true 677 }); 678 } 679 }; 680 681 // If user opened new page and then clicked browser's back button then would not be fired "statechange" event. 682 // So we need to bind on 'statechange' event and react only once. 683 // All farther work does Drupal.ajax_facets.pushState() function. 684 // Try to use HTML5 history object. 685 if (history) { 686 window.onpopstate = function () { 687 Drupal.ajax_facets.reactOnStateChange(); 688 }; 689 } else { 690 // If history.js Adapter available - use it to bind "statechange" event. 691 if (History.Adapter && Drupal.ajax_facets.firstLoad) { 692 History.Adapter.bind(window, 'statechange', Drupal.ajax_facets.reactOnStateChange); 693 } 694 } 695 696 /* Show tooltip if facet results are not updated by ajax (in settings). */ 697 Drupal.ajax_facets.showTooltip = function ($, response) { 698 // Reset the timeout handler to avoid troubles when user is clicking on items very fast. 699 window.clearTimeout(Drupal.ajax_facets.tooltipTimeout); 700 701 // If we have chosen facet. 702 if (Drupal.ajax_facets.current_facet_name) { 703 let pos = $('[data-raw-facet-name=' + Drupal.ajax_facets.current_facet_name + ']').offset(), 704 $tooltip = $('#ajax-facets-tooltip'); 705 $tooltip.css('top', pos.top - 15); 706 $tooltip.css('left', pos.left - $tooltip.width() - 40); 707 $tooltip.show(); 708 $tooltip.find('span').html(Drupal.t('Found:') + ' ' + response.total_results); 709 710 Drupal.ajax_facets.tooltipTimeout = setTimeout(function () { 711 $tooltip.hide(250); 712 }, 3000); 713 } 714 }; 715 716 if (Drupal.ajax) { 717 // Command for process search results and facets by ajax. 718 Drupal.ajax.prototype.commands.ajax_facets_update_content = function(ajax, response) { 719 if (response.data.activeItems) { 720 Drupal.ajax_facets.facetQueryState = response.data.activeItems; 721 } 722 // After Ajax success we should update reset, apply link to handle proper redirects. 723 if (response.data.resetUrls && Drupal.settings.facetapi.facets) { 724 for (var index in Drupal.settings.facetapi.facets) { 725 if (response.data.resetUrls[Drupal.settings.facetapi.facets[index].facetName]) { 726 // Update path from response. 727 Drupal.settings.facetapi.facets[index].resetPath = response.data.resetUrls[Drupal.settings.facetapi.facets[index].facetName]; 728 } 729 } 730 } 731 732 // Update content. 733 if (response.data.newContent) { 734 for (var id in response.data.newContent) { 735 let $wrapper = $('#' + id + '-wrapper'); 736 let $blockToReplace = $wrapper; 737 if ($blockToReplace.size()) { 738 $blockToReplace.replaceWith(response.data.newContent[id]); 739 } 740 let $block = $wrapper.parents('div.block--ajax_facets:not(:visible)'); 741 if ($block.size()) { 742 $block.show(); 743 } 744 } 745 } 746 747 /* Update results if we have they and ajax facets buttons are not enabled or force mode is enabled. */ 748 let show_tip = false; 749 let buttons_exist = Drupal.ajax_facets.ajax_facets_buttons, 750 force_update_results = Drupal.ajax_facets.force_update_results; 751 if (!buttons_exist || force_update_results) { 752 $.each(response.data.views, function (key, view) { 753 $('.view-dom-id-' + view.view_dom_id).replaceWith(view.content); 754 }); 755 756 // Update the exposed form separately if needed. 757 $.each(response.data.views, function(key, view) { 758 if (view.exposed_form) { 759 let viewId = view.view_name + '-' + view.view_display_id; 760 $('#views-exposed-form-' + viewId.replace(/_/g, '-')).replaceWith(view.exposed_form); 761 } 762 }); 763 } 764 else { 765 show_tip = true; 766 } 767 768 // As some blocks could be empty in results of filtering - hide them. 769 if (response.data.hideBlocks) { 770 for (var id in response.data.hideBlocks) { 771 if (response.data.hideBlocks.hasOwnProperty(id)) { 772 // Search for drupal blocks (id="block-facetapi-xcogxrrjodjldoogufmypf7sc4rpofuk"). 773 let $block = $('#' + response.data.hideBlocks[id]); 774 if ($block.size()) { 775 $block.hide(); 776 } 777 // Search for panel panes (class="block-facetapi-xcogxrrjodjldoogufmypf7sc4rpofuk"). 778 else { 779 let $pane = $('.' + response.data.hideBlocks[id]); 780 if ($pane.size()) { 781 $pane.hide(); 782 } 783 } 784 } 785 } 786 } 787 788 if (response.data.settings.views) { 789 Drupal.settings.views = response.data.settings.views; 790 } 791 792 // We add this here so we can run custom code after content updates 793 Drupal.ajax_facets.afterContentUpdate(ajax, response); 794 795 Drupal.attachBehaviors(); 796 // Show the tooltip if need. 797 if (show_tip) { 798 Drupal.ajax_facets.showTooltip($, response.data); 799 } 800 }; 801 } 802})(jQuery);
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.