Back to Directory

Code Explained

A complete beginner's guide to understanding every line of code in the Austin Karaoke Directory

1. Project Overview

The Austin Karaoke Directory is what developers call a "Single Page Application" or SPA. This means that instead of loading a new page every time you click something, JavaScript updates the content on the same page. This makes the app feel faster and more responsive, similar to how apps on your phone work.

What Technologies Does This App Use?

This application is built with three core web technologies that every website uses:

Why No Framework?

Many modern websites use frameworks like React, Vue, or Angular. These are pre-built libraries that make development faster but add complexity. This project uses "vanilla JavaScript" - meaning plain JavaScript without any framework. This approach has several benefits:

Project File Structure

Understanding how files are organized is crucial for navigating any codebase. Here's how this project is structured:

karaokedirectory/ ├── index.html # Main page - the entry point users see ├── about.html # About page ├── bingo.html # Karaoke bingo game ├── editor.html # Venue data editor with geocoding ├── submit.html # Venue submission form ├── codeexplained.html # This documentation page │ ├── css/ # All styling files │ ├── base.css # Variables, reset, typography │ ├── layout.css # Page structure, header, footer │ ├── components.css # Reusable UI components (cards, modals, search) │ ├── views.css # View-specific styles (weekly, map, etc.) │ ├── bingo.css # Bingo game styles │ ├── editor.css # Editor page styles │ ├── submit.css # Submission form styles │ └── docs.css # Documentation page styles │ ├── js/ # All JavaScript files │ ├── app.js # Main application entry point │ ├── data.js # Venue database (70+ venues) │ ├── bingo.js # Bingo game logic │ │ │ ├── core/ # Core application systems │ │ ├── state.js # State management (stores app data) │ │ └── events.js # Event bus (component communication) │ │ │ ├── components/ # Reusable UI pieces │ │ ├── Component.js # Base component class │ │ ├── Navigation.js # View tabs, search bar, filters │ │ ├── VenueCard.js # Venue display card │ │ ├── VenueModal.js # Popup venue details (mobile) │ │ ├── VenueDetailPane.js # Side panel details (desktop) │ │ └── DayCard.js # Day schedule card │ │ │ ├── views/ # Full-page views │ │ ├── WeeklyView.js # Calendar view │ │ ├── AlphabeticalView.js # A-Z list view │ │ └── MapView.js # Interactive map │ │ │ ├── services/ # Data handling │ │ └── venues.js # Venue data operations, search, filtering │ │ │ └── utils/ # Helper functions │ ├── date.js # Date formatting, schedule matching │ ├── debug.js # Debug mode utilities (?debug=1) │ ├── render.js # Shared rendering (schedule tables, host sections) │ ├── string.js # Text manipulation, escaping │ ├── tags.js # Venue tag rendering and configuration │ ├── url.js # URL building, sanitization │ └── validation.js # Form input validation │ ├── editor/ # Editor tool │ └── editor.js # Editor logic with geocode support │ ├── tests/ # Test suite │ ├── index.html # Visual test runner with date tester │ └── schedule-tests.js # Schedule matching tests │ ├── scripts/ # Developer tools │ ├── geocode-venues.js # Batch geocode venues (Node.js) │ └── validate-data.js # Validate venue data integrity │ └── assets/images/ # Static images

Key Concept: Separation of Concerns

Notice how different types of code are in different folders. This is a fundamental principle called "separation of concerns" - keeping code organized by what it does. This makes it easier to find things and prevents the codebase from becoming a tangled mess.

2. HTML Structure

HTML is written using "tags" - special keywords surrounded by angle brackets. Tags usually come in pairs: an opening tag and a closing tag. Everything between them is the content. For example, <p>Hello</p> creates a paragraph containing the word "Hello".

The DOCTYPE Declaration

Every HTML file starts with <!DOCTYPE html>. This isn't actually an HTML tag - it's an instruction to the web browser saying "this is an HTML5 document." HTML5 is the current version of HTML. Without this line, browsers might render your page in an older compatibility mode, which can cause layout problems.

The html Tag and Language

After the DOCTYPE, we have <html lang="en">. This is the root element - everything else goes inside it. The lang="en" attribute tells browsers and assistive technologies (like screen readers for visually impaired users) that the page content is in English. This helps with pronunciation in text-to-speech and affects things like spell-checking.

