This site uses HTML5/CSS3/JavaScript and some responsive design principals. It is hosted by 1&1. The original version of the site used the jwplayer media player to play audio using Adobe Flash. As the world went mobile, more and more visitors to the site were seeing the "no Flash?" fallback. As mobile platforms surged in popularity, HTML5 support became more and more prevalent. Rather than upgrade to the latest jwplayer, I wanted to learn HTML5 and go with a pure, no-Flash solution.

So, the big change was to use the HTML5 <audio> tag instead of the jwplayer. This involved learning a few neat tricks with hidden columns and object IDs. And while I was at it, I went for a responsive redesign with a white instead of black background. This was built on an existing foundation of a MySQL database accessed from PHP on the server.

If you get the source code of these pages, keep in mind a key point: php files are executed on the server and the php code is replaced with HTML. JavaScript, HTML, and CSS can be examined by downloading the source, but PHP code cannot. The PHP code is used for getting dynamic data like song and album info from a MySQL database. JavaScript jQuery is used for dynamically querying and updating objects in the browser and for controlling the HTML5 audio player.

HTML5 audio

In place of the old site's code that created a Flash player object from JavaScript with a new SWFObject() call, the new site just uses the HTML5 <audio> tag. If a browser doesn't support the <audio> tag, the code between the <audio> and </audio> tags displays a div explaining that the audio player isn't supported, providing a link to find an HTML5 browser and a link to play the current song. The PHP code earlier in the page has already set the $songURL and $songName variables appropriately.

<audio id='audio' preload='auto' tabindex='0' src='<?php echo($songURL) ?>' controls=''>
<div class='smallText'>[audio player not supported. 
<a href='https://html5test.com/index.html'>Find an HTML 5 web browser here</a>.]</div>
<?php echo('<a id="SongNameNoPlayerLink" href="'.$songURL.'">click here to play "'.$songName.'"</a>'); ?>
</audio>

This is all that's required for a page like index.php or picks.php that only provide the ability to play just one song. Playing a playlist and providing random access to a playlist is a lot more complicated, and required learning some more JavaScript.

hidden columns and object IDs

