Skip to content

Latest commit

 

History

History
478 lines (385 loc) · 12 KB

custom-ajax-filter-gallery.md

File metadata and controls

478 lines (385 loc) · 12 KB

Custom AJAX Filter (Image Gallery) + Lazy Loading

Gif of Custom AJAX Filter

Description

⚠️ Outdated: New version available at wp-oxygen-elements ⚠️

In this tutorial, I will show you how to make an image gallery with a custom AJAX filter and built-in lazy loading! We will use:

  • Oxygen Builder
  • Custom Post Type UI
  • Code Snippets
  • Advanced Custom Fields PRO

Tutorial

  1. Install Custom Post Type UI

    • CPT UIAdd/Edit Post Types
      • Post Type Slug: gallery
      • Plural Label: Galleries
      • Singular Label: Gallery
  2. Install Advanced Custom Fields PRO

    • Custom FieldsAdd New
      • Title: Gallery

      • Rules: Post Type is equal to Post

      • + Add Fields

        • Field Label: Gallery
        • Field Name: gallery
        • Field Type: Gallery
  3. Go to GalleriesAdd New and create for example three posts, Event, Portrait and Wedding.

    • Make sure you add some images to each gallery.
  4. Install Code Snippets

    • SnippetsAdd New
    • Title: Custom AJAX Filter
    • Code:
    // wp_ajax_{NAME OF THE ACTION} 
    add_action('wp_ajax_customfilter', 'custom_filter_function'); 
    add_action('wp_ajax_nopriv_customfilter', 'custom_filter_function');
    
    function custom_filter_function(){
    
    	$args = array(
    		'post_type' => 'gallery',
    		'posts_per_page' => -1
    	);
    
    	// Filter on gallery-category
    	if ( isset( $_POST['gallery_category'] ) ) {
    		$args['p'] = $_POST['gallery_category'];    
    	}
    
    	$query = new WP_Query( $args );
    
    	// If there are any posts found
    	if( $query->have_posts() ) {
    
    		$images = array();
    
    		while( $query->have_posts() ) {
    			$query->the_post();
    
    			$gallery = $query->post->gallery;
    
    			if ($gallery) {
    				foreach($gallery as $image) {
    					array_push($images, $image);
    				}
    			}
    		}
    
    		// Random order on the images
    		shuffle($images);
    
    		$output = '';
    		foreach($images as $id) {
    			$src 	= wp_get_attachment_image_url( $id, 'full' );
    			$srcset = wp_get_attachment_image_srcset( $id, 'full' );
    			$sizes 	= wp_get_attachment_image_sizes( $id, 'full' );
    			
    			$output .= '<img style="min-height:500px;" class="lazy loading gallery-image" data-src="'. $src .'" data-srcset="' . $srcset . '" sizes="' . $sizes . '" />';
    		}
    		echo $output;
    
    		wp_reset_postdata();
    	} 
    	else {
    		echo 'No posts found';
    	}
    
    	die();
    }		
    • Save Changes and Activate
  5. Create a new page and add a Code Block

    PHP & HTML


    This is the HTML form where the options for the filter is and a div where the received data will be inserted. $posts gets all posts with the post type gallery. If there exists any posts, loop through them one by one while creating a label and a radio button. $gallery_size will receive the amount of images in the Advanced Custom Fields PRO gallery and add it next to the post title.

    <form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
    	<?php
    		$posts = get_posts( array( 'post_type' => 'gallery' ) );
    				
    		if ($posts) {
    			
    			echo '<div class="radio-toolbar">';
    			  
    			$output = '';
    			$gallery_total_size = 0;
    		
    			foreach($posts as $post) {   
    			
    				$gallery_size = 0;
    				if (is_array($post->gallery)) {
    					$gallery_size = sizeof($post->gallery);
    				}
    				
    				$gallery_total_size += $gallery_size;
    				
    				$input = '<input id="' . $post->post_title . '" type="radio" class="gallery-filter" name="gallery_category" value="' . $post->ID . '" />';
    				$label =  '<label for="' . $post->post_title . '" class="radio-filter">' . $post->post_title . " (" . $gallery_size . ') </label>';
    				$output .= $input . $label;
    			}
    			
    			echo '<input id="all" type="radio" class="gallery-filter" name="gallery_category" value="-1" checked />';
    			echo '<label for="all">All (' . $gallery_total_size . ')</label>';
    		
    			echo $output;
    			
    			echo '</div>';
    		}
    	?>
    	<input type="hidden" name="action" value="customfilter">
    </form>
    
    <div id="response" class="gallery" />

    You can find the complete PHP & HTML file → here

    JavaScript


    The function sendAJAX is for sending a request when the form with the id filter is submitted, it will also receive the result. Before the request is sent (beforeSend), the class fadeOut is added to the gallery images so they fade out nicely. When the data is received, a new class (fadeIn) is added. The data gets inserted to a div with the id response. The buttons gets disabled until there is new data available.

    function sendAJAX() {
    	var filter = jQuery('#filter');
    
    	jQuery.ajax({
    		url:filter.attr('action'),
    		data:filter.serialize(),
    		type:filter.attr('method'),
    		startTime:new Date().getTime(),
    		
    		beforeSend:function(xhr){
    			disableButtons();
    			jQuery('.gallery-image').addClass('fadeOut');
    		},
    		
    		success:function(data){
    			var endTime = new Date().getTime();
    			var totalTime = endTime - this.startTime;
    			var timeToWait = 0;
    			
    			if (totalTime >= 400) { 
    				timeToWait = 0; 
    			} else {
    				timeToWait = 400 - totalTime; 
    			}
    
    			setTimeout(function(){
    				jQuery('#response').html(data);
    				
    				updateObserver();
    				
    				setTimeout(function(){
    					enableButtons();
    				}, 800);
    				
    			}, timeToWait);
    			
    		}
    	});
    }

    This will call the sendAJAX function whenever a button is pressed.

    $('.gallery-filter').change(function(){
    	sendAJAX();
    	return false;
    });

    On pageload, load all images

    sendAJAX();
    return false;

    For the lazy-loading-part we make use of the intersection observer. If the element is in the viewport, the src + srcset attributes are populated and then removed from the observer-list.

    lazyImageObserver = new IntersectionObserver(function(entries, observer) {
    	entries.forEach(function(entry) {
    		if (entry.isIntersecting) {
    			let lazyImage = entry.target;
    			
    			lazyImage.src = lazyImage.dataset.src;
    			lazyImage.srcset = lazyImage.dataset.srcset;
    			lazyImage.style.minHeight = null;
    			
    			lazyImage.removeAttribute('data-src');
    			lazyImage.removeAttribute('data-srcset');
    			
    			lazyImage.classList.remove("lazy");
    			lazyImageObserver.unobserve(lazyImage);
    			
    			lazyImage.classList.add('fadeIn');
    			lazyImage.classList.remove("loading");
    		}
    	});
    });

    Here we retrieve all the images with the class lazy and adds it to the observer.

    function updateObserver() {
    	let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
    	lazyImages.forEach(function(lazyImage) {
    		lazyImageObserver.observe(lazyImage);
    	});
    } 

    Functions to disable or enable the buttons.

    function disableButtons() {
    	jQuery('.radio-toolbar label').css('opacity', '0.5');
    	jQuery('.radio-toolbar label').css('cursor', 'default');
    	
    	jQuery('.gallery-filter').attr('disabled', true);
    }
    
    function enableButtons() {
    	jQuery('.radio-toolbar label').css('opacity', '1.0');
    	jQuery('.radio-toolbar label').css('cursor', 'pointer');
    	
    	jQuery('.gallery-filter').attr('disabled', false);
    }

    You can find the complete JavaScript file → here

    CSS


    Styling for the gallery so it becomes a grid.

    .gallery {
    	width: 100%;
    
    	display: grid;
    	grid-auto-rows: 1fr;
    	grid-template-columns: repeat(auto-fit, minmax(300px,1fr));
    	grid-column-gap: 10px;
    	grid-row-gap: 10px;
    }
    
    .gallery-image {
    	width: 100%;
    	height: 100%;
    	object-fit: cover;
    }

    Animations for fading the images in and out.

    .fadeIn {
    	animation-name: fadeIn;
    	animation-duration: 0.8s;
    	animation-timing-function: ease;
    }
    
    .fadeOut {
    	animation-name: fadeOut;
    	animation-duration: 0.4s;
    	animation-timing-function: ease;
    
    	opacity: 0;
    }
    
    .loading {
    	opacity: 0;
    }
    
    @keyframes fadeOut {
    	0% {
    		opacity: 1;
    		-webkit-transform: scale(1)
    	}
    
    	1% {
    		opacity: 0.5;
    		transform: scale(0.9)
    	}
    
    	100% {
    		opacity: 0;
    		-webkit-transform: scale(0.8)
    	}
    }
    
    @keyframes fadeIn {
    	0% {
    		opacity: 0;
    		-webkit-transform: scale(1)
    	}
    
    	1% {
    		opacity: 0.2;
    		transform: scale(0.2)
    	}
    
    	100% {
    		opacity: 1;
    		-webkit-transform: scale(1)
    	}
    }

    Styling for the radio buttons to look more like actual buttons.

    .radio-toolbar {
    	width: 100%;
    
    	display: flex;
    	flex-wrap: wrap;
    	margin-bottom: 40px;
    }
    
    .radio-toolbar input[type="radio"] {
    	display: none;
    }
    
    .radio-toolbar label {
    	border-color: #ccc;
    	border-radius: 4px;
    	border-style: solid;
    	border-width: 2px;
    
    	margin: 8px 8px 0px 0;
    	padding: 4px 11px;
    
    	font-size: 16px;
    	font-family: 'Poppins';
    
    	cursor: pointer;
    }
    
    .radio-toolbar input[type="radio"]:checked+label {
    	background-color: #0069ff;
    	border-color: #0069ff;
    	color: white;
    }

    You can find the complete CSS file → here