The head Section

The <head> section contains metadata - information ABOUT the page that doesn't appear visually on screen. This includes:

Character Encoding (meta charset)

The line <meta charset="UTF-8"> tells the browser how to interpret the text characters in your file. UTF-8 is a universal encoding that supports virtually every language and symbol, including emojis. Without this, special characters might display as garbage (like "é" instead of "é").

Viewport Meta Tag

The viewport meta tag is critical for mobile devices: <meta name="viewport" content="width=device-width, initial-scale=1.0">. Before smartphones, websites were designed for desktop monitors. When mobile browsers first loaded these sites, they would show a tiny zoomed-out version. The viewport tag tells the browser "make the page width match the device width, and don't zoom in or out initially." Without this, your responsive designs won't work on mobile.

The Title Tag

The <title> tag sets the text that appears in the browser tab. It's also what shows up in search engine results and when someone bookmarks your page. It should be descriptive but concise - search engines typically show about 50-60 characters.

Link Tags for CSS

We use <link rel="stylesheet" href="..."> to connect CSS files to our HTML. The browser loads these in order, which matters because CSS rules can override each other. Our app loads Font Awesome (for icons) first, then our custom stylesheets in a specific order: base (variables and resets), layout (structure), components (UI pieces), and views (page-specific styles).

The body Section

The <body> section contains everything users actually see on the page. In our app, this is organized into several main areas:

Semantic HTML Elements

Instead of using generic <div> tags for everything, we use "semantic" elements that describe their purpose:

Using semantic elements helps screen readers understand your page structure, improves SEO (search engine optimization), and makes your code more readable to other developers.

Data Attributes

You'll see attributes like data-song="0" or data-venue-id="..." throughout the HTML. These "data attributes" let us store custom information on HTML elements that JavaScript can read later. Any attribute starting with data- is accessible in JavaScript via element.dataset.attributeName.

Accessibility Attributes

Attributes starting with aria- are for accessibility. For example, aria-label="Close" tells screen readers what a button does when it only has an icon and no visible text. The hidden attribute hides elements from both visual display AND screen readers.

3. CSS Styling

CSS works by selecting HTML elements and applying style rules to them. A CSS rule has two parts: a selector (what to style) and declarations (how to style it). For example: .button { color: blue; } selects all elements with the class "button" and makes their text blue.

CSS Custom Properties (Variables)

At the top of base.css, you'll see a block starting with :root. This is where we define CSS custom properties, also known as CSS variables. They look like --color-primary: #6366f1;.

The :root selector targets the root element (the <html> tag), making these variables available throughout the entire document. We use them with the var() function: color: var(--color-primary);.

Why use variables? If we want to change the primary color of the entire site, we only need to change it in one place. Without variables, we'd have to find and update every instance of that color value throughout our stylesheets.

The CSS Reset

Browsers have built-in default styles - paragraphs have margins, headings have specific sizes, lists have bullet points. Unfortunately, different browsers have slightly different defaults. The CSS reset at the top of base.css removes these inconsistencies:

BEM Naming Convention

Our CSS classes follow a pattern called BEM (Block, Element, Modifier). This naming convention helps organize styles and prevent conflicts:

This creates a clear hierarchy. When you see .day-card__header, you immediately know it's the header element inside a day-card block, not just any header on the page.

Responsive Design

The app looks different on phones versus desktops. This is achieved through "media queries" - CSS rules that only apply at certain screen sizes. For example:

@media (min-width: 1200px) { ... } means "only apply these styles when the screen is at least 1200 pixels wide."

Our app uses a "mobile-first" approach: we write styles for small screens first, then add media queries to enhance the layout for larger screens. This generally results in cleaner, more maintainable CSS.

CSS Transitions and Animations

When elements change state (like when you hover over a button), we use transitions to make the change smooth rather than instant. The transition property specifies what should animate, how long it takes, and the easing function (how the animation accelerates/decelerates).

For example, transition: color 200ms ease; means "when the color changes, animate the change over 200 milliseconds with an ease timing function."

4. JavaScript Architecture

JavaScript makes websites interactive. When you click a button, switch views, or see content load dynamically, that's JavaScript at work. Our app uses modern JavaScript features (ES6+) and a modular architecture.

ES6 Modules

