Skip to main content
April 1, 2026Dan Rodney/12 min read

Photo Filter Website: Getting the Photos to Filter

Master JavaScript Photo Filtering with jQuery Integration

Core JavaScript Concepts Covered

Data Attributes

Learn to use HTML5 data attributes to store filter categories and selection states for dynamic content management.

Array Manipulation

Master JavaScript array operations including push() method and dynamic array population based on user interactions.

DOM Manipulation

Control element visibility and styling through JavaScript by dynamically showing and hiding photo containers.

Topics Covered in This JavaScript & jQuery Tutorial:

Setting up Data Attributes for Photo Categories, Creating an Array to Store the Filter Choices, Getting the All Button to Show All Filter Values, Getting the Rest of the Buttons to Select the Other Filters, Initially Hiding the Photos, Getting the Photos to Filter, Optional Bonus: Fixing a Usability Issue

Exercise Preview

ex prev gallery inclusive filter

Before You Begin

This tutorial builds on previous photo gallery work. Ensure you have the Photo-Site-Filter folder from the previous exercise and Chrome browser for DevTools testing.

Exercise Overview

In this exercise, we'll advance the functionality of our photo gallery website by implementing dynamic filtering capabilities. Building on the previous exercise where we programmed visual feedback for filter button selection, we'll now create the core logic that actually filters the displayed photos based on user interaction. This is a fundamental pattern you'll encounter frequently in modern web development—transforming visual interface elements into functional, interactive components that manipulate content in real-time.

Getting Started

