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

Photo Filter Website: User Friendly Navigation

Master JavaScript Photo Filtering with Interactive Navigation

Core JavaScript Concepts Covered

Data Attributes

Learn to use HTML5 custom data attributes to track user selections without cluttering your markup with unnecessary classes or IDs.

DOM Manipulation

Master querySelector methods and element manipulation to create dynamic user interfaces that respond to user interactions.

Event Handling

Implement click handlers and toggle functionality to create smooth, interactive navigation experiences for users.

Topics Covered in This JavaScript & jQuery Tutorial:

Using Data Attributes to Track a User's Selection, Creating Variables to Store Navigation Items, Styling the Selected Buttons, Toggling the Filter Buttons, Refining the Filter Buttons

Exercise Preview

ex prev nav selectors

Exercise Overview

In this comprehensive exercise series, we'll build a sophisticated filtering system for a photographer's portfolio website. This isn't just another basic tutorial—we're creating production-ready functionality that mirrors what you'll find on modern gallery sites and e-commerce platforms. The photographer's collection includes both color and black-and-white images across three categories: animals, buildings, and trees.

Our filtering system will allow users to intuitively navigate the collection by selecting multiple categories simultaneously, with clear visual feedback for their selections. This exercise focuses specifically on the user interface mechanics—the visual selection states and interaction patterns that create a polished user experience. We'll implement the actual photo filtering logic in the following exercise, giving you a methodical approach to building complex interactive features.

Photo Gallery Filter Development Process

1

Setup Navigation Structure

Create HTML structure with data attributes to track filter selections and establish the foundation for interactive filtering.

2

Implement Visual Feedback

Add CSS styling to show users which filters are active, creating clear visual indication of current selections.

3

Program Toggle Functionality

Write JavaScript functions to handle button clicks, toggle states, and manage the All button behavior.

Getting Started

  1. Launch your preferred code editor and ensure you have a clean workspace by closing any previously opened files.

  2. Navigate to the Photo-Site-Navigation folder located in Desktop > Class Files > yourname-JavaScript jQuery Class. If you're using a modern editor like Visual Studio Code, consider opening the entire folder as a workspace for better project navigation.

  3. Open index.html from the Photo-Site-Navigation folder to examine the base structure we'll be enhancing.

  4. Launch index.html in Chrome—we'll leverage Chrome DevTools throughout this exercise for debugging and testing our JavaScript implementation.

  5. Take a moment to scroll through the complete photo gallery and familiarize yourself with the layout. Notice the navigation elements at the top of the page—these filter buttons currently serve no function beyond basic HTML linking. This static state represents our starting point.

    Our objective for this exercise is transforming these static elements into an intelligent interface that provides immediate visual feedback when users interact with filter options. The "All" button will serve as a master control, visually deselecting other options when activated. This foundational interaction layer sets the stage for the filtering functionality we'll implement in subsequent exercises.

  6. Keep the browser window open—we'll be testing our progress frequently as we build out the functionality.

Development Environment Setup

0/4

Using Data Attributes to Track User Selection

HTML5 introduced data attributes as a powerful mechanism for storing custom information directly within DOM elements. This approach offers significant advantages over creating semantic-free classes or IDs, providing a clean separation between styling hooks and functional data storage. Data attributes follow the data-* naming convention, where the asterisk represents your custom identifier.

