1/** 2 * UpSolution Element: Post List 3 */ 4;( function( $ ) { 5 "use strict"; 6 7 const DELETE_FILTER = null; 8 const PAGINATION_PATTERN = /\/page\/?([0-9]{1,})\/?$/; 9 10 /** 11 * @type {String} The original URL to return after closing the popup. 12 */ 13 var _originalURL; 14 15 /** 16 * @param {Node} container. 17 */ 18 function usPostList( container ) { 19 const self = this; 20 21 // Private "variables" 22 self.data = { 23 paged: 1, 24 max_num_pages: 1, 25 pagination: 'none', 26 ajaxUrl: $us.ajaxUrl, 27 ajaxData: { 28 us_ajax_list_pagination: 1, 29 }, 30 facetedFilter: {}, 31 }; 32 self.xhr; 33 34 // Elements 35 self.$container = $( container ); 36 self.$list = $( '.w-grid-list', container ); 37 self.$loadmore = $( '.g-loadmore', container ); 38 self.$pagination = $( 'nav.pagination', container ); 39 self.$none = self.$container.next( '.w-grid-none' ); 40 41 // Get element settings 42 const $elmSettings = $( '.w-grid-list-json:first', container ); 43 if ( $elmSettings.is( '[onclick]' ) ) { 44 $.extend( self.data, $elmSettings[0].onclick() || {} ); 45 } 46 $elmSettings.remove(); 47 48 self.paginationType = $ush.toString( self.data.pagination ); 49 self.hideIsEmpty = self.$container.closest( '.hide_if_inner_list_is_empty' ).length > 0; 50 51 // Bondable events 52 self._events = { 53 addNextPage: self._addNextPage.bind( self ), 54 closePostInPopup: self.closePostInPopup.bind( self ), 55 closePostInPopupByEsc: self.closePostInPopupByEsc.bind( self ), 56 loadPostInPopup: self.loadPostInPopup.bind( self ), 57 navInPopup: self.navInPopup.bind( self ), 58 openPostInPopup: self.openPostInPopup.bind( self ), 59 60 usListOrder: self._usListOrder.bind( self ), 61 usListSearch: self._usListSearch.bind( self ), 62 usListFilter: self._usListFilter.bind( self ), 63 64 usbReloadIsotopeLayout: self.usbReloadIsotopeLayout.bind( self ), 65 }; 66 67 // Load posts on button click or page scroll; 68 if ( self.paginationType === 'load_on_btn' ) { 69 self.$loadmore.on( 'mousedown', 'button', self._events.addNextPage ); 70 71 } else if ( self.paginationType === 'load_on_scroll' ) { 72 $us.waypoints.add( self.$loadmore, /* offset */'-70%', self._events.addNextPage ); 73 } 74 75 // Events 76 self.$container 77 .add( self.$none ) 78 .on( 'usListSearch', self._events.usListSearch ) 79 .on( 'usListOrder', self._events.usListOrder ) 80 .on( 'usListFilter', self._events.usListFilter ); 81 82 // Open posts in popup 83 if ( self.$container.hasClass( 'open_items_in_popup' ) ) { 84 85 // Elements 86 self.$popup = $( '.l-popup', container ); 87 self.$popupBox = $( '.l-popup-box', self.$popup ); 88 self.$popupPreloader = $( '.g-preloader', self.$popup ); 89 self.$popupFrame = $( '.l-popup-box-content-frame', self.$popup ); 90 self.$popupToPrev = $( '.l-popup-arrow.to_prev', self.$popup ); 91 self.$popupToNext = $( '.l-popup-arrow.to_next', self.$popup ); 92 93 $us.$body.append( self.$popup ); 94 95 // Events 96 self.$list 97 .on( 'click', '.w-grid-item:not(.custom-link) .w-grid-item-anchor', self._events.openPostInPopup ); 98 self.$popupFrame 99 .on( 'load', self._events.loadPostInPopup ); 100 self.$popup 101 .on( 'click', '.l-popup-arrow', self._events.navInPopup ) 102 .on( 'click', '.l-popup-closer, .l-popup-box', self._events.closePostInPopup ); 103 } 104 105 // Initialize Masonry. 106 // @see https://isotope.metafizzy.co 107 if ( self.$container.hasClass( 'type_masonry' ) ) { 108 self.$list.imagesLoaded( () => { 109 110 const isotopeOptions = { 111 itemSelector: '.w-grid-item', 112 layoutMode: ( self.$container.hasClass( 'isotope_fit_rows' ) ) ? 'fitRows' : 'masonry', 113 isOriginLeft: ! $( '.l-body' ).hasClass( 'rtl' ), 114 transitionDuration: 0 115 }; 116 117 var columnWidth; 118 if ( $( '.size_1x1', self.$list ).length > 0 ) { 119 columnWidth = '.size_1x1'; 120 } else if ( $( '.size_1x2', self.$list ).length > 0 ) { 121 columnWidth = '.size_1x2'; 122 } else if ( $( '.size_2x1', self.$list ).length >0 ) { 123 columnWidth = '.size_2x1'; 124 } else if ( $( '.size_2x2', self.$list ).length > 0 ) { 125 columnWidth = '.size_2x2'; 126 } 127 if ( columnWidth ) { 128 columnWidth = columnWidth || '.w-grid-item'; 129 isotopeOptions.masonry = { columnWidth: columnWidth }; 130 } 131 132 // Run CSS animations locally after rendering elements in Isotope. 133 self.$list.on( 'layoutComplete', () => { 134 if ( _window.USAnimate ) { 135 $( '.w-grid-item.off_autostart', self.$list ).removeClass( 'off_autostart' ); 136 new USAnimate( self.$list ); 137 }
138 // Trigger scroll event to check positions for `$us.waypoints`. 139 $us.$window.trigger( 'scroll.waypoints' ); 140 } ); 141 142 self.$list.isotope( isotopeOptions ); 143 144 $us.$canvas.on( 'contentChange', () => { 145 self.$list.imagesLoaded( () => { 146 self.$list.isotope( 'layout' ); 147 } ); 148 } ); 149 150 } ); 151 152 self.$container.on( 'usbReloadIsotopeLayout', self._events.usbReloadIsotopeLayout ); 153 } 154 }; 155 156 // Post List API 157 $.extend( usPostList.prototype, { 158 159 /** 160 * Sets the search string from "List Search". 161 * 162 * @event handler 163 * @param {Event} e The Event interface represents an event which takes place in the DOM. 164 * @param {String} name 165 * @param {String} value The search text. 166 */ 167 _usListSearch: function( e, name, value ) { 168 this.applyFilter( name, value ); 169 }, 170 171 /** 172 * Sets orderby from "List Order". 173 * 174 * @event handler 175 * @param {Event} e The Event interface represents an event which takes place in the DOM. 176 * @param {String} name 177 * @param {String} value The search text. 178 */ 179 _usListOrder: function( e, name, value ) { 180 this.applyFilter( name, value ); 181 }, 182 183 /** 184 * Sets values from "List Filter". 185 * 186 * @event handler 187 * @param {Event} e The Event interface represents an event which takes place in the DOM. 188 * @param {{}} values 189 */ 190 _usListFilter: function( e, values ) { 191 const self = this; 192 $.each( values, self.applyFilter.bind( self ) ); 193 }, 194 195 /** 196 * Adds next page. 197 * 198 * @event handler 199 */ 200 _addNextPage: function() { 201 const self = this; 202 if ( $ush.isUndefined( self.xhr ) && ! self.$none.is( ':visible' ) ) { 203 self.addItems(); 204 } 205 }, 206 207 /** 208 * Apply param to "Post/Product List". 209 * 210 * @param {String} name 211 * @param {String} value 212 */ 213 applyFilter: function( name, value ) { 214 const self = this; 215 if ( $ush.toString( value ) == '{}' ) { 216 value = DELETE_FILTER; 217 } 218 219 // only save 220 if ( name === 'list_filters' ) { 221 self.data.ajaxData[ name ] = value; 222 return; 223 } 224 225 // Reset pagination 226 const pathname = location.pathname; 227 if ( PAGINATION_PATTERN.test( pathname ) ) { 228 history.pushState( {}, '', location.href.replace( pathname, pathname.replace( PAGINATION_PATTERN, '' ) + '/' ) ); 229 } 230 self.data.paged = 0; 231 232 if ( self.$container.hasClass( 'for_current_wp_query' ) ) { 233 self.data.ajaxUrl = $ush 234 .urlManager( self.data.ajaxUrl ) 235 .set( name, value ) 236 .toString(); 237 238 } else if ( value === DELETE_FILTER ) { 239 delete self.data.ajaxData[ name ]; 240 241 } else { 242 self.data.ajaxData[ name ] = value; 243 } 244 245 if ( ! $ush.isUndefined( self.xhr ) ) { 246 self.xhr.abort(); 247 } 248 self.addItems( /* apply filter */true ); 249 }, 250 251 /** 252 * Scrolls to the beginning of the list. 253 */ 254 scrollToList: function() { 255 const self = this; 256 257 if ( self.data.paged > 1 ) { 258 return; 259 } 260 261 const offsetTop = $ush.parseInt( self.$container.offset().top ); 262 if ( ! offsetTop ) { 263 return; 264 } 265 266 const scrollTop = $us.$window.scrollTop(); 267 268 if ( 269 ! $ush.isNodeInViewport( self.$container[0] ) 270 || offsetTop >= ( scrollTop + window.innerHeight ) 271 || scrollTop >= offsetTop 272 ) { 273 $us.$htmlBody 274 .stop( true, false ) 275 .animate( { scrollTop: ( offsetTop - $us.header.getInitHeight() ) }, 500 ); 276 } 277 }, 278 279 /** 280 * Adds items to element. 281 * 282 * @param {Boolean} applyFilter 283 */ 284 addItems: function( applyFilter ) { 285 const self = this; 286 287 self.data.paged += 1; 288 if ( ! applyFilter && self.data.paged > self.data.max_num_pages ) { 289 return; 290 } 291 292 if ( applyFilter ) { 293 self.$container.addClass( 'filtering' ); 294 295 // Show spinner for filtering action only if set in options 296 if ( self.$container.hasClass( 'preload_style_spinner' ) ) { 297 self.$loadmore.removeClass( 'hidden' ).addClass( 'loading' ); 298 } 299 300 // Always show spinner for pagination action 301 } else { 302 self.$loadmore.removeClass( 'hidden' ).addClass( 'loading' ); 303 } 304 305 if ( ! self.hideIsEmpty ) { 306 self.$container.removeClass( 'hidden' ); 307 } 308 self.$pagination.addClass( 'hidden' ); 309 310 // Get request link and data 311 var ajaxUrl = $ush.toString( self.data.ajaxUrl ), 312 ajaxData = $ush.clone( self.data.ajaxData ), 313 numPage = $ush.rawurlencode( '{num_page}' ); 314
315 if ( ajaxUrl.includes( numPage ) ) { 316 ajaxUrl = ajaxUrl.replace( numPage, self.data.paged ); 317 318 } else if ( ajaxData.template_vars ) { 319 ajaxData.template_vars = JSON.stringify( ajaxData.template_vars ); // convert for `us_get_HTTP_POST_json()` 320 ajaxData.paged = self.data.paged; 321 } 322 323 self.xhr = $.ajax( { 324 type: 'post', 325 url: ajaxUrl, 326 dataType: 'html', 327 cache: false, 328 data: ajaxData, 329 success: function( html ) { 330 331 // Remove previous items when filtered 332 if ( applyFilter ) { 333 self.$list.html(''); 334 self.$none.addClass( 'hidden' ); 335 } 336 337 // Reload element settings 338 var $listJson = $( '.w-grid-list-json:first', html ); 339 if ( $listJson.is( '[onclick]' ) ) { 340 $.extend( true, self.data, $listJson[0].onclick() || {} ); 341 } 342 343 var $items = $( '.w-grid-list:first > *', html ); 344 345 // List items loaded 346 $ush.timeout( () => { 347 $us.$document.trigger( 'usPostList.itemsLoaded', [ $items, applyFilter ] ); 348 }, 50 ); 349 350 if ( self.hideIsEmpty ) { 351 self.$container.toggleClass( 'hidden', $items.length === 0 ); 352 } 353 354 // Case when there are no results 355 if ( ! $items.length ) { 356 if ( ! self.$none.length ) { 357 self.$none = $( '.w-grid-none:first', html ); 358 if ( ! self.$none.length ) { 359 self.$none = $( html ).filter( '.w-grid-none:first' ); 360 } 361 self.$container.after( self.$none ); 362 } 363 self.$container.removeClass( 'filtering' ); 364 self.$loadmore.addClass( 'hidden' ); 365 self.$pagination.addClass( 'hidden' ); 366 self.$none.removeClass( 'hidden' ); 367 return 368 } 369 370 // Output of results 371 if ( self.$container.hasClass( 'type_masonry' ) ) { 372 self.$list 373 .isotope( 'insert', $items ) 374 .isotope( 'reloadItems' ); 375 } else { 376 self.$list.append( $items ); 377 } 378 379 // Init animation handler for new items 380 if ( window.USAnimate && self.$container.hasClass( 'with_css_animation' ) ) { 381 new USAnimate( self.$list ); 382 $us.$window.trigger( 'scroll.waypoints' ); 383 } 384 385 // Case with numbered pagination 386 if ( self.paginationType == 'numbered' ) { 387 const $pagination = $( 'nav.pagination', html ); 388 if ( $pagination.length && ! self.$pagination.length ) { 389 self.$list.after( $pagination.prop( 'outerHTML' ) ); 390 self.$pagination = self.$list.next( 'nav.pagination' ); 391 } 392 if ( self.$pagination.length && $pagination.length ) { 393 self.$pagination.html( $pagination.html() ).removeClass( 'hidden' ); 394 395 } else { 396 self.$pagination.addClass( 'hidden' ); 397 } 398 } 399 400 // Case when the last page is loaded 401 if ( self.data.paged >= self.data.max_num_pages ) { 402 self.$loadmore.addClass( 'hidden' ); 403 self.$none.addClass( 'hidden' ); 404 405 } else { 406 self.$loadmore.removeClass( 'hidden' ); 407 } 408 409 // Adds point to load the next page 410 if ( self.paginationType == 'load_on_scroll' ) {
411 $us.waypoints.add( self.$loadmore, /* offset */'-70%', self._events.addNextPage ); 412 } 413 414 $us.$canvas.trigger( 'contentChange' ); 415 }, 416 complete: function() { 417 self.$container.removeClass( 'filtering' ); 418 self.$loadmore.removeClass( 'loading' ); 419 if ( self.paginationType == 'load_on_scroll' ) { 420 self.$loadmore.addClass( 'hidden' ); 421 } 422 delete self.xhr; 423 424 // Scroll to top of list 425 self.scrollToList(); 426 } 427 } ); 428 }, 429 430 /** 431 * Reload layout in the Live Builder context. 432 * 433 * @event handler 434 */ 435 usbReloadIsotopeLayout: function() { 436 const self = this; 437 if ( self.$container.hasClass( 'with_isotope' ) ) { 438 self.$list.isotope( 'layout' ); 439 } 440 }, 441 } ); 442 443 // Functionality for popup window 444 $.extend( usPostList.prototype, { 445 446 /** 447 * Open post in popup. 448 * 449 * @event handler 450 * @param {Event} e The Event interface represents an event which takes place in the DOM. 451 */ 452 openPostInPopup: function( e ) { 453 const self = this; 454 455 // If scripts are disabled on a given screen width, then exit 456 if ( $us.$window.width() <= $us.canvasOptions.disableEffectsWidth ) { 457 return; 458 } 459 460 e.stopPropagation(); 461 e.preventDefault(); 462 463 // Remember original page URL 464 _originalURL = location.href; 465 466 // Set post by index in the list 467 self.setPostInPopup( $( e.target ).closest( '.w-grid-item' ).index() ); 468 469 // Show popup 470 self.$popup.addClass( 'active' ); 471 $us.$document.trigger( 'usPopupOpened' ); 472 $ush.timeout( () => { 473 self.$popupBox.addClass( 'show' ); 474 }, 25 ); 475 }, 476 477 /** 478 * Load post in popup. 479 * 480 * @event handler 481 */ 482 loadPostInPopup: function() { 483 const self = this; 484 self.$container.on( 'keyup', self._events.closePostInPopupByEsc ); 485 $( 'body', self.$popupFrame.contents() ).one( 'keyup.usCloseLightbox', self._events.closePostInPopupByEsc ); 486 }, 487 488 /** 489 * Navigation in the post popup. 490 * 491 * @event handler 492 * @param {Event} e The Event interface represents an event which takes place in the DOM. 493 */ 494 navInPopup: function( e ) { 495 this.setPostInPopup( $( e.target ).data( 'index' ) ); 496 }, 497 498 /** 499 * Sets post by index in the list. 500 * 501 * @param {String} url The new value. 502 */ 503 setPostInPopup: function( index ) { 504 const self = this; 505 506 // Get current node and url 507 var $node = $( '> *:eq(' + $ush.parseInt( index ) + ')', self.$list ), 508 url = $ush.toString( $( '[href]:first', $node ).attr( 'href' ) ); 509 510 // If there is no href, then exit 511 if ( ! url ) { 512 console.error( 'No url to loaded post' ); 513 return; 514 } 515 516 // Gen prev / next node 517 var $prev = $node.prev( ':not(.custom-link)' ), 518 $next = $node.next( ':not(.custom-link)' ); 519 520 // Pagination controls switch 521 self.$popupToPrev 522 .data( 'index', $prev.index() ) 523 .attr( 'title', $( '.post_title', $prev ).text() ) 524 .toggleClass( 'hidden', ! $prev.length ); 525 self.$popupToNext 526 .data( 'index', $next.index() ) 527 .attr( 'title', $( '.post_title', $next ).text() ) 528 .toggleClass( 'hidden', ! $next.length ); 529 530 self.$popupPreloader.show(); 531 self.$popupBox.off( 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd' ); 532 self.$popupFrame.attr( 'src', url + ( url.indexOf( '?' ) > -1 ? '&' : '?' ) + 'us_iframe=1' ); 533 534 history.replaceState( null, null, url ); 535 }, 536 537 /** 538 * Close post in popup. 539 * 540 * @event handler 541 */ 542 closePostInPopup: function() { 543 const self = this; 544 545 self.$popup.addClass( 'closing' ); 546 self.$popupFrame.attr( 'src', 'about:blank' ); 547 self.$popupBox 548 .removeClass( 'show' ) 549 .one( 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', $ush.debounce( () => { 550 self.$popup.removeClass( 'active closing' ); 551 self.$popupToPrev.addClass( 'hidden' ); 552 self.$popupToNext.addClass( 'hidden' ); 553 self.$popupPreloader.show(); 554 $us.$document.trigger( 'usPopupClosed' ); 555 }, 1 ) ); 556 557 // Restore original URL 558 if ( _originalURL ) { 559 history.replaceState( null, null, _originalURL ); 560 } 561 }, 562 563 /** 564 * Close post in popup by escape. 565 * 566 * @event handler 567 * @param {Event} e The Event interface represents an event which takes place in the DOM 568 */ 569 closePostInPopupByEsc: function( e ) { 570 const self = this; 571 if ( e.keyCode === $ush.ESC_KEYCODE && self.$popup.hasClass( 'active' ) ) { 572 self.closePostInPopup(); 573 } 574 }, 575 576 } ); 577 578 $.fn.usPostList = function() { 579 return this.each( function() { 580 $( this ).data( 'usPostList', new usPostList( this ) ); 581 } ); 582 }; 583 584 $( () =>
584 $( '.w-grid.us_post_list, .w-grid.us_product_list' ).usPostList() ); 585 586} )( 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.