PageSourceSearch

https://latepoint.com/wp-content/plugins/spectra-pro/assets/js/loop-builder.js?ver=1.3.2

js latepoint.com collected 2026-09-25 02:32:15 UTC 27,005 bytes, 568 lines download raw bytes

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.