This pattern has become a standard practice in modern web development, offering both performance benefits and improved code maintainability. Let's implement this approach to track our filter states.

  1. Return to index.html in your code editor and locate the navigation structure around lines 13–21.

  2. Examine the current href attributes—you'll notice they're set to JavaScript:; URLs. This is a deliberate choice for this exercise, as we're focusing on JavaScript-driven interactions rather than traditional page navigation. In production applications, you might implement this as a progressive enhancement over functional URLs.

  3. Now we'll implement our selection tracking system using data attributes. Add the following code to establish the initial state:

    <ul>
       <li><a data-selected="yes" href="javascript:;">All</a></li>
       <li><a data-selected="no" href="javascript:;">Animals</a></li>
       <li><a data-selected="no" href="javascript:;">Buildings</a></li>
       <li><a data-selected="no" href="javascript:;">Trees</a></li>
       <li><a data-selected="no" href="javascript:;">Black &amp; White</a></li>
    </ul>

    This configuration reflects the default state where all photos are visible, making "All" the logical default selection. This follows standard UX patterns found in professional gallery and e-commerce filtering systems.

  4. The "All" button requires special handling due to its role as a master control. Let's add a unique identifier to streamline our JavaScript targeting:

    <li><a id="all-button" data-selected="yes" href="javascript:;">All</a></li>

    This ID provides direct access to this critical element, improving both performance and code readability.

HTML5 Data Attributes Best Practice

Data attributes starting with 'data-' provide a clean way to store custom information without creating meaningless classes or IDs. They're perfect for tracking state in interactive elements.

Traditional vs Data Attribute Approach

FeatureTraditional ClassesData Attributes
HTML Markupclass='selected-yes'data-selected='yes'
Semantic MeaningLess clear purposeSelf-documenting
CSS Targeting.selected-yes[data-selected='yes']
JavaScript AccessclassName checksgetAttribute method
Recommended: Data attributes provide cleaner, more semantic markup for tracking element states

Creating Variables to Store the Navigation Items

Effective JavaScript development requires strategic element caching to avoid repeated DOM queries, which can impact performance in complex applications. We'll establish our variable references and set up the foundational event handling structure.

  1. Navigate to the end of your HTML document and add our JavaScript container with proper event handling. Insert this code around line 146, after the final closing </div> tag:

    </div>
    
    <script>
    
       window.onload = function() {
    
       };
    
    </script>

    The window.onload event ensures all DOM elements and assets are fully loaded before our script executes, preventing common timing-related issues in JavaScript applications.

  2. Now we'll cache our navigation elements using querySelectorAll(), which accepts standard CSS selector syntax and returns a NodeList. This approach provides flexibility and maintains consistency with CSS targeting patterns:

    window.onload = function() {
    
       var filterNav = document.querySelectorAll('nav a');
       console.log(filterNav);
    
    };

    The console.log statement serves as immediate verification that our selector is working correctly—a crucial debugging step.

  3. Save your file and reload index.html in Chrome to test our element selection.

  4. Open Chrome's DevTools Console using Cmd–Opt–J (Mac) or Ctrl–Shift–J (Windows) to examine our output.

  5. Expand the logged NodeList by clicking the arrow indicator. You should see all five anchor elements listed, confirming our selector is capturing the correct elements. This verification step prevents downstream issues and is a best practice in JavaScript development.

  6. With our element selection confirmed, let's expand our variable caching strategy and organize our code structure:

  7. Replace the console.log test code with a more comprehensive setup around lines 150-152:

    window.onload = function() {
    
       // grabbing elements
       var filterNav = document.querySelectorAll('nav a');
       var allButton = document.getElementById('all-button');
    
    };

    This organizational approach with descriptive comments becomes invaluable as your JavaScript applications grow in complexity. The separate variable for the all-button provides quick access to this frequently referenced element.

  8. Save your changes—we're ready to move into the styling implementation phase.

QuerySelectorAll vs GetElementsByTagName

QuerySelectorAll() allows you to use familiar CSS selector syntax and returns a NodeList, making it more versatile than traditional DOM selection methods.

Styling the Selected Buttons