Traditional JavaScript puts everything in one global space, which can cause naming conflicts. ES6 modules solve this by letting files explicitly import and export functionality:

In HTML, we load a module with <script type="module" src="...">. The browser then follows the import chain, loading each required file.

The Application Entry Point (app.js)

Every application needs a starting point. Our app begins in app.js, which does several things when the page loads:

  1. Loads venue data from data.js
  2. Creates the navigation component and renders it
  3. Creates the modal component (for mobile venue details)
  4. Creates the detail pane component (for desktop venue details)
  5. Sets up event listeners for venue selection
  6. Renders the initial view (the weekly calendar)

The init() function orchestrates all of this. It's called when the DOM (Document Object Model - the browser's representation of your HTML) is ready.

Async/Await

You'll see the async and await keywords in our code. These handle "asynchronous" operations - things that take time to complete, like loading data from a file.

async function loadData() { ... } marks a function as asynchronous. Inside it, await someOperation() pauses execution until that operation completes. This makes asynchronous code read like synchronous code, which is much easier to understand.

The Window Object

In browsers, window is the global object representing the browser window. We use it to:

5. Data Management

Our app displays information about karaoke venues. This data is stored in data.js as a JavaScript object and managed through venues.js service.

The Data Structure

The venue data is organized as a JavaScript object with a "listings" array. Each venue in the array has properties describing it:

The Venues Service (venues.js)

Instead of accessing the raw data directly, components use functions from venues.js. This is called a "service layer" - it provides a clean interface for working with data:

This abstraction has several benefits: if we ever change how data is stored (maybe fetching from a server instead of a local file), we only need to update the service - all the components using it continue to work unchanged.

Schedule Matching

One of the trickier parts of the app is determining which venues have karaoke on a given date. The scheduleMatchesDate() function in date.js handles this. It considers:

For "first", "second", etc., it calculates which occurrence of that weekday in the month the date falls on. For "last", it checks if there's another occurrence of that weekday later in the same month. For "once" events, it simply compares the schedule's date field against the target date.

The Tagging System

Venues can be tagged with characteristics that help users find the right spot. Tags are displayed as color-coded badges on venue cards throughout the app.

Tag Definitions

At the top of data.js, there's a tagDefinitions object that defines all available tags:

Each tag definition includes a human-readable label, background color, and text color for proper contrast.

Tag Utilities (tags.js)

The tags.js utility module handles tag rendering:

Tags are rendered using inline styles (background color and text color) from the tag configuration, wrapped in .venue-tags and .venue-tag CSS classes for layout and sizing.

Where Tags Appear

Tags are displayed in all venue displays throughout the app:

Adding New Tags

To add a new tag, simply add it to the tagDefinitions object in data.js with a unique ID, label, background color, and text color. The system will automatically make it available throughout the app.

6. State Management

"State" refers to the data that can change as users interact with the app. In our case, this includes which view is active, what week is being displayed, whether dedicated venues are shown, and which venue (if any) is selected.

Why Centralized State?

Without centralized state, components would need to directly communicate with each other, creating a tangled web of dependencies. With centralized state, components simply read from and write to one place. When state changes, the system notifies all interested components.

The State Object (state.js)

Our state is stored in a simple JavaScript object:

The Subscribe Pattern

Components can "subscribe" to state changes using subscribe(key, callback). When that piece of state changes, the callback function is called with the new value. This is similar to how you might subscribe to a YouTube channel - you get notified when new content appears.

For example: subscribe('view', (newView) => this.render()) means "whenever the view state changes, re-render this component."

Updating State

State is updated via setState({ key: value }). This function:

  1. Compares new values to current values
  2. Updates only the values that changed
  3. Notifies subscribers of changes

This is a "reactive" pattern - changes automatically propagate through the system. You don't need to manually tell every component "hey, the view changed" - it happens automatically.

7. Event System

While state management handles data changes, the event system handles actions and communication between components. Think of events as announcements: "A venue was selected!" or "The modal was closed!"

The Event Bus (events.js)

Our event bus is like a bulletin board where components can post messages and other components can listen for them. It provides several functions:

Why Events Instead of Direct Communication?

Imagine the VenueCard component needs to tell the VenueModal to open with specific venue details. Without events, VenueCard would need to know about VenueModal directly, creating a tight coupling. If we change or remove VenueModal, VenueCard breaks.

