1var FXLM = ( function( FXLM ) { 2 3 4 // making instances globally accessible in case other plugins/themes need to manipulate 5 FXLM.instances = [] 6 7 8 window.addEventListener( 'load', () => { 9 const containers = document.querySelectorAll('.js-load-more-block') 10 11 for( const container of containers ) { 12 const postType = container.dataset.loadMorePostType, 13 posts = container.querySelector('.js-load-more-posts'), 14 widget = container.querySelector('.js-load-more') 15 16 if( null !== widget && null !== posts ) { 17 const instance = new FxLoadMore( container, widget, posts, postType ) 18 19 FXLM.instances.push( instance ) 20 } 21 } 22 }) 23 24 25 26 class FxLoadMore { 27 28 constructor( elContainer, elWidget, elPosts, postType = 'post' ) { 29 30 // elements 31 this.container = elContainer 32 this.widget = elWidget 33 this.posts = elPosts 34 this.counter = this.widget.querySelector('.js-load-more-counter') 35 this.progress = this.widget.querySelector('.js-load-more-progress') 36 this.btn = this.widget.querySelector('.js-load-more-btn') 37 38 // states 39 this.isFetching = false 40 this.isShowingAll = false 41 42 // data 43 this.postType = postType 44 this.postsPerPage = parseInt( FXLM.posts_per_page ) 45 this.postTaxonomy = null 46 this.postTermId = null 47 48 // check container for data attribute for total posts 49 if( undefined !== this.container.dataset.loadMoreTotal ) { 50 this.totalPostCount = this.container.dataset.loadMoreTotal 51 52 // otherwise, check for value passed through wp_localize_script 53 } else { 54 this.totalPostCount = parseInt( FXLM.post_count ) 55 } 56 57 // check container for data attribute for current page 58 if( undefined !== this.container.dataset.loadMoreCurrentPage ) { 59 this.currentPage = this.container.dataset.loadMoreCurrentPage 60 } else { 61 this.currentPage = 1 62 } 63 64 // check if for taxonomy 65 if( undefined !== FXLM.post_taxonomy && undefined !== FXLM.post_term_id ) { 66 this.postTaxonomy = FXLM.post_taxonomy 67 this.postTermId = FXLM.post_term_id 68 } 69 70 // check for search query 71 if( undefined !== this.container.dataset.loadMoreSearchQuery ) { 72 this.searchQuery = this.container.dataset.loadMoreSearchQuery 73 } 74 75 this.currentPostCount = ( this.totalPostCount < this.postsPerPage ) ? this.totalPostCount : this.postsPerPage 76 77 this.init() 78 } 79 80 81 init() { 82 this.progress.max = this.totalPostCount 83 84 this.updateWidget() 85 this.bind() 86 } 87 88 89 bind() { 90 this.btn.addEventListener( 'click', this.handleBtnClick.bind( this ) ) 91 } 92 93 94 updateWidget() { 95 this.progress.value = this.currentPostCount 96 this.counter.innerText = `Viewing ${this.currentPostCount} of ${this.totalPostCount}` 97 98 // have we loaded all posts? 99 if( this.currentPostCount >= this.totalPostCount ) { 100 this.btn.remove() 101 102 // prevents additional Ajax requests 103 this.isShowingAll = true 104 } 105 } 106 107 108 async handleBtnClick() { 109 const self = this 110 111 // avoid duplicate clicks or clicks when all posts are showing 112 if( self.isFetching || self.isShowingAll ) { 113 return 114 } 115 116 // indicate loading state 117 self.setFetchStatus( true ) 118 119 // generate placeholders to avoid CLS 120 const placeholders = self.createPlaceholders( self.getPlaceholderCount() ) 121 122 // params for endpoint 123 const params = new URLSearchParams() 124 125 params.append( 'page', ++self.currentPage ) 126 params.append( 'posts_per_page', self.postsPerPage ) 127 params.append( 'post_type', self.postType ) 128 129 // if archive page, include taxonomy 130 if( null !== self.postTaxonomy && null !== self.postTermId ) { 131 params.append( 'taxonomy', self.postTaxonomy ) 132 params.append( 'term_id', self.postTermId ) 133 } 134 135 // if search page, include search term 136 if( undefined !== self.searchQuery ) { 137 params.append( 'search', self.searchQuery ) 138 } 139 140 // fetch posts from API endpoint 141 const { post_count, posts } = await self.fetchPosts( params ) 142 143 // remove loading state 144 self.setFetchStatus( false ) 145 146 // update object data 147 self.currentPostCount += post_count 148 149 // replace placeholder blocks with actual posts 150 self.replacePlaceholdersWithPosts( placeholders, posts ) 151 152 // update progress bar 153 self.updateWidget() 154 } 155 156 157 setFetchStatus( isFetching = false ) { 158 this.isFetching = isFetching 159 160 // show/hide loading animations, etc 161 this.btn.classList.toggle( 'is-loading', isFetching ) 162 } 163 164 165 getPlaceholderCount() { 166 let count = this.postsPerPage, 167 remaining = this.totalPostCount - this.currentPostCount 168 169 // if less than full page of posts, use that instead 170 if( remaining < count ) { 171 count = remaining 172 } 173 174 return count 175 } 176 177 178 async fetchPosts( params ) { 179 const response = await fetch( `${FXLM.rest_url}/get-posts?${params.toString()}` ) 180 181 return await response.json() 182 } 183 184 185 createPlaceholders( placeholderCount ) { 186 const self = this, 187 placeholders = [], 188 firstPost = self.posts.firstElementChild, 189 elHeight = firstPost.offsetHeight 190 191 for( let i = 0; i < placeholderCount; i++ ) { 192 const placeholder = firstPost.cloneNode(), // clone only container 193 innerBlock = document.createElement('div') 194 195 placeholder.classList.add( 'placeholder-block', 'is-loading' ) 196 placeholder.style.height = `${elHeight}px` 197 198 // add inner block for easier styling (in case using padding with outside block) 199 innerBlock.classList.add( 'placeholder-block__inner' ) 200 placeholder.appendChild( innerBlock ) 201 202 self.posts.appendChild( placeholder ) 203 204 placeholders.push( placeholder ) 205 } 206 207 return placeholders 208 } 209 210 211 replacePlaceholdersWithPosts( placeholders, posts ) { 212 const self = this 213 214 // goal is to replace each placeholder block with a post block 215 for( const placeholder of placeholders ) { 216 217 // check if a post exists 218 if( posts.length ) { 219 const post = posts.shift() 220 221 // convert string to HTML 222 const fragment = document.createRange().createContextualFragment( post ) 223 224 placeholder.replaceWith( fragment ) 225 226 // if no posts exist, delete placeholder block 227 } else { 228 placeholder.remove() 229 } 230 } 231 232 // do we still have posts left over? 233 if( posts.length ) { 234 235 // add to end of posts listing 236 for( const post of posts ) { 237 self.posts.insertAdjacentHTML( 'beforeend', post ) 238 } 239 } 240 } 241 } 242 243 244 245 return FXLM 246 247}) ( FXLM || {} )
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.