Before diving into the JavaScript logic, let's ensure our development environment is properly configured for this more complex functionality.

  1. Open your code editor if it isn't already open.

  2. Close any files you may have open to maintain focus on this specific exercise.

  3. For this exercise we'll be working with the Photo-Site-Filter folder located in Desktop > Class Files > yourname-JavaScript jQuery Class. You may want to open that folder in your code editor if it allows you to (like Visual Studio Code does).

  4. Open index.html from the Photo-Site-Filter folder.

  5. Preview index.html in Chrome (we'll be using its DevTools extensively for debugging and testing).

  6. Click the links in the navigation at the top to see that they only provide visual feedback. When users click on one or more categories, we want the photos in the main area of the page to dynamically filter based on their selections—this is the core functionality we'll be implementing.

  7. Leave the page open in Chrome so we can test our implementation in real-time as we build it.

Setup Process

1

Open Project Files

Navigate to Desktop > Class Files > yourname-JavaScript jQuery Class and open the Photo-Site-Filter folder in your code editor.

2

Preview in Browser

Open index.html in Chrome to test the current functionality and prepare for DevTools usage during development.

3

Test Current State

Click navigation links to confirm they only appear functional without actually filtering photos in the main gallery area.

Setting up Data Attributes for Photo Categories

Data attributes are the foundation of modern, semantic filtering systems. They allow us to embed machine-readable metadata directly into our HTML elements, creating a clean separation between content structure and JavaScript functionality. This approach is particularly valuable in content management systems and dynamic applications where categories might change frequently.

  1. Go back to index.html in your code editor.

  2. Let's examine how the images have been categorized in the markup. Starting around line 24, notice that each image is wrapped with a div tag containing one to four classes that assign it to various categories. These classes serve as our filtering criteria and demonstrate a scalable approach to content categorization that works well even as your gallery grows.

  3. We'll add data attributes to the navigation items to store and access information about photo categories. This creates a robust connection between user interface elements and the content they control. Add the following bold code:

    <ul>
       <li><a data-filter="all" id="all-button" data-selected="yes"  href="JavaScript:;">All</a></li>
       <li><a data-filter="animals" data-selected="no" href="javascript:;">Animals</a></li>
       <li><a data-filter="buildings" data-selected="no" href="javascript:;">Buildings</a></li>
       <li><a data-filter="trees" data-selected="no" href="javascript:;">Trees</a></li>
       <li><a data-filter="bw" data-selected="no" href="javascript:;">Black &amp; White</a></li>
    </ul>

    NOTE: The values we've added to the data-attributes here directly correspond to the classes assigned to the image containers, creating a systematic relationship between filters and content.

Filter Categories and Data Values

All Photos
1
Animals
1
Buildings
1
Trees
1
Black & White
1
Data Attribute Best Practice

The data-filter values match the CSS classes assigned to image containers, creating a direct relationship between navigation and content filtering.

Creating an Array to Store the Filter Choices

Professional JavaScript development requires breaking complex functionality into manageable, testable components. The filtering system we're building demonstrates this principle perfectly—we'll create modular functions that each handle a specific aspect of the filtering process. This approach not only makes debugging easier but also creates reusable code patterns you can apply to other projects.

Our first component captures user filter choices and stores them in an array, which serves as the single source of truth for determining which photos to display. This pattern—maintaining application state in a centralized data structure—is fundamental to modern JavaScript frameworks like React and Vue.

  1. Switch back to your code editor.

  2. In the active code section that starts around line 174, near the end of the for loop, add the following bold code to call the populateArray() function we'll implement next:

    // active code
    for(var i = 0; i < filterNav.length; i++) {
       filterNav[i].onclick = function() {
          toggleCategory(this);
          populateArray();
       }
    }
  3. In the grabbing elements section that starts around line 150, declare an empty array that will be populated dynamically as users make filter selections:

    // grabbing elements
    var filterNav = document.querySelectorAll('nav a');
    var allButton = document.getElementById('all-button');
    var selectedArray = [];
  4. Now let's create the function that will manage our array state. At the end of the functions section, add the following structure (around line 175):

    function populateArray() {
    
    }
    
    // active code
  5. To prevent duplicate entries when users change their selections, we need to reset the array on each function call. This ensures our state remains clean and predictable:

    function populateArray() {
       selectedArray = [];
    }

Array Implementation Strategy

1

Function Call Setup

Add populateArray() function call to the existing click event handlers in the for loop around line 174.

2

Array Declaration

Declare empty selectedArray variable in the grabbing elements section to store user filter choices.

3

Array Reset Logic

Clear the array at the start of populateArray() function to prevent data duplication on subsequent clicks.

Getting the All Button to Show All Filter Values

The "All" button presents a unique challenge in filter logic: it doesn't correspond to a specific category class but should display all available categories. This requires special handling that demonstrates conditional logic patterns you'll use frequently in interactive applications.

  1. Examine the navigation markup starting around line 15 to understand the data-filter structure:

    Category Data-Filter Value
    All "all"
    Animals "animals"
    Buildings "buildings"
    Trees "trees"
    Black & White "bw"

    Notice that all data-filter values except "all" directly match classes in the gallery markup. When users select "All," we need to programmatically include all other category values (animals, buildings, trees, and bw) in our filter array. For specific category selections, we only include the actively chosen filters.

  2. Around line 177, implement the conditional logic for handling both scenarios:

    function populateArray() {
       selectedArray = [];
       if(allButton.getAttribute('data-selected') == 'yes') {
          // cycle through every data filter except for 'all'
       } else {
          // get only the active filter choices
       }
    }
  3. For the "All" scenario, we'll iterate through the filterNav array starting at index 1 to skip the "All" button itself:

    if(allButton.getAttribute('data-selected') == 'yes') {
       // cycle through every data filter except for 'all'
       for(var i = 1; i < filterNav.length; i++) {
    
       }
    } else {
       // get only the active filter choices
    }
  4. Extract each navigation item's data-filter value and add it to our selectedArray using the push() method:

    if(allButton.getAttribute('data-selected') == 'yes') {
       // cycle through every data filter except for 'all'
       for(var i = 1; i < filterNav.length; i++) {
          selectedArray.push(filterNav[i].getAttribute('data-filter'));
       }
    } else {
  5. Add a console.log() statement to verify our array population is working correctly:

    for(var i = 1; i < filterNav.length; i++) {
       selectedArray.push(filterNav[i].getAttribute('data-filter'));
    }
       console.log(selectedArray);
    } else {
  6. Save the file and reload index.html in Chrome.

  7. Open the Console using Cmd–Opt–J (Mac) or Ctrl–Shift–J (Windows).

  8. Click the All button to deselect it, then click it again to select it.

    The Console should display ["animals", "buildings", "trees", "bw"]. This confirms that all category values are being correctly captured and stored in our selectedArray.

  9. Keep the Console open for continued testing as we implement the remaining functionality.

All Button vs Specific Filter Logic

FeatureAll Button SelectedSpecific Filters Selected
Array PopulationCycles through all categoriesOnly active selections
Loop Start IndexStarts at index 1Starts at index 1
Selection CriteriaAll non-'all' filtersdata-selected='yes' only
Result Array[animals, buildings, trees, bw]User's active choices
Recommended: Use conditional logic to handle these two distinct filtering scenarios.

Getting the Rest of the Buttons to Select the Other Filters

Now we'll implement the logic for individual category selection, which requires checking each navigation item's selection state and only including active choices in our array.

  1. Switch back to your code editor.

    The else condition handles cases where users select specific categories instead of "All." We need to iterate through all filter options but only include those marked as selected.

  2. In the else statement, implement the selective filtering logic (around line 185):

    } else {
       // get only the active filter choices
       for(var i = 1; i < filterNav.length; i++) {
          if(filterNav[i].getAttribute('data-selected') == 'yes') {
             selectedArray.push(filterNav[i].getAttribute('data-filter'));
          }
       }
    }
  3. Move the console.log() statement outside the conditional blocks so it executes regardless of which path is taken (around line 190):

    for(var i = 1; i < filterNav.length; i++) {
             selectedArray.push(filterNav[i].getAttribute('data-filter'));
          }
       } else {
          // get only the active filter choices
          for(var i = 1; i < filterNav.length; i++) {
             if(filterNav[i].getAttribute('data-selected') == 'yes') {
                selectedArray.push(filterNav[i].getAttribute('data-filter'));
             }
          }
       }
       console.log(selectedArray);
    }
  4. Save the file, reload index.html in Chrome, and ensure the Console is visible.

  5. Click the Animals button. The Console should display: ["animals"]

  6. Click the Buildings category to add it to the selection: ["animals", "buildings"]

  7. Click Animals again to deselect it. Only "buildings" should remain. This confirms our array management is working correctly for both adding and removing selections.