With events, VenueCard just announces emit(Events.VENUE_SELECTED, venue). It doesn't know or care who's listening. VenueModal (and VenueDetailPane, and anything else that cares) independently listens for that event. Components are decoupled - they can be added, removed, or changed without affecting each other.

Common Events

The Events object defines all event names used in the app:

Design Pattern: Pub/Sub

This event system implements the Publisher/Subscriber (Pub/Sub) pattern. Publishers (emit) don't need to know about subscribers (on), and subscribers don't need to know about publishers. This loose coupling makes the code more flexible and easier to maintain.

8. Component System

Modern web development organizes UIs into "components" - reusable, self-contained pieces. Our app has a simple component system built without any framework.

The Base Component Class (Component.js)

All our UI components extend the Component class. This base class provides common functionality:

Constructor

When you create a component with new Navigation('#navigation'), the constructor:

  1. Finds the container element (by selector or reference)
  2. Stores the props (properties passed in)
  3. Initializes empty state
  4. Sets up arrays to track subscriptions and event listeners (for cleanup)
  5. Calls the init() method

The Render Cycle

When render() is called:

  1. template() is called to generate HTML string
  2. That HTML is injected into the container via innerHTML
  3. afterRender() is called to set up event listeners

This pattern separates concerns: template() focuses purely on generating HTML, while afterRender() handles interactivity.

State and Props

When you call setState({ key: value }), the component automatically re-renders with the new state.

Event Delegation

The delegate(event, selector, handler) method enables "event delegation" - a technique where a single event listener on a parent handles events from many children. Instead of adding click listeners to 50 venue cards, we add one listener to their container that checks if the clicked element matches '.venue-card'.

Event delegation is more efficient and automatically handles dynamically added elements.

Cleanup with destroy()

When a component is removed, destroy() cleans up:

This prevents "memory leaks" - leftover handlers that waste memory and can cause bugs.

Specific Components

Navigation

Renders the view tabs (Calendar, A-Z, Map), week navigation arrows, a global search bar with clear button, and the "Show dedicated venues" toggle. The search bar filters venues across all views by name, city, neighborhood, host, company, and tags. Subscribes to view and weekStart state to update when these change. Notably, search query changes do not cause a full Navigation re-render, which preserves keyboard focus in the search input.

VenueCard

Displays a venue in either "compact" mode (for day cards) or "full" mode (for alphabetical view). Shows venue name, time, location, color-coded tags, host info, and special event indicators. Handles click events to select the venue. Event URLs are rendered as external links.

VenueModal

A full-screen popup for venue details on mobile. Listens for VENUE_SELECTED events and opens. Handles closing via button click, backdrop click, or Escape key.

VenueDetailPane

A sticky side panel for venue details on desktop (1200px+ screens). Similar to VenueModal but displayed inline instead of as an overlay.

DayCard

Shows all venues for a specific day. Displays the day name, date, "Today" badge if applicable, venue count, and a list of VenueCards. Past days are collapsed by default but can be expanded by clicking the header. Days with no venues after search/filter are collapsed with a .day-card--empty class. Special events sort to the top of the day's listings.

9. Views Explained

Views are full-page components that display venue information in different formats. The app has three views that users can switch between.

WeeklyView

The default view shows a 7-day calendar. Here's how it works:

  1. Gets the week start date from state (which Sunday)
  2. Generates an array of 7 dates for that week
  3. For each date, renders a DayCard showing venues with karaoke that day
  4. Automatically scrolls to today if viewing the current week

The view subscribes to weekStart and showDedicated state. When users navigate weeks or toggle the dedicated filter, the view re-renders.

Past Days Behavior

Days in the past get a day-card--past class which collapses them by default. Users can click the header to expand and see that day's venues. This keeps the interface focused on upcoming events.

AlphabeticalView

Shows all venues sorted A-Z, grouped by first letter:

  1. Gets all venues from the venues service
  2. Sorts alphabetically (ignoring articles like "The")
  3. Groups by first letter
  4. Renders a letter index at the top (click to jump to that letter)
  5. Renders each group with a header and list of VenueCards

The "ignore articles" logic is in getSortableName() - it moves articles like "The", "A", "An" to the end for sorting purposes. So "The Common Interest" sorts under "C", not "T".

MapView (Immersive Mode)

