Skip to main content

Filters Module

The Filters module manages the restaurant filter buttons that allow users to quickly filter reviews by restaurant. It dynamically generates filter buttons based on available restaurants and coordinates with the UI module to update the displayed reviews.

Overview

The Filters module is responsible for:
  • Generating filter buttons for each unique restaurant
  • Tracking the currently active filter
  • Applying visual styles to the active filter button
  • Triggering UI updates when filter changes
Source: src/js/modules/filters.js

Public Properties

currentFilter

Tracks the currently active filter selection. Type: string Default: 'all' Values:
  • 'all' - Show all reviews (no filtering)
  • 'Restaurant Name' - Show only reviews from this restaurant
Usage:
// Check current filter
if (Filters.currentFilter === 'all') {
  console.log('Showing all reviews');
} else {
  console.log(`Filtering by: ${Filters.currentFilter}`);
}

Public Methods

init()

Initializes the filters module and generates initial filter buttons.
none
void
This method takes no parameters
Returns: void Usage:
// From src/js/app.js or ui.js
import { Filters } from './modules/filters.js';

Filters.init();
Behavior:
  • Calls update() to generate filter buttons
  • Should be called after DataStore is initialized

update()

Regenerates filter buttons based on the current list of restaurants in DataStore. Returns: void Usage:
// From src/js/modules/filters.js:12
update() {
  const restaurants = DataStore.getRestaurants();
  const filtersContainer = document.getElementById('restaurantFilters');

  if (!filtersContainer) return;

  filtersContainer.innerHTML = restaurants.map(restaurant =>
    `<button data-filter="${Utils.escapeHtml(restaurant)}" class="filter-btn px-4 py-2 rounded-full text-sm font-medium transition-all duration-200">
      ${Utils.escapeHtml(restaurant)}
    </button>`
  ).join('');

  document.querySelectorAll('.filter-btn').forEach(btn => {
    btn.addEventListener('click', (e) => this.filterByRestaurant(e.target.dataset.filter));
  });
}
Behavior:
  • Gets unique restaurant names from DataStore
  • Generates HTML button for each restaurant
  • Escapes restaurant names to prevent XSS
  • Attaches click event listeners to all filter buttons
  • Should be called when reviews are added/updated/deleted
When to call:
  • During initialization
  • After adding a new review with a new restaurant
  • After deleting all reviews from a restaurant
  • When reviews update from Firebase real-time listener

filterByRestaurant(restaurant)

Filters reviews by the specified restaurant and updates the UI.
restaurant
string
required
The restaurant name to filter by, or ‘all’ to show all reviews
Returns: void Usage:
// From src/js/modules/filters.js:29
filterByRestaurant(restaurant) {
  this.currentFilter = restaurant;

  // Update button styles
  document.querySelectorAll('.filter-btn').forEach(btn => {
    btn.classList.remove('active');
  });

  const activeBtn = document.querySelector(`[data-filter="${restaurant}"]`);
  if (activeBtn) {
    activeBtn.classList.add('active');
  }

  // Trigger UI update
  UI.renderReviews();
}
Behavior:
  1. Updates currentFilter property
  2. Removes active class from all filter buttons
  3. Adds active class to the clicked button
  4. Calls UI.renderReviews() to update the review grid
Example:
// Filter to show only "El Emperador" reviews
Filters.filterByRestaurant('El Emperador');

// Show all reviews
Filters.filterByRestaurant('all');

HTML Structure

The filters module requires this HTML structure:
<div class="flex flex-wrap gap-2">
  <!-- "All" button is always present in the HTML -->
  <button data-filter="all" class="filter-btn active px-4 py-2 rounded-full text-sm font-medium transition-all duration-200">
    Todos
  </button>
  
  <!-- Dynamic restaurant filters generated here -->
  <div id="restaurantFilters" class="flex flex-wrap gap-2">
    <!-- Generated by Filters.update() -->
  </div>