Testing Console Output

Use Chrome DevTools Console (Cmd-Opt-J on Mac, Ctrl-Shift-J on Windows) to verify array population. Expected outputs: single categories show ['animals'] and multiple selections show ['animals', 'buildings'].

Initially Hiding the Photos

The next phase implements the visual filtering mechanism. We'll use a "hide-then-show" approach: first hiding all photos, then selectively displaying only those matching the current filter criteria. This technique ensures clean transitions and prevents visual artifacts that can occur with more complex show/hide logic.

  1. Return to index.html in your code editor.

  2. Remove the console.log() statement around line 190, as we no longer need it for testing.

  3. Around line 192, create a utility function for hiding all images:

    function hideAllPics() {
    
    }
    
    // active code
  4. We need to capture all image containers for manipulation. Add this to your variable declarations (around line 154):

    // grabbing elements
    var filterNav = document.querySelectorAll('nav a');
    var allButton = document.getElementById('all-button');
    var selectedArray = [];
    var imageContainers = document.querySelectorAll('.gallery div');
  5. Implement the hiding functionality by setting display to 'none' for all containers (around line 194):

    }
    
    function hideAllPics() {
       for(var i = 0; i < imageContainers.length; i++) {
          imageContainers[i].style.display = 'none';
       }
    }
    
    // active code
  6. Test the function by temporarily calling it in the active code section (around line 204):

    // active code
    for(var i = 0; i < filterNav.length; i++) {
       filterNav[i].onclick = function() {
          toggleCategory(this);
          populateArray();
          hideAllPics();
       }
    }
  7. Save the file.

  8. Switch to Chrome and reload index.html.

  9. Click any navigation button—all images should disappear, confirming our hiding mechanism works. Now we can implement the selective display functionality that will show only filtered content.

Photo Filtering Strategy

Hide All First

Set all image containers to display: none initially to create a clean slate for filtering operations.

Inline-Block Display

Use inline-block display property to maintain layout structure and prevent container collapsing issues.

Implementation Steps

1

Variable Declaration

Create imageContainers variable using querySelectorAll('.gallery div') to access all photo containers.

2

Hide Function

Write hideAllPics() function that loops through containers and sets display to 'none' for each element.

3

Test Functionality

Temporarily call hideAllPics() in click handlers to verify all images disappear when any button is clicked.

Getting the Photos to Filter

This is where our filtering system comes together. We'll create a function that combines the hiding mechanism with selective display logic, using our populated array to determine which photos should be visible.

  1. Switch back to your code editor.

  2. Remove the temporary hideAllPics(); call around line 204—we'll integrate this into our main filtering function.

  3. Create the master filtering function around line 199:

    function hideAllPics() {
       for(var i = 0; i < imageContainers.length; i++) {
          imageContainers[i].style.display = 'none';
       }
    }
    
    function filterPhotos() {
       hideAllPics();
    }
    
    // active code
  4. Add logic to iterate through our selectedArray and process each filter category:

    function filterPhotos() {
       hideAllPics();
       for(var i = 0; i < selectedArray.length; i++) {
    
       }
    }
  5. For each category in the array, query the DOM for elements with matching classes and store them in a group variable:

    function filterPhotos() {
       var group;
       hideAllPics();
       for(var i = 0; i < selectedArray.length; i++) {
          group = document.querySelectorAll('.' + selectedArray[i]);
       }
    }
  6. Add an inner loop to display all elements in each selected group. Note the use of 'j' as the inner loop variable to avoid conflicts:

    for(var i = 0; i < selectedArray.length; i++) {
       group = document.querySelectorAll('.' + selectedArray[i]);
       for(var j = 0; j < group.length; j++) {
          group[j].style.display = 'inline-block';
       }
    }

    NOTE: When nesting loops, always use different variable names for each level. The convention is to use i for the outer loop and j for the inner loop, though any descriptive names work.

  7. Integrate the filtering function into our event handling system (around line 216):

    // active code
    for(var i = 0; i < filterNav.length; i++) {
       filterNav[i].onclick = function() {
          toggleCategory(this);
          populateArray();
          filterPhotos();
       }
    }
  8. Save the file.

  9. Return to Chrome and reload index.html.

  10. Test the Black & White filter—only black and white photos should display.

  11. Click Animals to add it to the selection—both black & white photos and animal photos should now be visible.

  12. Experiment with different filter combinations to verify the system works correctly.

    The complete filtering process works as follows: when a user toggles a filter, the system updates the selected categories array, hides all photos, queries for elements matching each selected category, and displays those matching elements. This creates a smooth, predictable filtering experience.

