1 // Function to scroll to the div element with the query ID 2 function scrollToQueryId( queryId ) { 3 const targetElement = document.getElementById( `uagb-block-queryid-${queryId}` ); 4 5 if ( targetElement ) { 6 const rect = targetElement.getBoundingClientRect(); 7 const adminBar = document.querySelector( '#wpadminbar' ); 8 const adminBarOffSetHeight = adminBar?.offsetHeight || 0; 9 const scrollTop = window?.pageYOffset || document?.documentElement?.scrollTop; 10 const targetOffset = ( rect?.top + scrollTop ) - adminBarOffSetHeight; 11 12 window.scrollTo( { 13 top: targetOffset, 14 behavior: 'smooth' 15 } ); 16 } 17 } 18 19 /** 20 * Function to find the ancestor with the given class name. 21 * 22 * @param {Element} element The element. 23 * @param {string} className The class name. 24 * @return {Element} The element. 25 * @since 1.2.0 26 */ 27 function findAncestorWithClass( element, className ) { 28 while ( element && ! element.classList.contains( className ) ) { 29 element = element.parentNode; 30 } 31 return element; 32 } 33 34 document.addEventListener( 'DOMContentLoaded', function () { 35 // Debounce function to limit the rate of execution 36 function debounce( func, wait ) { 37 let timeout; 38 39 return function executedFunction( ...args ) { 40 const context = this; 41 const later = () => { 42 timeout = null; 43 func.apply( context, args ); 44 }; 45 46 clearTimeout( timeout ); 47 timeout = setTimeout( later, wait ); 48 }; 49 }; 50 51 // Add functions to store and get active filters 52 function storeActiveFilter( loopBuilder, filterData, filterType ) { 53 if ( loopBuilder && filterData ) { 54 loopBuilder.setAttribute( `data-active-filter-${filterType}`, filterData ); 55 } 56 } 57 58 function getActiveFilter( loopBuilder, filterType ) { 59 return loopBuilder?.getAttribute( `data-active-filter-${filterType}` ) || null; 60 } 61 62 /** 63 * Function to update the loop wrapper content 64 * as per data in filters. 65 * 66 * @param {Event} event The event. 67 * @param {string} paged The paged parameter for displaying a particular page on click of pagination links. 68 * @param {string} buttonFilter The array of selected button value. 69 * @param {string} loopParentContainer The loop parent container. 70 * @return {Promise} The Promise. 71 * @throws {Error} The error. 72 * @since 1.2.0 73 */ 74 async function updateContent( event, paged = null, buttonFilter = null, loopParentContainer ) { 75 try { 76 const loopBuilder = loopParentContainer; 77 const formData = new FormData(); 78 79 // Get all form data 80 const search = loopBuilder?.querySelector( '.uagb-loop-search' )?.value || ''; 81 const sorting = loopBuilder?.querySelector( '.uagb-loop-sort' )?.value || ''; 82 83 // Get category select value 84 const categorySelect = loopBuilder?.querySelector( '.uagb-loop-category' ); 85 if ( categorySelect?.value ) { 86 formData.append( 'category', categorySelect.value ); 87 storeActiveFilter( loopBuilder, categorySelect.value, 'select' ); 88 } else { 89 // Try to get stored select filter 90 const storedSelectFilter = getActiveFilter( loopBuilder, 'select' ); 91 if ( storedSelectFilter ) { 92 formData.append( 'category', storedSelectFilter ); 93 } 94 } 95 96 // Handle checkboxes 97 const checkBoxValues = loopBuilder?.querySelectorAll( '.uagb-cat-checkbox' ); 98 const checkedValues = []; 99 checkBoxValues?.forEach( checkBox => { 100 if ( checkBox.checked && checkBox.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ) { 101 checkedValues.push( checkBox.value ); 102 } 103 } ); 104 105 // Handle button filter 106 let activeButtonData = getActiveFilter( loopBuilder, 'button' ); 107 if ( ! activeButtonData && buttonFilter?.type ) { 108 activeButtonData = buttonFilter.type; 109 storeActiveFilter( loopBuilder, activeButtonData, 'button' ); 110 } 111 112 // Clear all filters first 113 formData.delete( 'buttonFilter' ); 114 formData.delete( 'checkbox' ); 115 formData.delete( 'category' ); 116 loopBuilder.removeAttribute( 'data-active-filter-checkbox' ); 117 loopBuilder.removeAttribute( 'data-active-filter-select' ); 118 loopBuilder.removeAttribute( 'data-active-filter-button' ); 119 120 // Handle filters based on the event target 121 if ( event.target.classList.contains( 'uagb-cat-checkbox' ) ) { 122 // Checkbox was clicked - clear select and apply checkbox 123 if ( categorySelect ) categorySelect.value = ''; 124 formData.append( 'checkbox', checkedValues ); 125 storeActiveFilter( loopBuilder, JSON.stringify( checkedValues ), 'checkbox' ); 126 } else if ( event.target.classList.contains( 'uagb-loop-category' ) ) { 127 // Select was changed - clear checkboxes and apply select 128 checkBoxValues?.forEach( checkBox => { 129 if ( checkBox.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ) { 130 checkBox.checked = false;
131 } 132 } ); 133 formData.append( 'category', categorySelect.value ); 134 storeActiveFilter( loopBuilder, categorySelect.value, 'select' ); 135 } else if ( buttonFilter?.type ) { 136 // Button was clicked - clear select and checkboxes 137 if ( categorySelect ) categorySelect.value = ''; 138 checkBoxValues?.forEach( checkBox => { 139 if ( checkBox.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ) { 140 checkBox.checked = false; 141 } 142 } ); 143 formData.append( 'buttonFilter', buttonFilter.type ); 144 storeActiveFilter( loopBuilder, buttonFilter.type, 'button' ); 145 } 146 147 // Add other form data 148 if ( search ) { 149 formData.append( 'search', search ); 150 } 151 if ( sorting ) { 152 formData.append( 'sorting', sorting ); 153 } 154 if ( paged ) { 155 formData.append( 'paged', paged ); 156 } 157 158 let queryId = null; 159 if ( buttonFilter?.type === 'all' ) { 160 const el = document.querySelector( '[data-query-id]' ); 161 if ( el ) { 162 queryId = el.getAttribute( 'data-query-id' ); 163 } else { 164 queryId = 0; // fallback to default 165 } 166 167 } else { 168 // Get query ID 169 queryId = event.target?.dataset?.uagbBlockQueryId || 170 event.target?.parentElement?.dataset?.uagbBlockQueryId || 171 event?.dataset?.uagbBlockQueryId || 172 event.target.closest( 'a' )?.getAttribute( 'data-uagb-block-query-id' ) || 0; 173 } 174 175 // Scroll to the query ID 176 scrollToQueryId( queryId ); 177 178 formData.append( 'queryId', queryId ); 179 formData.append( 'block_id', loopBuilder?.getAttribute( 'data-block_id' ) ); 180 formData.append( 'action', 'uagb_update_loop_builder_content' ); 181 formData.append( 'postId', uagb_loop_builder.post_id ); 182 formData.append( 'postType', uagb_loop_builder.post_type ); 183 formData.append( 'security', uagb_loop_builder.nonce ); 184 185 // Log the complete form data 186 const formDataObj = {}; 187 formData.forEach( ( value, key ) => { 188 formDataObj[key] = value; 189 } ); 190 191 const output = await getUpdatedLoopWrapperContent( formData ); 192 193 // Update content 194 if ( output?.content?.wrapper ) { 195 const loopElement = loopBuilder?.querySelector( '#uagb-block-queryid-' + queryId ); 196 if ( loopElement ) { 197 loopElement.innerHTML = output.content.wrapper; 198 } 199 } 200 201 // Update pagination 202 if ( output?.content?.pagination ) { 203 const paginationElements = loopBuilder?.querySelectorAll( '#uagb-block-pagination-queryid-' + queryId ); 204 paginationElements?.forEach( element => { 205 element.innerHTML = output.content.pagination; 206 } ); 207 } 208 209 } catch ( error ) { 210 throw error; 211 } 212 } 213 214 /** 215 * Handles the input event for the search functionality within the UAGB Loop Builder block. 216 * Synchronizes input values across all search inputs within the same loop builder container 217 * and triggers a content update. 218 * 219 * @param {Event} event - The input event triggered by the user. 220 * @since 1.2.0 221 */ 222 function handleInput( event ) { 223 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 224 const searchInputs = loopParentContainer.querySelectorAll( '.uagb-loop-search' );
225 searchInputs.forEach( searchInput => { 226 if( searchInput.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ){ 227 searchInput.value = event.target.value; 228 } 229 } ); 230 updateContent( event, null, null, loopParentContainer ); 231 } 232 233 /** 234 * Handles the checkbox selection within the UAGB Loop Builder block. 235 * Collects the values of all checked checkboxes that match the block query ID 236 * and triggers a content update. 237 * 238 * @param {Event} event - The change event triggered by the user when interacting with a checkbox. 239 * @since 1.2.0 240 */ 241 function handleCheckBoxVal( event ) { 242 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 243 const checkBoxValues = loopParentContainer.querySelectorAll( '.uagb-cat-checkbox' ); 244 // Initialize an array to store checked checkbox values. 245 const checkedValues = []; 246 checkBoxValues.forEach( checkBoxVal => { 247 // Check if the checkbox is checked. 248 const isChecked = checkBoxVal.checked; 249 if ( isChecked && checkBoxVal.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ) { 250 // Add the value to the array. 251 checkedValues.push( checkBoxVal.value ); 252 } 253 } ); 254 255 // Always update content when checkbox state changes, even if no checkboxes are checked 256 updateContent( event, null, null, loopParentContainer ); 257 } 258 259 /** 260 * Handles the selection event on the search filter. 261 * Updates the value of all relevant search filter elements with the same query ID and triggers content update. 262 * 263 * @param {Event} event - The select event triggered by the user interaction. 264 * @since 1.2.0 265 */ 266 function handleSelect( event ) { 267 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 268 const sortSelects = loopParentContainer.querySelectorAll( '.uagb-loop-sort' ); 269 sortSelects.forEach( sortSelect => { 270 if( sortSelect.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ){ 271 sortSelect.value = event.target.value; 272 } 273 } ); 274 updateContent( event, null, null, loopParentContainer ); 275 } 276 277 /** 278 * Handles the category selection event on a dropdown filter. 279 * Updates the value of all relevant category select elements with the same query ID and triggers content update. 280 * 281 * @param {Event} event - The select event triggered by the user interaction. 282 * @since 1.2.0 283 */ 284 function handleCatSelect( event ) { 285 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 286 const categorySelects = loopParentContainer.querySelectorAll( '.uagb-loop-category' ); 287 categorySelects.forEach( categorySelect => { 288 if ( categorySelect.getAttribute( 'data-uagb-block-query-id' ) === event.target.dataset.uagbBlockQueryId ) { 289 categorySelect.value = event.target.value; 290 } 291 } ); 292 293 // If "all" or empty value is selected, clear the stored filter 294 if ( !event.target.value || event.target.value === '' || event.target.value === 'all' ) { 295 loopParentContainer.removeAttribute( 'data-active-filter-select' ); 296 } 297 298 updateContent( event, null, null, loopParentContainer ); 299 } 300 301 /** 302 * Resets the values of elements within a container based on their query ID. 303 * 304 * @param {HTMLElement} container - The container element to search within. 305 * @param {string} selector - The CSS selector for the elements to reset. 306 * @param {string} queryId - The query ID to match. 307 * @param {Function} resetCallback - A callback function to apply the reset logic to each element. 308 * @since 1.2.0 309 */ 310 function resetValues( container, selector, queryId, resetCallback ) { 311 const elements = container.querySelectorAll( selector );
312 elements.forEach( element => { 313 const elementQueryId = element.dataset.uagbBlockQueryId; 314 if ( elementQueryId === queryId ) { 315 resetCallback( element ); 316 } 317 } ); 318 } 319 320 /** 321 * Handles the reset event for all filters within the loop builder block. 322 * Resets the values of search inputs, sort selects, category selects, and checkboxes to their default state. 323 * 324 * @param {Event} event - The reset event triggered by the user interaction. 325 * @since 1.2.0 326 */ 327 function handleReset( event ) { 328 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 329 // Get the query ID from the event target 330 let queryId = event.target.parentElement.dataset.uagbBlockQueryId; 331 if ( event.target.tagName.toLowerCase() === 'a' ) { 332 queryId = event.target.dataset.uagbBlockQueryId; 333 } else if ( event.target.tagName.toLowerCase() === 'svg' || event.target.tagName.toLowerCase() === 'path' ) { 334 queryId = event.target.closest( 'a' )?.getAttribute( 'data-uagb-block-query-id' ); 335 } 336 337 // Reset the value of the filter inputs 338 const loopBuilder = findAncestorWithClass( event.target.parentNode, 'wp-block-uagb-loop-builder' ); 339 340 resetValues( loopBuilder, '.uagb-loop-search', queryId, element => { 341 element.value = ''; // Reset search input value 342 } ); 343 344 resetValues( loopBuilder, '.uagb-loop-sort', queryId, element => { 345 element.value = ''; // Reset sort select value 346 } ); 347 348 resetValues( loopBuilder, '.uagb-loop-category', queryId, element => { 349 element.value = ''; // Reset category select value 350 } ); 351 352 resetValues( loopBuilder, '.uagb-cat-checkbox', queryId, element => { 353 element.checked = false; // Uncheck category checkbox 354 } ); 355 356 // Clear all stored filter data attributes 357 if ( loopBuilder ) { 358 loopBuilder.removeAttribute( 'data-active-filter-select' ); 359 loopBuilder.removeAttribute( 'data-active-filter-checkbox' ); 360 loopBuilder.removeAttribute( 'data-active-filter-button' ); 361 } 362 363 // Trigger the updateContent function to reflect the changes 364 updateContent( event, null, null, loopParentContainer ); 365 } 366 367 const resetButtons = document.querySelectorAll( '.uagb-loop-reset' ); 368 369 const searchInputs = document.querySelectorAll( '.uagb-loop-search' ); 370 371 searchInputs.forEach( searchInput => { 372 const debouncedHandleInput = debounce( handleInput, 250 ); 373 searchInput.addEventListener( 'input', debouncedHandleInput ); 374 } ); 375 376 const sortSelects = document.querySelectorAll( '.uagb-loop-sort' ); 377 378 sortSelects.forEach( sortSelect => { 379 const debouncedHandleInput = debounce( handleSelect, 250 ); 380 sortSelect.addEventListener( 'change', debouncedHandleInput ); 381 } ); 382 383 const categorySelects = document.querySelectorAll( '.uagb-loop-category' ); 384 385 categorySelects.forEach( categorySelect => { 386 const debouncedHandleInput = debounce( handleCatSelect, 250 ); 387 categorySelect.addEventListener( 'change', debouncedHandleInput ); 388 } ); 389 390 // Get a reference to the checkbox element. 391 const checkBoxValues = document.querySelectorAll( '.uagb-cat-checkbox' ); 392 checkBoxValues.forEach( checkBoxVal => { 393 const debouncedHandleInput = debounce( handleCheckBoxVal, 250 ); 394 checkBoxVal.addEventListener( 'click', debouncedHandleInput ); 395 } ); 396 397 resetButtons.forEach( resetButton => { 398 const debouncedHandleReset = debounce( handleReset, 250 ); 399 resetButton.addEventListener( 'click', debouncedHandleReset ); 400 } ); 401 402 const oldPaginations = document.querySelectorAll( '.wp-block-uagb-loop-builder >
402 :not(.uagb-loop-pagination).wp-block-uagb-buttons' ); 403 404 oldPaginations?.forEach( function( container ) { 405 // Create a new div with class "parent-container" 406 const parentContainer = document.createElement( 'div' ); 407 parentContainer.classList.add( 'uagb-loop-pagination' ); 408 const queryIdPAginationLink = container.querySelector( 'a' ).getAttribute( 'data-uagb-block-query-id' ); 409 parentContainer.id = 'uagb-block-pagination-queryid-'+queryIdPAginationLink; 410 411 // Append the container content to the new div 412 parentContainer.innerHTML = container.outerHTML; 413 414 // Append the new div after the original container 415 container.parentNode.insertBefore( parentContainer, container.nextSibling ); 416 417 // Remove the original container 418 container.parentNode.removeChild( container ); 419 } ); 420 421 const paginationContainer = document.querySelectorAll( '.uagb-loop-pagination' ); 422 423 paginationContainer.forEach( pagination => { 424 pagination.addEventListener( 'click', function( event ) { 425 event.preventDefault(); 426 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 427 428 const paged = event.target.dataset.uagbBlockQueryPaged || 429 event.target.parentElement.dataset.uagbBlockQueryPaged || 430 event?.target?.closest( 'a' )?.getAttribute( 'data-uagb-block-query-paged' ); 431 432 // Get all active filter data 433 const activeButtonData = getActiveFilter( loopParentContainer, 'button' ); 434 const activeSelectData = getActiveFilter( loopParentContainer, 'select' ); 435 const activeCheckboxData = getActiveFilter( loopParentContainer, 'checkbox' ); 436 437 // Create form data based on active filter 438 const formData = new FormData(); 439 440 // Add pagination 441 if ( paged ) { 442 formData.append( 'paged', paged ); 443 } 444 445 // Add active filter based on type 446 if ( activeButtonData ) { 447 formData.append( 'buttonFilter', activeButtonData ); 448 } else if ( activeSelectData ) { 449 formData.append( 'category', activeSelectData ); 450 } else if ( activeCheckboxData ) { 451 formData.append( 'checkbox', activeCheckboxData ); 452 } 453 454 // Add other required data 455 formData.append( 'queryId', event.target.dataset.uagbBlockQueryId || 456 event.target.parentElement.dataset.uagbBlockQueryId || 457 event?.target?.closest( 'a' )?.getAttribute( 'data-uagb-block-query-id' ) || 0 ); 458 formData.append( 'block_id', loopParentContainer?.getAttribute( 'data-block_id' ) ); 459 formData.append( 'action', 'uagb_update_loop_builder_content' ); 460 formData.append( 'postId', uagb_loop_builder.post_id ); 461 formData.append( 'postType', uagb_loop_builder.post_type ); 462 formData.append( 'security', uagb_loop_builder.nonce ); 463
464 // Get search and sorting values if they exist 465 const search = loopParentContainer?.querySelector( '.uagb-loop-search' )?.value || ''; 466 const sorting = loopParentContainer?.querySelector( '.uagb-loop-sort' )?.value || ''; 467 468 if ( search ) { 469 formData.append( 'search', search ); 470 } 471 if ( sorting ) { 472 formData.append( 'sorting', sorting ); 473 } 474 475 // Make the AJAX request 476 getUpdatedLoopWrapperContent( formData ) 477 .then( output => { 478 if ( output?.content?.wrapper ) { 479 const loopElement = loopParentContainer?.querySelector( '#uagb-block-queryid-' + formData.get( 'queryId' ) ); 480 if ( loopElement ) { 481 loopElement.innerHTML = output.content.wrapper; 482 } 483 } 484 if ( output?.content?.pagination ) { 485 const paginationElements = loopParentContainer?.querySelectorAll( '#uagb-block-pagination-queryid-' + formData.get( 'queryId' ) ); 486 paginationElements?.forEach( element => { 487 element.innerHTML = output.content.pagination; 488 } ); 489 } 490 } ) 491 .catch( error => { 492 throw error; // Propagate the error 493 } ); 494 } ); 495 } ); 496 497 const categoryButtonFilterContainer = document.querySelectorAll( '.uagb-loop-category-inner a' ); 498 499 categoryButtonFilterContainer.forEach( ( buttons ) => { 500 buttons.addEventListener( 'click', function ( event ) { 501 event.preventDefault(); 502 const loopParentContainer = this.closest( '.wp-block-uagb-loop-builder' ); 503 let buttonData = null; 504 505 if ( event.target.tagName.toLowerCase() === 'a' ) { 506 buttonData = event.target.children[0].dataset.type; 507 } else if ( event.target.tagName.toLowerCase() === 'div' && event.target.parentElement.tagName.toLowerCase() === 'a' ) { 508 buttonData = event.target.dataset.type; 509 } 510 511 // Uncheck all checkboxes when a button is clicked 512 checkBoxValues?.forEach( checkBox => { 513 checkBox.checked = false; 514 } ); 515 516 // Check if "all" button was clicked. 517 if ( buttonData === 'all' || buttonData === undefined ) { 518 // Clear stored button filter. 519 loopParentContainer.removeAttribute( 'data-active-filter-button' ); 520 // Update content with all type. 521 updateContent( event, null, { type: 'all' }, loopParentContainer ); 522 } else if ( buttonData ) { 523 storeActiveFilter( loopParentContainer, buttonData, 'button' ); 524 updateContent( event, null, { type: buttonData }, loopParentContainer ); 525 } 526 } ); 527 } ); 528 } ); 529 530 /** 531 * Function to get the updated loop wrapper content. 532 * as per data in filters. 533 * 534 * @param {FormData} data The form data. 535 * @since 1.2.0 536 * @return {Promise} The Promise. 537 */ 538 function getUpdatedLoopWrapperContent( data ) { 539 // Create a new FormData object 540 data.append( 'action', 'uagb_update_loop_builder_content' ); 541 data.append( 'postId', uagb_loop_builder?.post_id ); 542 data.append( 'postType', uagb_loop_builder?.what_post_type ); 543 data.append( 'security', uagb_loop_builder?.nonce ) 544 545 // The function now returns a Promise 546 return fetch( uagb_loop_builder?.ajax_url, { 547 method: 'POST', 548 credentials: 'same-origin', 549 body: data, 550 } ) 551 .then( response => { 552 if ( ! response.ok ) { 553 throw new Error( 'Network response was not ok' ); 554 } 555 return response.json(); 556 } ) 557 .then( output => { 558 if ( output.success ) { 559 // Return the actual output. 560 return output.data; 561 } 562 throw new Error( output.data.message ); 563 564 } ) 565 .catch( error => { 566 throw error; // Propagate the error 567 } ); 568 }
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.