Creating intuitive visual feedback requires leveraging CSS attribute selectors in conjunction with our data attributes. This approach maintains clean separation between behavior and presentation while providing the dynamic styling capabilities our interface requires.

  1. Open styles.css from the Photo-Site-Navigation > css directory to examine and extend the existing styling rules.

  2. Locate the nav a:hover rule around line 33. We'll extend this existing styling to create consistent visual treatment for both hover states and selected states:

    nav a:hover, 
    nav a[data-selected="yes"] {
       color: #fff;
       background: #e07360;
    }

    This CSS attribute selector [data-selected="yes"] demonstrates the power of data attributes in creating dynamic styling. The selector will automatically apply our highlight styling whenever the data-selected value changes to "yes", regardless of how that change occurs in our JavaScript.

  3. Save the CSS file and reload index.html in Chrome. You should immediately see the "All" tab highlighted with the red background and white text, demonstrating that our CSS selector is correctly interpreting the data-selected="yes" attribute we set earlier.

This immediate visual confirmation validates our approach and provides the foundation for the dynamic interactions we'll implement next.

CSS Attribute Selector Implementation

Hover State Enhancement

Combine existing hover styles with data attribute selectors to create consistent visual feedback across different interaction states.

Attribute-Based Styling

Use CSS attribute selectors to style elements based on their data-selected values, creating automatic visual updates when JavaScript changes attributes.

Toggling the Filter Buttons

Now we'll implement the core interaction logic that transforms our static navigation into a dynamic filtering interface. Breaking complex functionality into focused, single-responsibility functions is a fundamental principle of maintainable JavaScript development.

  1. Return to index.html in your code editor and enhance our code organization with a functions section around line 154:

    var allButton = document.getElementById('all-button');
    
    // functions

    Our first function will handle the toggle logic—the core mechanism that switches elements between selected and deselected states based on user interaction.

  2. Implement the toggleCategory function with a parameter to accept the clicked element:

    // functions
       function toggleCategory(filterChoice) {
    
       }
    };

    The parameter pattern allows this function to work with any filter button, making our code both reusable and scalable. This design principle becomes increasingly valuable as applications grow in complexity.

  3. Add the conditional logic that examines and updates the data-selected attribute:

    // functions
    function toggleCategory(filterChoice) {
       if(filterChoice.getAttribute('data-selected') == 'no') {
           filterChoice.setAttribute('data-selected', 'yes');
       } else {
           filterChoice.setAttribute('data-selected', 'no');
       }
    }

    This toggle mechanism checks the current state and switches to the opposite state—a reliable pattern for binary state management. The CSS rule we created earlier will automatically apply visual styling based on these attribute changes.

  4. Now we need to connect our function to user interactions through event handling. Add the following code around line 163 to set up our event loop:

    filterChoice.setAttribute('data-selected', 'no');
          }
       }
    
       // active code
       for(var i = 0; i < filterNav.length; i++) {
    
       }
    };

    This organizational comment helps delineate between function definitions and the code that actually executes when the page loads.

  5. Within the loop, we'll attach click event handlers to each navigation element:

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

    The this keyword is crucial here—it refers to the specific element that was clicked, allowing our generic function to operate on the correct DOM element. This pattern demonstrates how JavaScript's context system enables flexible, reusable code.

  6. Save your file and test the implementation by reloading index.html in Chrome.

  7. Click various navigation buttons to verify the toggle functionality. Each button should highlight when selected and return to the default state when clicked again. This basic interaction forms the foundation for more sophisticated filtering behavior.

Toggle Function Implementation

1

Create Toggle Function

Build a function that accepts a filterChoice parameter to handle the toggle logic for any navigation button.

2

Check Current State

Use getAttribute to read the current data-selected value and determine whether to select or deselect the button.

3

Update Attribute Value

Use setAttribute to change the data-selected value, which automatically triggers the CSS styling changes.

Function Parameter Best Practice

Using 'this' as an argument when calling toggleCategory ensures the function receives the correct DOM element that triggered the click event.

Refining the Filter Buttons

