Skip to main content

Overview

The render functions handle dynamic creation and display of song search results. They transform JSON data from the backend API into interactive, accessible HTML elements.

Global Variables

const results = document.getElementById("results");
results
HTMLElement
Container div with ID results where song cards are rendered

Function

renderSongs(songs)

Renders an array of song objects as interactive cards in the results container.
songs
array
required
Array of song objects from the search API
songs[].id
string
required
YouTube video ID
songs[].title
string
required
Song title
songs[].artist
string
required
Artist name
songs[].thumbnail
string
required
Thumbnail image URL
return
void
No return value - modifies DOM directly

Implementation

index.html
function renderSongs(songs) {
  results.innerHTML = "";

  if (!songs?.length) {
    results.innerHTML = `<p>No se encontraron resultados</p>`;
    return;
  }

  songs.forEach(song => {
    const div = document.createElement("div");
    div.className = "song";
    div.tabIndex = 0;

    div.innerHTML = `
      <img src="${song.thumbnail}" 
           alt="Portada de ${song.title}"
           loading="lazy"
           crossorigin="anonymous">
      <div class="song-info">
        <strong>${song.title}</strong>
        <small>${song.artist}</small>
      </div>
    `;

    div.addEventListener("mouseenter", () => detectarColor(song.thumbnail, div));
    div.addEventListener("mouseleave", () => div.style.background = "");
    div.addEventListener("click", () => loadSong(song));
    div.addEventListener("keydown", e => {
      if (e.key === "Enter") loadSong(song);
    });

    results.appendChild(div);
  });
}

Function Flow

Step 1: Clear Previous Results

results.innerHTML = "";
Removes all existing song cards from the container.

Step 2: Handle Empty Results

if (!songs?.length) {
  results.innerHTML = `<p>No se encontraron resultados</p>`;
  return;
}
Uses optional chaining (?.) to safely handle null or undefined values.

Step 3: Create Song Cards

songs.forEach(song => {
  const div = document.createElement("div");
  div.className = "song";
  div.tabIndex = 0;
  // ...
});
className
string
Set to "song" for CSS styling
tabIndex
number
Set to 0 to make the element keyboard-focusable for accessibility

Step 4: Build HTML Structure

div.innerHTML = `
  <img src="${song.thumbnail}" 
       alt="Portada de ${song.title}"
       loading="lazy"
       crossorigin="anonymous">
  <div class="song-info">
    <strong>${song.title}</strong>
    <small>${song.artist}</small>
  </div>
`;

Image Attributes

loading
string
Set to "lazy" for deferred loading (improves performance)
crossorigin
string
Set to "anonymous" to enable CORS for color detection
alt
string
Descriptive text for screen readers (accessibility)

Step 5: Attach Event Listeners

div.addEventListener("mouseenter", () => detectarColor(song.thumbnail, div));
div.addEventListener("mouseleave", () => div.style.background = "");
div.addEventListener("click", () => loadSong(song));
div.addEventListener("keydown", e => {
  if (e.key === "Enter") loadSong(song);
});

Event Types

EventTriggerAction
mouseenterMouse hovers over cardDetect and apply dominant color
mouseleaveMouse leaves cardClear background color
clickUser clicks cardLoad and play song
keydown (Enter)User presses Enter while focusedLoad and play song (keyboard accessibility)

Step 6: Append to DOM

results.appendChild(div);
Adds the completed song card to the results container.

Usage Examples

const songs = [
  {
    id: "dQw4w9WgXcQ",
    title: "Never Gonna Give You Up",
    artist: "Rick Astley",
    thumbnail: "https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg"
  },
  {
    id: "9bZkp7q19f0",
    title: "Gangnam Style",
    artist: "PSY",
    thumbnail: "https://i.ytimg.com/vi/9bZkp7q19f0/default.jpg"
  }
];

renderSongs(songs);

DOM Structure Generated

<div id="results">
  <div class="song" tabindex="0">
    <img src="https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg" 
         alt="Portada de Never Gonna Give You Up"
         loading="lazy"
         crossorigin="anonymous">
    <div class="song-info">
      <strong>Never Gonna Give You Up</strong>
      <small>Rick Astley</small>
    </div>
  </div>
  
  <div class="song" tabindex="0">
    <!-- Additional song cards... -->
  </div>
</div>

Accessibility Features

WCAG Compliance

  • Keyboard Navigation: tabIndex="0" enables Tab key navigation
  • Enter Key Support: Activates song on Enter keypress
  • Alt Text: Descriptive alt attributes for screen readers
  • Semantic HTML: Uses <strong> and <small> for hierarchy

Testing Keyboard Accessibility

// User workflow:
// 1. Tab to focus on song card
// 2. Press Enter to play song
// 3. Tab to next song card
// 4. Repeat

Performance Optimizations

Lazy Loading Images

<img loading="lazy" ...>
Benefit
Images load only when scrolled into viewport, reducing initial page load time

Event Delegation Consideration

This implementation attaches individual event listeners to each song card. For very large result sets (100+ songs), consider using event delegation:
results.addEventListener("click", (e) => {
  const songDiv = e.target.closest(".song");
  if (songDiv) {
    const songId = songDiv.dataset.songId;
    // Load song by ID
  }
});

Integration with Other Functions

Function Dependencies

detectarColor()
function
Called on mouseenter to apply color effect
loadSong()
function
Called on click or Enter key to start playback

Error Handling

The search event handler wraps renderSongs() in error handling:
try {
  const res = await fetch(apiURL);
  if (!res.ok) throw new Error();
  
  const songs = await res.json();
  renderSongs(songs);
  
} catch {
  results.innerHTML = `<p>Error al buscar 😕</p>`;
}

State Management

Each render completely replaces the previous results. No state is preserved between renders.

Clearing Results

// Clear all songs
results.innerHTML = "";

// Or use renderSongs
renderSongs([]);

CSS Classes Used

.song
class
Main container for each song card
.song-info
class
Container for title and artist text
.hidden
class
Applied to canvas element (not used in renderSongs)

API Response Format

Expected JSON structure from backend:
[
  {
    "id": "dQw4w9WgXcQ",
    "title": "Never Gonna Give You Up",
    "artist": "Rick Astley",
    "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg"
  }
]
All four fields (id, title, artist, thumbnail) are required for proper rendering.

Build docs developers (and LLMs) love