PageSourceSearch

https://www.durlem.be/wp-content/plugins/portfolio-elementor/assets/js/custom-portfolio.js?ver=20151215

js durlem.be collected 2026-09-24 11:51:26 UTC 26,746 bytes, 611 lines download raw bytes

1jQuery(window).on('load', function () {
2
3
4    // Aqui eu consigo alternar entre a versao paginada ou normal, apenas pela classe
5    // vamos deixar tudo no mesmo arquivo, e mudar a classe atraves do elementor.
6    // vamos precisar passar algumas informacoes para o JS (numero de paginas, etc)
7
8    // Talvez de para deixar o packery etc tudo na mesma variavel, porque o packery tambem pode ser paginado
9
10    if ( jQuery( ".elpt-portfolio-content" ).length ) {
11        // IMPORTANT: Use :not(.elpt-portfolio-content-isotope-pro) to prevent double Isotope
12        // initialization. Elements with -pro class are handled by $container for pagination.
13
14        //Isotope Layout (masonry without pagination)
15        var $grid = jQuery('.elpt-portfolio-content-isotope:not(.elpt-portfolio-content-isotope-pro)').isotope({
16            //layoutMode: 'packery',
17            layoutMode: 'masonry',
18            itemSelector: '.portfolio-item-wrapper'
19        });
20
21        $grid.imagesLoaded().progress( function() {
22            $grid.isotope('layout');
23        });
24
25        //Packery Layout (without pagination)
26        var $packery = jQuery('.elpt-portfolio-content-packery:not(.elpt-portfolio-content-isotope-pro)').isotope({
27            layoutMode: 'packery',
28            itemSelector: '.portfolio-item-wrapper'
29        });
30
31        $packery.imagesLoaded().progress( function() {
32            $packery.isotope('layout');
33        });
34
35        //fitRows Layout (for Special Grid 7 and similar, without pagination)
36        var $fitrows = jQuery('.elpt-portfolio-content-fitrows:not(.elpt-portfolio-content-isotope-pro)').isotope({
37            layoutMode: 'fitRows',
38            itemSelector: '.portfolio-item-wrapper'
39        });
40
41        $fitrows.imagesLoaded().progress( function() {
42            $fitrows.isotope('layout');
43        });
44
45        /*
46        * Paginated Isotope
47        */
48        //https://codepen.io/TimRizzo/details/ervrRq
49        //https://codepen.io/Igorxp5/pen/ojJLQE
50
51        var itemSelector = ".portfolio-item-wrapper";
52
53        // Support masonry, packery, and fitRows pagination
54        var $container = jQuery('.elpt-portfolio-content-isotope-pro').isotope({
55            layoutMode: 'masonry',
56            itemSelector: itemSelector
57        });
58
59        // Check layout mode based on additional classes
60        if ($container.hasClass('elpt-portfolio-content-fitrows')) {
61            // Special Grid 7 - Alternate Rows 1
62            $container.isotope('option', { layoutMode: 'fitRows' });
63        } else if ($container.hasClass('elpt-portfolio-content-packery')) {
64            // Grid Builder
65            $container.isotope('option', { layoutMode: 'packery' });
66        }
67
68        $container.imagesLoaded().progress( function() {
69            $container.isotope('layout');
70        });
71
72        // Pagination Variables
73        var itemsPerPageDefault = 10; // Default value
74
75        // Prefer the per-container data attribute (works in shortcode + Elementor
76        // contexts and per container). Fall back to the legacy gridSettings global.
77        var containerPerPage = parseInt($container.attr('data-items-per-page'), 10);
78        if (!isNaN(containerPerPage) && containerPerPage > 0) {
79            itemsPerPageDefault = containerPerPage;
80        } else if (typeof gridSettings !== 'undefined' && gridSettings.itemsPerPageDefault !== undefined) {
81            itemsPerPageDefault = gridSettings.itemsPerPageDefault;
82        }
83
84        var itemsPerPage = defineItemsPerPage();
85        var currentNumberPages = 1;
86        var currentPage = 1;
87        var currentFilter = '*';
88        var filterAtribute = 'data-filter';
89        var pageAtribute = 'data-page';
90        var pagerClass = 'isotope-pager';
91
92        // ===========================================
93        // Fixed Layout Mode: Race Condition Prevention
94        // ===========================================
95        // These variables prevent race conditions when users click rapidly on
96        // pagination or filters. Only affects Fixed Layout Mode (Grid Builder).
97        //
98        // KEY CONCEPT: Each filter/pagination click should be treated as a fresh start.
99        // We cache the original DOM order and sizes, then restore from cache on each operation.
100        var fixedLayoutEventId = 0;           // Invalidates outdated event handlers
101        var fixedLayoutOriginalOrder = [];    // Cache of items in original DOM order
102        var fixedLayoutInitialized = false;   // Whether cache has been built
103        var fixedLayoutCompletedEventId = 0;  // Tracks which eventId has completed (prevents infinite loop)
104
105        // Debug logging for Fixed Layout Mode (disabled in production)
106        var FIXED_LAYOUT_DEBUG = false;
107        function fixedLayoutLog(message, data) {
108            if (FIXED_LAYOUT_DEBUG) {
109                if (data !== undefined) {
110                    console.log('[FixedLayout]', message, data);
111                } else {
112                    console.log('[FixedLayout]', message);
113                }
114            }
115        }
116
117        /**
118         * Initialize Fixed Layout cache - stores original DOM order of all items.
119         * This should be called once on page load for Fixed Layout grids.
120         */
121        function initFixedLayoutCache($containerElement) {
122            if (fixedLayoutInitialized) {
123                return;
124            }
125
126            var $allItems = $containerElement.children('.portfolio-item-wrapper');
127            fixedLayoutOriginalOrder = [];
128
129            $allItems.each(function(index) {
130                fixedLayoutOriginalOrder.push({
131                    element: this,
132                    originalIndex: index
133                });
134            });
135
136            fixedLayoutInitialized = true;
137            fixedLayoutLog('Cache initialized with ' + fixedLayoutOriginalOrder.length + ' items');
138        }
139
140        /**
141         * Reset all position classes to original state based on cached order.
142         * This ensures each operation starts from a clean, consistent state.
143         */
144        function resetFixedLayoutPositions($containerElement) {
145            var $allItems = $containerElement.children('.portfolio-item-wrapper');
146
147            // Remove ALL position classes from ALL items
148            $allItems.removeClass(function(index, className) {
149                return (className.match(/\belpt-grid-pos-\d+\b/g) || []).join(' ');
150            });
151
152            fixedLayoutLog('All position classes removed');
153        }
154
155        /**
156         * Scroll to the top of the grid container INSTANTLY (no animation).
157         * This must happen BEFORE any layout processing so user sees the grid top first.
158         * Only applies to Fixed Layout Mode (Grid Builder).
159         *
160         * Addons can modify the scroll target via 'powerfolio:scroll_target' filter.
161         *
162         * @param {jQuery} $containerElement - The grid container element
163         */
164        function scrollToGridTop($containerElement) {
165            if (!$containerElement || $containerElement.length === 0) {
166                return;
167            }
168
169            var gridTop = $containerElement.offset().top;
170            var margin = 100; // pixels above the grid to account for headers/nav
171            var targetScroll = Math.max(0, gridTop - margin);
172            var currentScroll = jQuery(window).scrollTop();
173
174            // Allow addons to modify scroll target (e.g., scroll to page top instead of grid top)
175            var scrollData = {
176                target: targetScroll,
177                gridTop: gridTop,
178                margin: margin,
179                container: $containerElement
180            };
181            jQuery(document).trigger('powerfolio:scroll_target', [scrollData]);
182            targetScroll = scrollData.target;
183
184            // Only scroll if user is below the target (don't scroll up if already above)
185            if (currentScroll > targetScroll) {
186                // INSTANT scroll - no animation, must complete before layout processing
187                window.scrollTo(0, targetScroll);
188
189                fixedLayoutLog('Scrolled to: ' + targetScroll + 'px');
190            }
191        }
192
193        // Resize handler with debounce
194        // Fixed Layout Mode requires special handling to maintain positions
195        var resizeTimeout;
196        jQuery(window).resize(function(){
197            clearTimeout(resizeTimeout);
198            resizeTimeout = setTimeout(function() {
199                // Check if Fixed Layout Mode is enabled (only for Grid Builder)
200                var $gridBuilder = jQuery('.elpt-portfolio-content-packery.elpt-portfolio-grid-builder');
201                var isFixedLayout = $gridBuilder.length > 0 && $gridBuilder.hasClass('elpt-fixed-layout-mode');
202
203                if ($container.length > 0) {
204                    // Has pagination: maintain current page and filter
205                    if (isFixedLayout) {
206                        fixedLayoutLog('Resize detected - triggering fresh layout');
207                        // For Fixed Layout, just trigger goToPage which will handle everything
208                    }
209                    goToPage(currentPage);
210                } else {
211                    // No pagination: just refresh layout
212                    if (isFixedLayout) {
213                        // Fixed Layout without pagination: re-apply positions
214                        applyFixedLayoutPositionsFromIsotope($packery);
215                        $packery.isotope('layout');
216                    } else {
217                        // Normal grids: just trigger layout refresh
218                        $grid.isotope('layout');
219                        $packery.isotope('layout');
220                        $fitrows.isotope('layout');
221                    }
222                }
223            }, 150); // 150ms debounce
224        });
225
226
227
228        // update items based on current filters
229        function changeFilter(selector) {
230            $container.isotope({ filter: selector }
231        ); }
232
233
234        function getFilterSelector() {
235            var selector = itemSelector;
236            if (currentFilter != '*') {
237              selector += currentFilter;
238            }
239            return selector;
240        }
241
242        function goToPage(n) {
243            // Check if Fixed Layout Mode is enabled (only for Grid Builder)
244            var $gridBuilder = jQuery('.elpt-portfolio-content-packery.elpt-portfolio-grid-builder');
245            var isFixedLayout = $gridBuilder.length > 0 && $gridBuilder.hasClass('elpt-fixed-layout-mode');
246
247            currentPage = n;
248
249            var selector = getFilterSelector();
250            selector += '[' + pageAtribute + '="' + currentPage + '"]';
251
252            if (isFixedLayout) {
253                // FIXED LAYOUT MODE - Fresh Start Approach
254                // ========================================
255                // Each call to goToPage CANCELS any previous operation and starts fresh.
256                // No queuing, no waiting - just invalidate old handlers and restart.
257                //
258                // Strategy:
259                // 1. Increment eventId to invalidate ALL previous handlers
260                // 2. Initialize cache if needed (first run only)
261                // 3. Reset all position classes to clean state
262                // 4. Apply position classes for visible items (DOM order)
263                // 5. Apply Isotope filter and layout
264                // 6. On arrangeComplete, correct positions based on visual order
265                // 7. Final layout pass (deferred with setTimeout to break recursion)
266
267                // Step 1: Invalidate ALL previous operations
268                fixedLayoutEventId++;
269                var currentEventId = fixedLayoutEventId;
270
271                fixedLayoutLog('goToPage(' + n + ') START - eventId: ' + currentEventId + ' (previous operations cancelled)');
272
273                // Step 2: Initialize cache on first run
274                initFixedLayoutCache($container);
275
276                // Step 3: Reset ALL position classes to clean state
277                resetFixedLayoutPositions($container);
278
279                // Step 4: Apply position classes for items matching selector (DOM order)
280                applyFixedLayoutPositionsForCurrentFilter($container, selector);
281
282                // Step 5: Apply Isotope filter
283                changeFilter(selector);
284
285                // Step 6: Force layout - Isotope will calculate positions
286                $container.isotope('layout');
287
288                fixedLayoutLog('goToPage(' + n + ') - layout triggered, eventId: ' + currentEventId);
289
290                // Step 7: After Isotope arranges, correct positions and do final layout
291                $container.isotope('once', 'arrangeComplete', function() {
292                    // CRITICAL: Ignore if this event is outdated (user clicked something else)
293                    if (currentEventId !== fixedLayoutEventId) {
294                        fixedLayoutLog('arrangeComplete SKIPPED - outdated eventId: ' + currentEventId + ', current: ' + fixedLayoutEventId);
295                        return;
296                    }
297
298                    // Prevent infinite loop: if this eventId already completed its final pass, ignore
299                    // This happens when layout() triggers another arrangeComplete
300                    if (currentEventId <= fixedLayoutCompletedEventId) {
301                        fixedLayoutLog('arrangeComplete SKIPPED - eventId ' + currentEventId + ' already completed');
302                        return;
303                    }
304
305                    fixedLayoutLog('goToPage(' + n + ') arrangeComplete - eventId: ' + currentEventId);
306
307                    // Correct position classes based on Isotope's visual order
308                    applyFixedLayoutPositionsFromIsotope($container);
309
310                    // Mark this event as completed BEFORE the final layout
311                    // This prevents the arrangeComplete triggered by layout() from running again
312                    fixedLayoutCompletedEventId = currentEventId;
313
314                    // Final layout pass - deferred to break synchronous chain
315                    setTimeout(function() {
316                        // Check again - user might have clicked during the timeout
317                        if (currentEventId !== fixedLayoutEventId) {
318                            fixedLayoutLog('setTimeout SKIPPED - outdated eventId: ' + currentEventId);
319                            return;
320                        }
321
322                        fixedLayoutLog('goToPage(' + n + ') FINAL layout - eventId: ' + currentEventId);
323                        $container.isotope('layout');
324
325                        fixedLayoutLog('goToPage(' + n + ') COMPLETE - eventId: ' + currentEventId);
326
327                        // Trigger event for addons (e.g., parallax effects)
328                        jQuery(document).trigger('powerfolio:layout_complete', [$container, currentPage, currentFilter]);
329                    }, 0);
330                });
331            } else {
332                // Normal mode: just apply filter (unchanged behavior)
333                changeFilter(selector);
334
335                // Trigger event for addons after layout settles
336                setTimeout(function() {
337                    jQuery(document).trigger('powerfolio:layout_complete', [$container, currentPage, currentFilter]);
338                }, 100);
339            }
340        }
341
342        /**
343         * Fixed Layout Mode: Apply position classes based on current filter selector
344         * This applies classes BEFORE Isotope calculates positions, using DOM order.
345         * Used to ensure items have correct sizes during initial layout calculation.
346         */
347        function applyFixedLayoutPositionsForCurrentFilter($containerElement, filterSelector) {
348            var $allItems = $containerElement.children('.portfolio-item-wrapper');
349
350            // Remove all position classes from all items
351            $allItems.removeClass(function(index, className) {
352                return (className.match(/\belpt-grid-pos-\d+\b/g) || []).join(' ');
353            });
354
355            // Get items matching the filter selector
356            var $matchingItems = $allItems.filter(filterSelector);
357
358            // Apply position classes in DOM order
359            $matchingItems.each(function(index) {
360                jQuery(this).addClass('elpt-grid-pos-' + (index + 1));
361            });
362        }
363
364        function defineItemsPerPage() {
365            var pages = itemsPerPageDefault;
366
367            return pages;
368        }
369
370        function setPagination() {
371
372            var SettingsPagesOnItems = function(){
373
374                var item = 1;
375                var page = 1;
376                var selector = getFilterSelector();
377
378                $container.children(selector).each(function(){
379                    if( item > itemsPerPage ) {
380                        page++;
381                        item = 1;
382                    }
383                    jQuery(this).attr(pageAtribute, page);
384                    item++;
385                });
386
387                currentNumberPages = page;
388
389            }();
390
391            var CreatePagers = function() {
392
393                var $isotopePager = ( jQuery('.'+pagerClass).length == 0 ) ? jQuery('<div class="'+pagerClass+'"></div>') : jQuery('.'+pagerClass);
394
395                $isotopePager.html('');
396
397                for( var i = 0; i < currentNumberPages; i++ ) {
398                    var $pager = jQuery('<a href="javascript:void(0);" class="pager" '+pageAtribute+'="'+(i+1)+'"></a>');
399                        $pager.html(i+1);
400
401                        $pager.click(function(){
402                            jQuery('.isotope-pager .active').removeClass('active');
403                            jQuery(this).addClass('active');
404                            var page = jQuery(this).eq(0).attr(pageAtribute);
405
406                            // Scroll to grid top BEFORE processing (Fixed Layout Mode)
407                            var $gridBuilder = jQuery('.elpt-portfolio-content-packery.elpt-portfolio-gri
407d-builder');
408                            if ($gridBuilder.length > 0 && $gridBuilder.hasClass('elpt-fixed-layout-mode')) {
409                                scrollToGridTop($container);
410                            }
411
412                            goToPage(page);
413                        });
414
415                    $pager.appendTo($isotopePager);
416                }
417
418                $container.after($isotopePager);
419
420            }();
421
422        }
423
424        setPagination();
425        goToPage(1);
426
427        /**
428         * Fixed Layout Mode: Apply position classes based on Isotope's visual order
429         *
430         * This function uses Isotope's internal filteredItems array to get elements
431         * in their correct visual order (not DOM order). This is essential for
432         * Fixed Layout Mode where CSS classes determine element sizes/positions.
433         *
434         * @param {jQuery} $isotopeContainer - The Isotope container element
435         */
436        function applyFixedLayoutPositionsFromIsotope($isotopeContainer) {
437            var isotope = $isotopeContainer.data('isotope');
438            if (!isotope) {
439                return;
440            }
441
442            var $allItems = $isotopeContainer.children('.portfolio-item-wrapper');
443
444            // Remove all position classes from all items
445            $allItems.removeClass(function(index, className) {
446                return (className.match(/\belpt-grid-pos-\d+\b/g) || []).join(' ');
447            });
448
449            // Get filtered items in Isotope's visual order
450            var filteredItems = isotope.filteredItems;
451            if (!filteredItems || filteredItems.length === 0) {
452                return;
453            }
454
455            // Apply position classes based on visual order
456            filteredItems.forEach(function(item, index) {
457                jQuery(item.element).addClass('elpt-grid-pos-' + (index + 1));
458            });
459        }
460
461        /**
462         * Fixed Layout Mode: Apply position classes for filtered items (pre-pagination)
463         *
464         * This function is called BEFORE pagination is applied. It assigns position
465         * classes to ALL items matching the filter, which will then be narrowed down
466         * by pagination. Uses DOM order since Isotope hasn't filtered yet.
467         *
468         * Note: This is kept for the initial filter application. After Isotope
469         * arranges items, applyFixedLayoutPositionsFromIsotope() corrects the order.
470         *
471         * @param {jQuery} $containerElement - The grid container element
472         * @param {string} filterValue - The filter selector (e.g., '.category-1' or '*')
473         */
474        function applyFixedLayoutFilter($containerElement, filterValue) {
475            var $allItems = $containerElement.children('.portfolio-item-wrapper');
476
477            // Determine which items match the filter
478            var $matchingItems;
479            if (filterValue === '*') {
480                $matchingItems = $allItems;
481            } else {
482                $matchingItems = $allItems.filter(filterValue);
483            }
484
485            // Remove all position classes from all items
486            $allItems.removeClass(function(index, className) {
487                return (className.match(/\belpt-grid-pos-\d+\b/g) || []).join(' ');
488            });
489
490            // Apply initial position classes (will be corrected after Isotope arranges)
491            $matchingItems.each(function(visualIndex) {
492                jQuery(this).addClass('elpt-grid-pos-' + (visualIndex + 1));
493            });
494        }
495
496        // On Click Actions
497        jQuery('.elpt-portfolio-filter').on('click', 'button', function () {
498            jQuery('.elpt-portfolio-filter button').removeClass('item-active');
499            jQuery(this).addClass('item-active');
500
501            var filterValue = jQuery(this).attr(filterAtribute);
502            var filter = filterValue;
503            currentFilter = filter;
504
505            // Check if Fixed Layout Mode is enabled (only for Grid Builder)
506            var $gridBuilder = jQuery('.elpt-portfolio-content-packery.elpt-portfolio-grid-builder');
507            var isFixedLayout = $gridBuilder.hasClass('elpt-fixed-layout-mode');
508
509            if (isFixedLayout) {
510                fixedLayoutLog('Filter clicked: ' + filterValue);
511
512                // If pagination is NOT enabled, apply Isotope filter directly
513                if ($container.length === 0) {
514                    // No pagination: apply Isotope filter on $packery
515                    // Use Fresh Start Approach - cancel previous and restart
516
517                    fixedLayoutEventId++;
518                    var currentEventId = fixedLayoutEventId;
519
520                    fixedLayoutLog('Filter START (no pagination) - eventId: ' + currentEventId);
521
522                    // Reset and apply position classes
523                    resetFixedLayoutPositions($packery);
524                    applyFixedLayoutFilter($gridBuilder, filterValue);
525
526                    $packery.isotope({ filter: filterValue });
527                    $packery.isotope('layout');
528
529                    // After Isotope arranges, correct the position classes and do final layout
530                    $packery.isotope('once', 'arrangeComplete', function() {
531                        if (currentEventId !== fixedLayoutEventId) {
532                            fixedLayoutLog('Filter arrangeComplete SKIPPED - outdated eventId: ' + currentEventId);
533                            return;
534                        }
535
536                        // Prevent infinite loop: if this eventId already completed, ignore
537                        if (currentEventId <= fixedLayoutCompletedEventId) {
538                            fixedLayoutLog('Filter arrangeComplete SKIPPED - eventId ' + currentEventId + ' already completed');
539                            return;
540                        }
541
542                        fixedLayoutLog('Filter arrangeComplete (no pagination) - eventId: ' + currentEventId);
543
544                        applyFixedLayoutPositionsFromIsotope($packery);
545
546                        // Mark as completed before final layout
547                        fixedLayoutCompletedEventId = currentEventId;
548
549                        setTimeout(function() {
550                            if (currentEventId !== fixedLayoutEventId) {
551                                return;
552                            }
553
554                            fixedLayoutLog('Filter FINAL layout (no pagination) - eventId: ' + currentEventId);
555                            $packery.isotope('layout');
556
557                            fixedLayoutLog('Filter COMPLETE (no pagination) - eventId: ' + currentEventId);
558
559                            // Trigger event for addons (Fixed Layout without pagination)
560                            jQuery(document).trigger('powerfolio:layout_complete', [$packery, 1, filterValue]);
561                        }, 0);
562                    });
563                }
564                // If pagination IS enabled, we'll handle it below with setPagination + goToPage
565            } else {
566                // Normal: Use Isotope filter (current behavior for non-Fixed Layout)
567                $grid.isotope({
568                    filter: filterValue
569                });
570                $packery.isotope({
571                    filter: filterValue
572                });
573                $fitrows.isotope({
574                    filter: filterValue
575                });
576
577                // Trigger event for addons after filter layout settles (non-paginated grids)
578                if ($container.length === 0) {
579                    setTimeout(function() {
580                        // Pass whichever grid is active
581                        var $activeGrid = $packery.length > 0 ? $packery : ($grid.length > 0 ? $grid : $fitrows);
582                        jQuery(document).trigger('powerfolio:layout_complete', [$activeGrid, 1, filterValue]);
583                    }, 100);
584                }
585            }
586
587            // Only call pagination functions if pagination is enabled
588            // ($container targets .elpt-portfolio-content-isotope-pro which only exists with pagination)
589            if ($container.length > 0) {
590                if (isFixedLayout) {
591                    // Scroll to grid top BEFORE processing
592                    scrollToGridTop($container);
593                    fixedLayoutLog('Filter with pagination - calling setPagination + goToPage(1)');
594                }
595                setPagination();
596                goToPage(1);
597            }
598        });
599
600        // Trigger initialization event for addons
601        // Provides references to all grid containers for addon initialization
602        jQuery(document).trigger('powerfolio:grid_initialized', [{
603            grid: $grid,
604            packery: $packery,
605            fitrows: $fitrows,
606            container: $container
607        }]);
608
609    }
610
611});

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.