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" );
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.
Array of song objects from the search API
No return value - modifies DOM directly
Implementation
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
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 ;
// ...
});
Set to "song" for CSS styling
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
Set to "lazy" for deferred loading (improves performance)
Set to "anonymous" to enable CORS for color detection
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
Event Trigger Action mouseenterMouse hovers over card Detect and apply dominant color mouseleaveMouse leaves card Clear background color clickUser clicks card Load and play song keydown (Enter)User presses Enter while focused Load and play song (keyboard accessibility)
Step 6: Append to DOM
results . appendChild ( div );
Adds the completed song card to the results container.
Usage Examples
From API Response
From Search Event
Empty Results Handling
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
Lazy Loading Images
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
Called on mouseenter to apply color effect
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
Main container for each song card
Container for title and artist text
Applied to canvas element (not used in renderSongs)
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.