Lightbox with Featherlight

Enqueue JavaScript & CSS by following Enqueue Scripts & CSS or place the following cdn inside the HTML part of a codeblock.

<link href="//cdn.jsdelivr.net/npm/[email protected]/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<script src="//cdn.jsdelivr.net/npm/[email protected]/release/featherlight.min.js" type="text/javascript" charset="utf-8"></script>

Inside the code-snippet, replace

$output .= '<img style="min-height:500px;" class="lazy loading gallery-image" data-src="'. $src .'" data-srcset="' . $srcset . '" sizes="' . $sizes . '" />';

with

$output .= '<img data-featherlight="image" data-featherlight-target-attr="src" style="min-height:500px;" class="lazy loading gallery-image" data-src="'. $src .'" data-srcset="' . $srcset . '" sizes="' . $sizes . '" />';

Advanced Custom Fields PRO Alternatives

  • PodsExtend Existing

    • Content Type: Post Type
    • Post Type: Galleries
  • Manage FieldsAdd Field

    • Label: Gallery
    • Name: gallery
    • Field Type: File / Image / Video
    • Additional Field Options
      • Upload Limit: Multiple Files

Inside the code-snippet, replace

$gallery = $query->post->gallery;

if ($gallery) {
	foreach($gallery as $image) {
		array_push($images, $image);
	}
}

with

$pod = pods('gallery', $query->post->ID);

$gallery = $pod->field('gallery');

if($gallery) {
	foreach ($gallery as $image) {
		array_push($images, $image['ID']);
	}
}
  • Meta BoxPost TypesNew Post Type

    • Plural name: Galleries
    • Singular name: Gallery
    • Slug: gallery
  • Meta BoxCustom FieldsAdd New

    • + Add FieldImage Advanced
      • Change ID to gallery
      • Settings → Location → Post Type → Select Gallery

Inside the code-snippet, replace

$gallery = $query->post->gallery;

if ($gallery) {
	foreach($gallery as $image) {
		array_push($images, $image);
	}
}

with

$gallery = rwmb_meta( $field_id='gallery', array(), $post_id=$query->post->ID );

if($gallery) {
	foreach ($gallery as $image) {
		array_push($images, $image['ID']);
	}
}

Inside PHP & HTML, replace

if (is_array($post->gallery)) {
	$gallery_size = sizeof($post->gallery);
}

with

$gallery = rwmb_meta( $field_id='gallery', array(), $post_id=$post->ID);

if (is_array($gallery)) {
	$gallery_size = sizeof($gallery);
}

Sources

[codex.wordpress] [rudrastyh] [weichie]