</div>

Generated Filter Button

Each filter button is generated as:
<button 
  data-filter="Restaurant Name" 
  class="filter-btn px-4 py-2 rounded-full text-sm font-medium transition-all duration-200"
>
  Restaurant Name
</button>
When active, the active class is added:
<button 
  data-filter="El Emperador" 
  class="filter-btn active px-4 py-2 rounded-full text-sm font-medium transition-all duration-200"
>
  El Emperador
</button>

Styling

Filter buttons use Tailwind CSS classes:
  • filter-btn - Custom class for targeting all filter buttons
  • active - Applied to currently selected filter
  • px-4 py-2 - Padding
  • rounded-full - Fully rounded corners (pill shape)
  • text-sm font-medium - Typography
  • transition-all duration-200 - Smooth transitions
Define .filter-btn and .filter-btn.active styles in your CSS file to customize the appearance of filter buttons.

Integration with UI Module

The Filters module works closely with the UI module:
// In UI.renderReviews() - src/js/modules/ui.js:48
if (Filters.currentFilter !== 'all') {
  reviews = reviews.filter(r => r.restaurant === Filters.currentFilter);
}
The UI module checks Filters.currentFilter and filters the reviews array before rendering.

Security

All restaurant names are properly escaped before rendering:
import { Utils } from './utils.js';

// Escaping prevents XSS attacks
data-filter="${Utils.escapeHtml(restaurant)}"
${Utils.escapeHtml(restaurant)}
Never render user input directly into HTML without escaping. Always use Utils.escapeHtml().

Dependencies

The Filters module depends on:
  • DataStore: To get unique restaurant names (DataStore.getRestaurants())
  • Utils: For HTML escaping (Utils.escapeHtml())
  • UI: To trigger review grid updates (UI.renderReviews())

Dynamic Updates

The filter buttons automatically update when restaurants change:
// In src/js/modules/ui.js:17-21
document.addEventListener('reviewsUpdated', () => {
  this.renderReviews();
  Filters.update();  // Regenerate filter buttons
  Stats.update();
});
When reviews are added/deleted via Firebase real-time sync, the reviewsUpdated event triggers Filters.update() to refresh the button list.

Edge Cases

No Reviews

When no reviews exist, DataStore.getRestaurants() returns an empty array, and no filter buttons are generated (only the “Todos” button remains).

Single Restaurant

If all reviews are from one restaurant, only one filter button is generated.

Special Characters

Restaurant names with special characters are properly escaped:
// Restaurant name: "O'Reilly's Pub"
// Generated HTML (safely escaped):
<button data-filter="O&#39;Reilly&#39;s Pub">O&#39;Reilly&#39;s Pub</button>

Usage Example

import { Filters } from './modules/filters.js';
import { DataStore } from './modules/datastore.js';
import { UI } from './modules/ui.js';

// Initialize DataStore first
await DataStore.init();

// Initialize Filters
Filters.init();  // Generates filter buttons

// User clicks a filter button
// Automatically calls Filters.filterByRestaurant('Restaurant Name')
// Which updates currentFilter and calls UI.renderReviews()

// Reset to show all
Filters.filterByRestaurant('all');
Filters work alongside the Search module. Both can be active simultaneously:
// In UI.renderReviews() - src/js/modules/ui.js:44-51
let reviews = DataStore.reviews;

// Apply search first
if (Search.currentQuery) {
  reviews = DataStore.searchReviews(Search.currentQuery);
}

// Then apply restaurant filter
if (Filters.currentFilter !== 'all') {
  reviews = reviews.filter(r => r.restaurant === Filters.currentFilter);
}
Search and filter are applied sequentially. The UI shows reviews that match both the search query AND the selected restaurant filter.
  • DataStore - Provides restaurant list
  • UI - Renders filtered reviews
  • Search - Complementary filtering by text
  • Utils - HTML escaping utility

Build docs developers (and LLMs) love