1/** 2 * Makes sure we have all the required levels on the Tribe Object 3 * 4 * @since 4.9.2 5 * 6 * @type {Object} 7 */ 8tribe.events = tribe.events || {}; 9tribe.events.views = tribe.events.views || {}; 10 11/** 12 * Configures Views Object in the Global Tribe variable 13 * 14 * @since 4.9.2 15 * 16 * @type {Object} 17 */ 18tribe.events.views.manager = {}; 19 20/** 21 * Initializes in a Strict env the code that manages the Event Views 22 * 23 * @since 4.9.2 24 * 25 * @param {Object} $ jQuery 26 * @param {Object} _ Underscore.js 27 * @param {Object} obj tribe.events.views.manager 28 * 29 * @return {void} 30 */ 31( function( $, _, obj ) { 32 'use strict'; 33 var $window = $( window ); 34 35 /** 36 * Selectors used for configuration and setup 37 * 38 * @since 4.9.2 39 * 40 * @type {Object} 41 */ 42 obj.selectors = { 43 container: '[data-js="tribe-events-view"]', 44 form: '[data-js="tribe-events-view-form"]', 45 link: '[data-js="tribe-events-view-link"]', 46 dataScript: '[data-js="tribe-events-view-data"]', 47 loader: '.tribe-events-view-loader', 48 loaderText: '.tribe-events-view-loader__text', 49 hiddenElement: '.tribe-common-a11y-hidden', 50 }; 51 52 /** 53 * Object with the details of the last location URL. 54 * 55 * @since 5.7.0 56 * 57 * @type {{origin: string, pathname: string}} 58 */ 59 obj.lastLocation = { 60 origin: '', 61 pathname: '', 62 }; 63 64 /** 65 * Flag when a popstate change is happening. 66 * 67 * @since 4.9.12 68 * 69 * @type {boolean} 70 */ 71 obj.doingPopstate = false; 72 73 /** 74 * Stores the current ajax request been handled by the manager. 75 * 76 * @since 4.9.12 77 * 78 * @type {jqXHR|null} 79 */ 80 obj.currentAjaxRequest = null; 81 82 /** 83 * Stores the last container that used PushState, which prevents fails. 84 * 85 * @todo @bordoni @paul once shortcodes start managing URLs this will need 86 * to improve to a full tracker of history. 87 * 88 * @since 4.9.12 89 * 90 * @type {jQuery} 91 */ 92 obj.$lastContainer = $(); 93 94 /** 95 * Containers on the current page that were initialized. 96 * 97 * @since 4.9.2 98 * 99 * @type {jQuery} 100 */ 101 obj.$containers = $(); 102 103 /** 104 * Clean up the container and event listeners 105 * 106 * @since 5.0.0 107 * 108 * @param {jQuery} container Which element we are going to clean up 109 * 110 * @return {void} 111 */ 112 obj.cleanup = function( container ) { 113 var $container = $( container ); 114 var $form = $container.find( obj.selectors.form ); 115 var $data = $container.find( obj.selectors.dataScript ); 116 var data = {}; 117 118 // If we have data element set it up. 119 if ( $data.length ) { 120 data = JSON.parse( $data.text().trim() ); 121 } 122 123 $container.trigger( 'beforeCleanup.tribeEvents', [ $container, data ] ); 124 125 $container.find( obj.selectors.link ).off( 'click.tribeEvents', obj.onLinkClick ); 126 127 if ( $form.length ) { 128 $form.off( 'submit.tribeEvents', obj.onSubmit ); 129 } 130 131 $container.trigger( 'afterCleanup.tribeEvents', [ $container, data ] ); 132 }; 133 134 /** 135 * Setup the container for views management 136 * 137 * @since 4.9.2 138 * 139 * @todo Requirement to setup other JS modules after hijacking Click and Submit 140 * 141 * @param {Integer} index jQuery.each index param 142 * @param {Element|jQuery} container Which element we are going to setup 143 * 144 * @return {void} 145 */ 146 obj.setup = function( index, container ) { 147 var $container = $( container ); 148 var $form = $container.find( obj.selectors.form ); 149 var $data = $container.find( obj.selectors.dataScript ); 150 var data = {}; 151 152 // If we have data element set it up. 153 if ( $data.length ) { 154 data = JSON.parse( $data.text().trim() ); 155 } 156 157 $container.trigger( 'beforeSetup.tribeEvents', [ index, $container, data ] ); 158 159 $container.find( obj.selectors.link ).on( 'click.tribeEvents', obj.onLinkClick ); 160 161 // Only catch the submit if properly setup on a form 162 if ( $form.length ) { 163 $form.on( 'submit.tribeEvents', obj.onSubmit ); 164 } 165 166 $container.trigger( 'afterSetup.tribeEvents', [ index, $container, data ] ); 167 }; 168 169 /** 170 * Given an Element determines it's view container 171 * 172 * @since 4.9.2 173 * 174 * @param {Element|jQuery} element Which element we getting the container from 175 * 176 * @return {jQuery} 177 */ 178 obj.getContainer = function( element ) { 179 var $element = $( element ); 180 181 if ( ! $element.is( obj.selectors.container ) ) { 182 return $element.parents( obj.selectors.container ).eq( 0 ); 183 } 184 185 return $element; 186 }; 187 188 /** 189 * Given an Element determines it's view container data from the script. 190 * 191 * @since 4.9.2 192 * 193 * @param {jQuery} $container Which element we getting the data from. 194 * 195 * @return {mixed} 196 */ 197 obj.getContainerData = function( $container ) { 198 var $data = $container.find( obj.selectors.dataScript ); 199 200 // Bail in case we dont find data script. 201 if ( ! $data.length ) { 202 return; 203 } 204 205 var data = JSON.parse( $data.text().trim() ); 206 207 return data; 208 }; 209 210 /** 211 * Given an container determines if it should manage URL. 212 * 213 * @since 4.9.4 214 * 215 * @param {Element|jQuery} $container Which element we are using as the container. 216 * 217 * @return {Boolean} 218 */ 219 obj.shouldManageUrl = function( $container ) { 220 var shouldManageUrl = $container.data( 'view-manage-url' ); 221 var tribeIsTruthy = /^(true|1|on|yes)$/; 222 223 // When undefined we use true as the default. 224 if ( typeof shouldManageUrl === typeof undefined ) { 225 shouldManageUrl = true; 226 } else { 227 // When not undefined we cast as string and test for valid boolean truth. 228 shouldManageUrl = tribeIsTruthy.test( String( shouldManageUrl ) ); 229 } 230 231 return shouldManageUrl; 232 }; 233 234 /** 235 * Using data passed by the Backend once we fetch a new HTML via an 236 * container action. 237 * 238 * Usage, on the AJAX request we will pass data back using a <script> 239 * formatted as a `application/json` that we will parse and apply here. 240 * 241 * @since 4.9.4 242 * 243 * @param {jQuery} $container Which element we are updating the URL from. 244 * 245 * @return {void} 246 */ 247 obj.updateUrl = function( $container ) { 248 // When handling popstate (browser back/next) it will not handle this part. 249 if ( obj.doingPopstate ) { 250 return; 251 } 252 253 // Bail when we dont manage URLs 254 if ( ! obj.shouldManageUrl( $container ) ) { 255 return; 256 } 257 258 var $data = $container.find( obj.selectors.dataScript ); 259 260 // Bail in case we dont find data script. 261 if ( ! $data.length ) { 262 return; 263 } 264 265 var data = JSON.parse( $data.text().trim() ); 266 267 // Bail when the data is not a valid object 268 if ( ! _.isObject( data ) ) { 269 return; 270 } 271 272 // Bail when URL is not present 273 if ( _.isUndefined( data.url ) ) { 274 return; 275 } 276 277 // Bail when Title is not present 278 if ( _.isUndefined( data.title ) ) { 279 return; 280 } 281 282 /** 283 * Compatibility for browsers updating title 284 */ 285 document.title = data.title; 286 287 // Push browser history 288 window.history.pushState( null, data.title, data.url ); 289 obj.lastLocation.pathname = document.location.pathname; 290 obj.lastLocation.origin = document.location.origin; 291 }; 292 293 /** 294 * Hijacks the link click and passes the URL as param for REST API 295 * 296 * @since 4.9.2 297 * 298 * @param {Event} event DOM Event related to the Click action 299 * 300 * @return {boolean} 301 */ 302 obj.onLinkClick = function( event ) { 303 var $container = obj.getContainer( this ); 304 305 $container.trigger( 'beforeOnLinkClick.tribeEvents', event ); 306 307 event.preventDefault(); 308 309 var containerData = obj.getContainerData( $container ); 310 311 var $link = $( this ); 312 var url = $link.attr( 'href' ); 313 var currentUrl = containerData.prev_url; 314 var nonce = $link.data( 'view-rest-nonce' ); 315 var shouldManageUrl = obj.shouldManageUrl( $container ); 316 var shortcodeId = $container.data( 'view-shortcode' ); 317 318 // Fetch nonce from container if the link doesn't have any 319 if ( ! nonce ) { 320 nonce = $container.data( 'view-rest-nonce' ); 321 } 322 323 var data = {
324 prev_url: encodeURI( decodeURI( currentUrl ) ), 325 url: encodeURI( decodeURI( url ) ), 326 should_manage_url: shouldManageUrl, 327 _wpnonce: nonce, 328 }; 329 330 if ( shortcodeId ) { 331 data.shortcode = shortcodeId; 332 } 333 334 obj.request( data, $container ); 335 336 $container.trigger( 'afterOnLinkClick.tribeEvents', event ); 337 338 return false; 339 }; 340 341 /** 342 * Hijacks the form submit passes all form details to the REST API 343 * 344 * @since 4.9.2 345 * 346 * @todo make sure we are only capturing fields on our Namespace 347 * 348 * @param {Event} event DOM Event related to the Click action 349 * 350 * @return {boolean} 351 */ 352 obj.onSubmit = function( event ) { 353 var $container = obj.getContainer( this ); 354 $container.trigger( 'beforeOnSubmit.tribeEvents', event ); 355 356 event.preventDefault(); 357 358 // The submit event is triggered on the form, not the container. 359 var $form = $( this ); 360 var nonce = $container.data( 'view-rest-nonce' ); 361 362 var formData = Qs.parse( $form.serialize() ); 363 364 var data = { 365 view_data: formData[ 'tribe-events-views' ], 366 _wpnonce: nonce, 367 }; 368 369 // Pass the data to the request reading it from `tribe-events-views`. 370 obj.request( data, $container ); 371 372 $container.trigger( 'afterOnSubmit.tribeEvents', event ); 373 374 return false; 375 }; 376 377 /** 378 * Catches the normal browser interactions for Next and Previous pages 379 * so that we can use the manager to load the page requested instead 380 * of just changing the URL. 381 * 382 * @since 4.9.12 383 * 384 * @param {Event} event DOM Event related to the window popstate 385 * 386 * @return {boolean} Will always return false on this one. 387 */ 388 obj.onPopState = function( event ) { 389 var target = event.originalEvent.target; 390 var url = target.location.href; 391 var $container = obj.getLastContainer(); 392 393 // We are at the same URL + path as before so not really a change on the 394 // actual URL happen, it might be just a hash change which shouldn't 395 // trigger and XHR request. 396 // eslint-disable-next-line max-len 397 if ( obj.lastLocation.origin === target.location.origin && obj.lastLocation.pathname === target.location.pathname ) { 398 return false; 399 } 400 401 obj.lastLocation.pathname = document.location.pathname; 402 obj.lastLocation.origin = document.location.origin; 403 404 if ( ! $container ) { 405 return false; 406 } 407 408 if ( obj.currentAjaxRequest ) { 409 obj.currentAjaxRequest.abort(); 410 } 411 412 // Flag that we are doing popstate globally. 413 obj.doingPopstate = true; 414 415 $container.trigger( 'beforePopState.tribeEvents', event ); 416 417 var nonce = $container.data( 'view-rest-nonce' ); 418 419 var data = { 420 url: url, 421 _wpnonce: nonce, 422 }; 423 424 obj.request( data, $container ); 425 426 return false; 427 }; 428 429 /** 430 * Sets up the request data for AJAX request. 431 * 432 * @since 5.2.0 433 * 434 * @param {object} data Data object to modify and setup. 435 * @param {Element|jQuery} $container Which container we are dealing with. 436 * 437 * @return {void} 438 */ 439 obj.setupRequestData = function( data, $container ) { 440 var shouldManageUrl = obj.shouldManageUrl( $container ); 441 var containerData = obj.getContainerData( $container ); 442 443 if ( ! data.url ) { 444 data.url = containerData.url; 445 } 446 447 if ( ! data.prev_url ) { 448 data.prev_url = containerData.prev_url; 449 } 450 451 data.should_manage_url = shouldManageUrl; 452 453 // Allow other values to be passed to request from container data. 454 var requestData = $container.data( 'tribeRequestData' ); 455 456 if ( ! $.isPlainObject( requestData ) ) { 457 return data; 458 } 459 460 return $.extend( requestData, data ); 461 }; 462 463 /** 464 * Performs an AJAX request given the data for the REST API and which container 465 * we are going to pass the answer to. 466 * 467 * @since 4.9.2 468 * 469 * @param {object} data DOM Event related to the Click action 470 * @param {Element|jQuery} $container Which container we are dealing with 471 * 472 * @return {void} 473 */ 474 obj.request = function( data, $container ) { 475 $container.trigger( 'beforeRequest.tribeEvents', [ data, $container ] ); 476 477 var settings = obj.getAjaxSettings( $container ); 478 479 // Pass the data setup to the $.ajax settings 480 settings.data = obj.setupRequestData( data, $container ); 481 482 obj.currentAjaxRequest = $.ajax( settings ); 483 484 $container.trigger( 'afterRequest.tribeEvents', [ data, $container ] ); 485 }; 486 487 /** 488 * Gets the jQuery.ajax() settings provided a views container 489 * 490 * @since 4.9.2 491 * 492 * @param {Element|jQuery} $container Which container we are dealing with 493 * 494 * @return {Object} 495 */ 496 obj.getAjaxSettings = function( $container ) { 497 var ajaxSettings = { 498 url: $container.data( 'view-rest-url' ), 499 accepts: 'html', 500 dataType: 'html', 501 method: $container.data( 'view-rest-method' ) || 'POST', 502 'async': true, // async is keyword 503 beforeSend: obj.ajaxBeforeSend, 504 complete: obj.ajaxComplete, 505 success: obj.ajaxSuccess, 506 error: obj.ajaxError, 507 context: $container, 508 }; 509 510 return ajaxSettings; 511 }; 512 513 /** 514 * Triggered on jQuery.ajax() beforeSend action, which we hook into to
515 * setup a Loading Lock, as well as trigger a before and after hook, so 516 * third-party developers can always extend all requests 517 * 518 * Context with the View container used to fire this AJAX call 519 * 520 * @since 4.9.2 521 * 522 * @param {jqXHR} jqXHR Request object 523 * @param {Object} settings Settings that this request will be made with 524 * 525 * @return {void} 526 */ 527 obj.ajaxBeforeSend = function( jqXHR, settings ) { 528 var $container = this; 529 var $loader = $container.find( obj.selectors.loader ); 530 531 $container.trigger( 'beforeAjaxBeforeSend.tribeEvents', [ jqXHR, settings ] ); 532 533 if ( $loader.length ) { 534 $loader.removeClass( obj.selectors.hiddenElement.className() ); 535 var $loaderText = $loader.find( obj.selectors.loaderText ); 536 $loaderText.text( $loaderText.text() ); 537 } 538 $container.attr( 'aria-busy', 'true' ); 539 540 $container.trigger( 'afterAjaxBeforeSend.tribeEvents', [ jqXHR, settings ] ); 541 }; 542 543 /** 544 * Triggered on jQuery.ajax() complete action, which we hook into to 545 * removal of Loading Lock, as well as trigger a before and after hook, 546 * so third-party developers can always extend all requests 547 * 548 * Context with the View container used to fire this AJAX call 549 * 550 * @since 4.9.2 551 * 552 * @param {jqXHR} qXHR Request object 553 * @param {String} textStatus Status for the request 554 * 555 * @return {void} 556 */ 557 obj.ajaxComplete = function( jqXHR, textStatus ) { 558 var $container = this; 559 var $loader = $container.find( obj.selectors.loader ); 560 561 $container.trigger( 'beforeAjaxComplete.tribeEvents', [ jqXHR, textStatus ] ); 562 563 if ( $loader.length ) { 564 $loader.addClass( obj.selectors.hiddenElement.className() ); 565 } 566 567 $container.trigger( 'afterAjaxComplete.tribeEvents', [ jqXHR, textStatus ] ); 568 569 // Flag that we are done with popstate if that was the case. 570 if ( obj.doingPopstate ) { 571 obj.doingPopstate = false; 572 } 573 574 // Reset the current ajax request on the manager object. 575 obj.currentAjaxRequest = null; 576 }; 577 578 /** 579 * Triggered on jQuery.ajax() success action, which we hook into to 580 * replace the contents of the container which is the base behavior 581 * for the views manager, as well as trigger a before and after hook, 582 * so third-party developers can always extend all requests 583 * 584 * Context with the View container used to fire this AJAX call 585 * 586 * @since 4.9.2 587 * 588 * @param {String} html HTML sent from the REST API 589 * @param {String} textStatus Status for the request 590 * @param {jqXHR} qXHR Request object 591 * 592 * @return {void} 593 */ 594 obj.ajaxSuccess = function( data, textStatus, jqXHR ) { 595 var $container = this; 596 597 $container.trigger( 'beforeAjaxSuccess.tribeEvents', [ data, textStatus, jqXHR ] ); 598 599 var $html = $( data ); 600 601 // Clean up the container and event listeners 602 obj.cleanup( $container ); 603 604 // Replace the current container with the new Data. 605 $container.replaceWith( $html ); 606 $container = $html; 607 608 // Setup the container with the data received. 609 obj.setup( 0, $container ); 610 611 // Update the global set of containers with all of the manager object. 612 obj.selectContainers(); 613 614 // Trigger the browser pushState 615 obj.updateUrl( $container ); 616 617 $container.trigger( 'afterAjaxSuccess.tribeEvents', [ data, textStatus, jqXHR ] ); 618 619 if ( obj.shouldManageUrl( $container ) ) { 620 obj.$lastContainer = $container; 621 } 622 }; 623 624 /** 625 * Triggered on jQuery.ajax() error action, which we hook into to 626 * display error and keep the user on the same "page", as well as 627 * trigger a before and after hook, so third-party developers can 628 * always extend all requests 629 * 630 * Context with the View container used to fire this AJAX call 631 * 632 * @since 4.9.2 633 * 634 * @param {jqXHR} jqXHR Request object 635 * @param {Object} settings Settings that this request was made with 636 * 637 * @return {void} 638 */ 639 obj.ajaxError = function( jqXHR, settings ) { 640 var $container = this; 641 642 $container.trigger( 'beforeAjaxError.tribeEvents', [ jqXHR, settings ] ); 643 644 /** 645 * @todo we need to handle errors here 646 */ 647 648 $container.trigger( 'afterAjaxError.tribeEvents', [ jqXHR, settings ] ); 649 }; 650 651 /** 652 * Saves all the containers in the page into the object. 653 * 654 * @since 4.9.12 655 * 656 * @return {jQuery} Which containers were selected. 657 */ 658 obj.selectContainers = function() { 659 obj.$containers = $( obj.selectors.container ); 660 return obj.$containers; 661 }; 662 663 /** 664 * Selects the last container to change the URL. 665 * 666 * @since 4.9.12 667 * 668 * @return {jQuery} Last container element. 669 */ 670 obj.getLastContainer = function() { 671 /** 672 * @todo @bordoni @paul improve this when shortcodes are also managing the URL. 673 */ 674 if ( ! obj.$lastContainer.length ) { 675 obj.$lastContainer = obj.$containers.filter( '[data-view-manage-url="1"]' ).eq( 0 ); 676 } 677 678 return obj.$lastContainer; 679 }; 680 681 /** 682 * Handles the initialization of the manager when Document is ready. 683 * 684 * @since 4.9.2 685 * 686 * @return {void} 687 */ 688 obj.ready = function() { 689 obj.selectContainers().each( obj.setup ); 690 obj.lastLocation = { 691 origin: document.location.origin, 692 pathname: document.location.pathname, 693 }; 694 }; 695 696 // Configure on document ready. 697 $( obj.ready ); 698 699 // Attaches the popstate method to the window object. 700 $window.on( 'popstate', obj.onPopState ); 701} )( jQuery, window.underscore || window._, tribe.events.views.manager );
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.