Professional filtering interfaces require intelligent state management where related controls influence each other logically. The "All" button should function as a master control, while individual category selections should automatically deselect the "All" option to maintain interface consistency.

  1. Return to index.html in your code editor to implement our state management function.

  2. Add the deselectOthers function above the active code section around line 164:

    filterChoice.setAttribute('data-selected', 'no');
       }
    }
    
    function deselectOthers(filterChoice) {
    
    }
    
    // active code
  3. Implement the logic to detect when the "All" button is being activated:

    function deselectOthers(filterChoice) {
       if(filterChoice == allButton) {
    
       }
    }

    This comparison uses our cached allButton reference to efficiently identify the master control interaction.

  4. When "All" is selected, we need to deselect all category-specific filters. We'll start our loop at index 1 to skip the "All" button itself:

    if(filterChoice == allButton) {
       for(var i = 1; i < filterNav.length; i++) {
          filterNav[i].setAttribute('data-selected', 'no');
       }
    }

    This approach demonstrates efficient array manipulation—by understanding our data structure, we can avoid unnecessary conditional checks within the loop.

  5. Complete the function by handling the inverse relationship—when any specific category is selected, "All" should automatically deselect:

    function deselectOthers(filterChoice) {
       if(filterChoice == allButton) {
          for(var i = 1; i < filterNav.length; i++) {
             filterNav[i].setAttribute('data-selected', 'no');
          }
       } else {
          allButton.setAttribute('data-selected', 'no');
       }
    }

    This mutual exclusivity logic creates an intuitive user experience that matches expectations from professional web applications.

  6. Integrate this function into our existing toggle logic by calling it at the appropriate moment—when a filter is being activated (changed from 'no' to 'yes'). Update the toggleCategory function around line 154:

    // functions
    function toggleCategory(filterChoice) {
       if(filterChoice.getAttribute('data-selected') == 'no') {
          deselectOthers(filterChoice);
          filterChoice.setAttribute('data-selected', 'yes');
       } else {

    By calling deselectOthers before changing the current selection to 'yes', we ensure clean state transitions and prevent conflicting selections.

  7. Save your implementation and reload index.html in Chrome for comprehensive testing.

  8. Verify the interaction patterns: "All" should be selected by default. Clicking any other button should activate that selection while deactivating "All". Multiple category buttons can be selected simultaneously, but selecting "All" should clear all other selections.

  9. Test edge cases by clicking various combinations to ensure the state management behaves predictably under different user interaction patterns.

    Excellent! You've successfully implemented a sophisticated selection interface that mirrors the interaction patterns found in professional web applications. This foundational system provides the user feedback and state management necessary for the photo filtering functionality we'll implement in the next exercise.

    For reference, you can examine the complete implementation in Desktop > Class Files > yourname-JavaScript jQuery Class > Done-Files > Photo-Site-Navigation.

All Button Logic Implementation

Pros
Clear visual hierarchy when All is selected
Prevents conflicting filter selections
Intuitive user experience with mutual exclusion
Automatic deselection prevents user confusion
Cons
Additional complexity in function logic
Requires careful loop index management
Need to track All button separately from others

Filter Button Interaction Flow

Click All Button
4
Deselect Other Filters
4
Click Category Filter
2
Deselect All Button
1
Exercise Completion

Navigation system successfully implements toggle functionality with proper All button behavior. Users can now see visual feedback for their filter selections before actual photo filtering is implemented.

Key Takeaways

1HTML5 data attributes provide a clean, semantic way to track element states without cluttering markup with unnecessary classes
2QuerySelectorAll method enables CSS selector syntax in JavaScript, making DOM element selection more intuitive and powerful
3Combining CSS attribute selectors with JavaScript setAttribute creates automatic visual updates when element states change
4Window.onload event handler ensures all DOM elements are fully loaded before JavaScript manipulation begins
5Toggle functions with conditional logic allow elements to switch between states based on current attribute values
6Loop index manipulation (starting at 1 instead of 0) provides precise control over which elements are affected by functions
7Proper function parameter usage with 'this' keyword ensures correct element targeting in event handlers
8Strategic function placement within conditional blocks optimizes performance by only executing code when state changes occur

RELATED ARTICLES