Photo Filtering Process

1

Hide All Photos

Call hideAllPics() function to set all image containers to display: none as the starting point.

2

Loop Through Selected Array

Iterate through selectedArray to process each active filter category the user has chosen.

3

Query Selected Classes

Use document.querySelectorAll('.' + selectedArray[i]) to find all elements matching the current filter class.

4

Show Matching Photos

Set matching image containers to display: inline-block using nested loop with variable j to avoid conflicts.

Nested Loop Variables

When writing loops within loops, use different variables (i for outer loop, j for inner loop) to prevent variable conflicts and ensure proper functionality.

Optional Bonus: Fixing a Usability Issue

Professional applications require robust edge case handling. One common usability issue in filtering systems occurs when users deselect all categories, leaving them with an empty screen. We'll implement graceful fallback behavior that automatically shows all content when no specific filters are active.

  1. Switch back to your code editor.

  2. Create a function to handle the empty selection scenario around line 199:

    function hideAllPics() {
       for(var i = 0; i < imageContainers.length; i++) {
          imageContainers[i].style.display = 'none';
       }
    }
    
    function noFilterSelection() {
       if(selectedArray == 0) {
    
       }
    }
  3. When no categories are selected, display all photos as a logical default state:

    function noFilterSelection() {
       if(selectedArray == 0) {
          for(var i = 0; i < imageContainers.length; i++) {
             imageContainers[i].style.display = 'inline-block';
          }
       }
    }
  4. Integrate this fallback behavior into the main filtering function:

    function filterPhotos() {
       hideAllPics();
       for(var i = 0; i < selectedArray.length; i++) {
          group = document.querySelectorAll('.' + selectedArray[i]);
          for(var j = 0; j < group.length; j++) {
            group[j].style.display = 'inline-block';
          }
        }
        noFilterSelection();
      }
  5. Save the file.

  6. Test in Chrome by deselecting all filters—all photos should remain visible, providing a better user experience.

  7. For complete UX consistency, we should also update the visual interface to reflect this "show all" state. Return to your code editor.

  8. Enhance the noFilterSelection() function to automatically select the "All" button when no specific filters are active:

    function noFilterSelection() {
       if(selectedArray == 0) {
          for(var i = 0; i < imageContainers.length; i++) {
             imageContainers[i].style.display = 'inline-block';
          }
          allButt

Empty Array Handling

Pros
Prevents blank gallery when all filters are deselected
Automatically resets to show all photos for better user experience
Visually indicates All button selection when array is empty
Provides intuitive fallback behavior for edge cases
Cons
Adds complexity to the filtering logic
Requires additional function and array length checking

Usability Fix Implementation

1

Empty Array Detection

Create noFilterSelection() function that checks if selectedArray length equals 0 to detect when no filters are active.

2

Show All Photos

Loop through imageContainers and set display to inline-block when array is empty to show complete gallery.

3

Update All Button State

Set allButton data-selected attribute to 'yes' to provide visual feedback that all photos are now showing.

Key Takeaways

1Data attributes provide a clean way to store filter categories and selection states directly in HTML elements, creating a direct relationship between navigation and content
2JavaScript arrays combined with the push() method enable dynamic storage and management of user filter selections for complex filtering operations
3Conditional logic separating 'All' button behavior from specific filter selections allows for comprehensive photo gallery functionality with different user interaction patterns
4DOM manipulation through display property changes (none vs inline-block) provides smooth photo filtering while maintaining layout structure and preventing container collapse
5Nested loops require different variable names (i and j) to prevent conflicts when iterating through multiple data sets simultaneously in complex filtering operations
6Console.log debugging in Chrome DevTools is essential for testing array population and verifying that filter selection logic works correctly during development
7Edge case handling for empty filter arrays improves user experience by automatically showing all photos and updating visual indicators when no specific filters are selected
8The combination of hiding all photos first, then showing selected categories creates a reliable filtering pattern that can be applied to various content management scenarios

RELATED ARTICLES