The map view uses an immersive full-screen mode that hides all page chrome (header, footer, navigation) and displays venues on an interactive map using Leaflet.js.

How Immersive Mode Works

  1. Body class toggle - When map view is active, app.js adds body.view--map class
  2. CSS hides chrome - The class triggers CSS rules that hide header, footer, and navigation
  3. Map fills viewport - The map container becomes fixed position, 100% width and height
  4. Floating controls - View switcher and filter buttons float on top of the map

Floating UI Elements

User Interactions

The floating venue card replaces the modal overlay, allowing users to see the venue's location on the map while viewing its details.

Note About Map Coordinates

Not all venues have coordinates in the data. The map shows how many venues are mapped and suggests using the editor to add coordinates for unmapped venues.

10. Utility Functions

Utility functions are small, reusable pieces of code that don't belong to any specific component. They're organized by purpose in the utils folder.

Date Utilities (date.js)

Working with dates in JavaScript is notoriously tricky. Our date utilities handle common operations:

String Utilities (string.js)

Functions for manipulating and formatting text:

URL Utilities (url.js)

Functions for building and validating URLs:

Render Utilities (render.js)

Shared rendering functions used by both VenueModal and VenueDetailPane to avoid code duplication:

Debug Utilities (debug.js)

Development tools activated by adding ?debug=1 to the URL or setting localStorage.setItem('debug', '1'). When enabled, venue cards display the reason they appear on a given date (e.g., "Every Friday", "First Saturday"), and a "Debug Mode" indicator appears in the corner of the page.

Why Escape HTML?

If venue data contained <script>alert('hacked')</script> and we rendered it directly, that script would execute. The escapeHtml() function converts < to &lt; and > to &gt; so it displays as text instead of executing. Always escape user-provided content!

11. How Everything Works Together

Now let's trace through what happens when you perform common actions in the app.

When the Page First Loads

  1. Browser loads index.html and parses the HTML structure
  2. Browser loads CSS files and applies styles
  3. Browser loads data.js, creating the karaokeData global variable with tag definitions and venue listings
  4. Browser loads app.js as a module, which imports its dependencies
  5. When DOM is ready, app.js calls init()
  6. init() calls loadData(), which initializes tag configuration and venue data in the service layer
  7. init() creates Navigation (with search bar and filters), VenueModal, and VenueDetailPane components
  8. init() calls renderView('weekly'), which creates and renders WeeklyView
  9. WeeklyView generates DayCards for the current week, which generate VenueCards (with tags, schedule info, and host details)
  10. WeeklyView scrolls to today's card

When You Click a Venue

  1. Click event bubbles up to WeeklyView's container
  2. Event delegation matches '.venue-card' selector
  3. Handler extracts venue ID from data-venue-id attribute
  4. Handler calls getVenueById() to get full venue data
  5. Handler emits Events.VENUE_SELECTED with venue data
  6. VenueModal (on mobile) receives event, calls open(venue)
  7. VenueModal updates state, re-renders with venue details, shows modal
  8. VenueDetailPane (on desktop) also receives event, updates its content
  9. app.js handler adds 'venue-card--selected' class to highlight the card

When You Switch Views

  1. Click on navigation tab (e.g., "A-Z")
  2. Navigation's event delegation captures click
  3. Handler calls setState({ view: 'alphabetical' })
  4. State system notifies subscribers
  5. app.js's subscriber calls renderView('alphabetical')
  6. Current view's destroy() is called (cleanup)
  7. AlphabeticalView is created and rendered
  8. Navigation re-renders to show new active tab

When You Navigate Weeks

  1. Click on left/right arrow in Navigation
  2. Handler calls navigateWeek(-1) or navigateWeek(1)
  3. navigateWeek() calculates new date and calls setState({ weekStart: newDate })
  4. WeeklyView's subscriber triggers re-render
  5. Navigation's subscriber triggers re-render (to update week range display)
  6. WeeklyView generates new DayCards for the new week

The Flow of Data

Understanding data flow is key to understanding any application:

Data flows down (from state to components), while events flow up (from user interactions to state changes). This unidirectional flow makes the app predictable and easier to debug.

Congratulations!

You've just learned the fundamentals of how a modern JavaScript application works. The concepts here - components, state management, event systems, modules - are used in frameworks like React, Vue, and Angular. Understanding them at this fundamental level will make learning any framework much easier.