To display an album or playlist, I employed a table with a row for each song in the album/playlist. The song data was fetched from the MySQL database. One trick was to use the song ID from the MySQL song record as the ID of the link (<a id="'.$row['songID']...), and the other is to stuff some extra info about each song in three columns that are hidden (style="display:none" for the recording info and highlight link info). This extra info is used to dynamically update the song info when the current song changes in the playlist.

echo( '<tr>
<td align="center">'.$songNum.'</td>
<td'.$hotCell.'><a id="'.$row['songID'].'"href="'.$pathmusic.$row['songURL'].'">'.$row['songName'].'</a></td>
<td>'.$row['songYear'].'</td>
<td style="display:none">'.$row['recordingInfo'].'</td>
<td style="display:none">'.$highlight_link.'</td>
<td style="display:none">'.$highlight_blurb.'</td>
</tr>' );

This song ID and extra info are used in some JavaScript code that's executed in the document's ready() function:

$(document).ready(function () {
  var audio;
  var playlist;
  var tracks;
  var current;
  var hlink;

  init();

This ready() function calls init():

  function init(){
    current = 0;
    audio = $('audio'); 
    playlist = $('#playlist'); 		// find the table name 'playlist'
    tracks = playlist.find('tr'); 	// array of table rows
    len = tracks.length - 1; 		// number of rows in table

And within this init() function is an inline click handler. The click handler is called when a playlist link is clicked, and some fancy JavaScript code walks the heirarchy to find the two hidden cells (columns 3 and 4) in the clicked row:

    playlist.find('a').click(function(e){
      e.preventDefault();
      link = $(this);
      // need to determine the index in the playlist of the just clicked link
      // three parent() calls because: this link's parent is a td, 
      // whose parent is a tr, whose parent is a table
      // then get the table's children, which is an array of trs. 
      // Then get the index of the current tr in that.
      current = link.parent().parent().parent().children().index($(this).parent().parent());			
      blurb = $(this).closest('tr').find('td:eq(3)').text();
      hilink = $(link).parent().closest('tr').find('td:eq(4)').text();
      powblurb = $(this).closest('tr').find('td:eq(5)').html();

The clicked link, the recording info in 'blurb', the "Pick of the Week" info in 'hilink', the "Pick of the Week" HTML content, and the audio player object are passed to a function called run():

      run(link, blurb, hilink, highlightblurb, audio[0]);
    });

Before we look into the run() function, notice that the JavaScript code also registers an event listener that will be called when the audio player gets to the end of a song. This handler increments a current index. Then it checks if the incremented index is greater than the number of songs, in which case it resets the index to zero (the first song plays after the last). This causes an album or playlist to repeat infinitely. Then the tracks array is searched for all anchors with the class "songlink", and the link for the next track is set by subscripting that array with the current (just-incremented) index. From that link, the row is found, and from the row, the additional info is extracted from Then the current link, recording info, Pick of the Week highlight info, Pick of the week HTML content, and audio player are passed to run() to play the next song:

    audio[0].addEventListener('ended',function(e){
      current++;
      if(current == len+1)
        current = 0;
      link = tracks.find('a.songlink')[current];
	  row = $(link).parent().parent();
	  blurb = $(row).find('td:eq(3)').text();
	  hilink = $(row).find('td:eq(4)').text();
	  powblurb = $(row).find('td:eq(5)').html();
      run($(link), blurb, hilink, powblurb, audio[0]);
    });
  }

In the function run(), various divs and anchors have their info updated for the latest song. Note the "SongNameNoPlayerLink" div that was seen in the first code snippet in the code that is shown if the <audio> tag is not supported. Note also that the clicked link is passed in, and then link.text() is used to access the song title, link.attr('href') gets the URL to the MP3 file, and link.attr('id') gets the song ID (used by the MySQL database).

  function run(link, songinfo, highlightinfo, player){
    // update the song name field and song URL for sharing
    $('#SongName').text( link.text() );
    // and update the no-HTML5 link 
    $('#SongNameNoPlayerLink').text( 
      'click here to play "'.concat( link.text() ).concat( '"' ) );
    $('#SongNameNoPlayerLink').attr('href', link.attr('href') );

    // update the song info
    $('#SongInfoTag').text( 'Info for song: '.concat( link.text() ) );
    $('#SongInfo').text( songinfo );

If a song has been featured as a "Pick of the Week", the highlightinfo is not blank, and is parsed out to find the posting date and highlight ID. The div containing this link under the 'info' view, "InfoHighlightLinkDiv" is shown or hidden depending on if a "Pick of the Week" exists:

    if ( highlightinfo != '' ) {
      // have a string like 2014.05.04_1 where that's date_id. Need to parse out.
      var highbits = highlightinfo.split( "_" );
      $('#SongHighlightLink').attr('href', 'picks.php?id='.concat( highbits[1] ) );
      $('#SongHighlightLink').text( 'Pick of the Week on '.concat( highbits[0] ) );
      $('#main').html( highlightblurb );			
      $('#InfoHighlightLinkDiv').show();
    }
    else {
      $('#InfoHighlightLinkDiv').hide();
      $('#SongHighlightLink').text( highlightinfo );
    }

The song ID is extracted from the link, and used to create a song link in the 'info' page:

    // the php code that generates the playlist links adds the songID to the anchor's id
    hlink = 'https://mangobananas.com/music.php?songID='.concat( link.attr('id') );
    $('#current_song_tag').text( "Link for song '".concat( link.text() ).concat( "':" ) );
    $('#current_song_URL').text( hlink );
    $('#current_song_URL').attr('href', hlink );

In the last block of JavaScript code, the source file for the audio player is updated. Then the link that had previously had the 'active' class has that class removed, and the active class is added to the new 'active' link. (This creates visual highlighting of the current song via CSS.)

    player.src = link.attr('href');
    $('.active').removeClass('active');
    par = link.parent();
    par.addClass('active');

The last steps are to tell the audio player to load the song (this causes a download from the server of the MP3 file), then to play the song.

    player.load();
    player.play();
  }
}); // end document.ready() inline function

Responsive design

The old site used <table width="610" ...> everywhere, and fixed widths in the code pervasively. The HTML code contained far too much layout and style information. So I created a consistent page header in CSS, and removed all tables in all pages except the playlist in music.php, since it actually is semantically tabular data and I liked the JavaScript code around finding the clicked song and extracting data from hidden columns. I properly moved style and layout info into a new CSS style sheet. One of the keys to make the layout work on devices from iPhones through iPads to iMacs was to define a div I called window_viewport:

#window_viewport{
min-width:420px;
max-width:860px;
margin-left:auto;
margin-right:auto;
margin-top:0px;
margin-bottom:0px;
}

This sets a minimum and maximum width for the overall view, and the 'margin-left' and 'margin-right' set to 'auto' means the content will be centered if the window is wider than 860 pixels. I also used this meta tag at the top of each page to ensure the best display on mobile devices:

<meta name="viewport" content="width=device-width" />

This ensures that things are scaled and don't create horizontal scrollbars. The other key responsive style is for images that are used in the Pick of the Week descriptions:

img.float_right {
float:right;
margin:4px;
max-width:100%;
height:auto;
}

The key things in this style are 'max-width:100%' and 'height:auto'. This means that if the image is 420 pixels by 240 pixels, and the containing div is 320 pixels wide, the image will be scaled down to 320 pixels by 160 pixels. But if the containing div is 700 pixels wide, the image will display at 420 pixels by 240 pixels.

Similar responsive design is employed in laying out the "more..." section. Each of the major 'icons' is an image and a text link within a div that's defined with a set width and height.

div.big_pick{
float:left;
margin:6px;
width:128px;
height:200px;
}

This way, on an iPhone the "more..." icons wrap into two rows, but on larger displays they all fit in one row.

MySQL and PHP

The backend of the mangobananas site didn't have to change at all for this redesign. There is still a MySQL database hidden behind the web server that can be queried with PHP code. This database contains tables for artists, albums, and songs, as well as playlists, playlist songs, photo albums, photos, and highlights ("picks of the week"). For example, when the page music.php is passed an album ID, the PHP code gets all the songs with that album ID and sorts by the trackNum field.

$query = "SELECT songName,songURL,songYear,songID,recordingInfo FROM songs WHERE albumID=$albumID ORDER BY trackNum;
$result = mysql_query($query) or die('Error getting song details: '.mysql_error());
$row = @mysql_fetch_array($result);

After this code, the playlist table can be created by looping and outputting the data for each $row, as is done in the second code snippet above.

Any questions? email the mangobananas.com webmaster

Dark Mode

In September 2019, soon after iOS 13 and iPadOS 13 came out with the new "dark mode", I figured out how to make the site adapt to a dark or light color scheme depending on the user's preference. The site has a single CSS file, to which I added "@media (prefers-color-scheme: dark) { ... }" at the end with objects in the braces defining all the color changes for dark mode. That's all there is to it! Works on iOS 13, iPadOS 13, macOS 10.14 Mojave, and I've also confirmed it works in Chrome on Windows 10. (The webview within the Facebook app on iOS and iPadOS does NOT know anything about dark mode, however.)

icon: pick of the week
pick of the week
icon: music
music
icon: videos
videos
icon: photos
photos
icon: recording studios
recording studios