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

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
Setup Navigation Structure
Create HTML structure with data attributes to track filter selections and establish the foundation for interactive filtering.
Implement Visual Feedback
Add CSS styling to show users which filters are active, creating clear visual indication of current selections.
Program Toggle Functionality
Write JavaScript functions to handle button clicks, toggle states, and manage the All button behavior.
Getting Started
Launch your preferred code editor and ensure you have a clean workspace by closing any previously opened files.
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.
Open index.html from the Photo-Site-Navigation folder to examine the base structure we'll be enhancing.
Launch index.html in Chrome—we'll leverage Chrome DevTools throughout this exercise for debugging and testing our JavaScript implementation.
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.
Keep the browser window open—we'll be testing our progress frequently as we build out the functionality.
Development Environment Setup
Organize your workspace for efficient development
Chrome DevTools will be essential for debugging
Understanding the layout helps plan JavaScript implementation
Confirm buttons are non-functional before adding scripts
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.
Return to index.html in your code editor and locate the navigation structure around lines 13–21.
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.
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 & 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.
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.
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
| Feature | Traditional Classes | Data Attributes |
|---|---|---|
| HTML Markup | class='selected-yes' | data-selected='yes' |
| Semantic Meaning | Less clear purpose | Self-documenting |
| CSS Targeting | .selected-yes | [data-selected='yes'] |
| JavaScript Access | className checks | getAttribute method |
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.
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.
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.
Save your file and reload index.html in Chrome to test our element selection.
Open Chrome's DevTools Console using Cmd–Opt–J (Mac) or Ctrl–Shift–J (Windows) to examine our output.
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.
With our element selection confirmed, let's expand our variable caching strategy and organize our code structure:
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.
Save your changes—we're ready to move into the styling implementation phase.
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.
Open styles.css from the Photo-Site-Navigation > css directory to examine and extend the existing styling rules.
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.
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.
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'); // functionsOur first function will handle the toggle logic—the core mechanism that switches elements between selected and deselected states based on user interaction.
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.
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.
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.
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.
Save your file and test the implementation by reloading index.html in Chrome.
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
Create Toggle Function
Build a function that accepts a filterChoice parameter to handle the toggle logic for any navigation button.
Check Current State
Use getAttribute to read the current data-selected value and determine whether to select or deselect the button.
Update Attribute Value
Use setAttribute to change the data-selected value, which automatically triggers the CSS styling changes.
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.
Return to index.html in your code editor to implement our state management function.
Add the deselectOthers function above the active code section around line 164:
filterChoice.setAttribute('data-selected', 'no'); } } function deselectOthers(filterChoice) { } // active codeImplement 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.
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.
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.
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.
Save your implementation and reload index.html in Chrome for comprehensive testing.
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.
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
Filter Button Interaction Flow
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.