In my last "techie" article about DanielMenjivar.com I went over some of the technology behind my music site and my decisions for choosing one over the other. Now it’s time to get deeper into things; I’ve decided to work my way backwards…
In this article, I’ll go over the JavaScript used on the site and as you’ve gathered from the title, I’ll focus on HTML5 audio with jQuery. But first, let’s get all the other JavaScript out of the way.
All the code examples below use jQuery, which makes life much easier.
// Open External Links in a new tab
$('a').live('click', function() {
if ($(this).attr('rel') == 'external') { $(this).attr('target','_blank'); }
});
Pretty straight forward. All external links have rel="external"
and this forces them to open in a new window/tab. I’m using the .live();
method to make sure that this also applies to any links loaded later on through AJAX.
All <article>
s on the site (used on the audio, videos, chart samples and free charts pages) are expanded/open by default. Then JavaScript collapses/closes them upon page load. This way, if people have JavaScript turned off for whatever reason, all the page content is still accessible.
// Make article heights smaller (closed) upon loading
$('article:not(.single)').not(hash).each(function() {
$(this).addClass('collapse');
if ($(this).hasClass('videos')) { $(this).addClass('videos-collapse'); }
});
So, all <article>
s (except single events and <article>
s loaded by a #hash in the URL) get closed/collapsed upon page load.
// Open/close articles on click
$('article').click(function(event) {
var $clicked = $(event.target);
if ( ! ($clicked.is('a') && $clicked.attr('rel'))) {
$('article:not(.collapse)').not(this).addClass('collapse');
$(this).toggleClass('collapse');
if ($(this).hasClass('videos')) {
$('article:not(.videos-collapse)').not(this).addClass('videos-collapse');
$(this).toggleClass('videos-collapse');
}
}
});
If it isn’t immediately obvious, clicking anywhere on the <article>
s expands them – and to keep things neat for the user, only one <article>
can be open at a time. Also, if the user clicks an external link within the <article>
, it doesn’t make sense to close/collapse that <article>
, so nothing else happens. The CSS for this is simple:
article {
height: auto;
overflow: visible;
cursor: default;
}
.collapse {
height: 48px;
overflow: hidden;
cursor: pointer;
}
.videos-collapse {
height: 85px;
}
I wanted to have a way to link to individual audio/video tracks and have the user not only taken to the requested content when the page loads, but also to start playback right away and expand/open the track details automatically. This is actually very simple to do – you just have to trigger the click function.
The same is true for the Twitter updates and my blog headlines – for example, a link to DanielMenjivar.com/#twitter will load the twitter updates on page load:
// Load our twitter updates
var hash = window.location.hash;
if (hash == '#twitter' && $('#nav-sub-twitter').length){ load_content('twitter'); }
$('#nav-sub-twitter a').click(function () { load_content('twitter'); });
As you can see from the code above, it only works if there is a "twitter" tab on the sub-navigation (which exists on the home page and the contact page). Also, the example above doesn’t trigger the click function, (but it could have). I’m also using a custom load_content();
function which takes care of the AJAX request and changing the page title – this way I can reuse the load_content();
function to load other content as well.
I originally intended for the charts list on my site to be sortable using JavaScript, but when I was researching how to do it, I realized that it would involve a lot of coding on my part. It made sense to use a plugin, (why reinvent the wheel?) but I couldn’t find anything lightweight enough. In the end, I realized that if the table was sorted by PHP/MySQL and the HTML was minified, an AJAX request would actually be much smaller in file size than all the JavaScript sorting code would be. (And maybe just as fast? Or even faster?) So that’s how I’m doing it – I’m replacing the table with AJAX content and letting PHP/MySQL handle the sorting.
// Allow the Charts List Table to be sorted
$('#charts-table th a').live('click', function (event) {
$.get('/x/charts/' + $(this).text().toLowerCase(), function(data) { $('#charts-table').replaceWith(data); });
event.preventDefault();
});
And again I’m using the .live();
method to make sure that even the AJAX loaded table is sortable again.
Now, finally, we get to the HTML5 Audio Part!
First, Here’s an explanation of what’s happening with the markup on the page:
<audio>
, <embed>
nor <object>
element anywhere on the page – it gets created dynamically by JavaScript.<audio>
element. Otherwise, I create an <object>
element and use QuickTime for playback. Why QuickTime? Well, because I like apple for one. Second, lots of people have iPods, which requires iTunes, which requires QuickTime, so chances are high(ish) that they’ll already have QuickTime installed. Third, QuickTime can for sure play AAC audio – it means I only have to encode audio in two formats then. And the fourth reason for using QuickTime is that it makes it a little easier to control playback – remember, if the browser chooses to use QuickTime, it means that none of the HTML5 Audio API is available to me…Make sense so far? I hope so. Here’s the JavaScript function (used later on) that creates the <audio>
element (or the <object>
element):
// This is the function that returns the audio element
function createNewAudio (song) {
var type = ($('body').attr('id') == 'charts-samples') ? 'charts' : 'audio';
var audiotag = $('<audio></audio>');
if (audiotag.get(0).canPlayType && (audiotag.get(0).canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '') || audiotag.get(0).canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/, ''))) {
audiotag.remove();
var createNewAudio = $('<audio autoplay></audio>')
.hide()
.attr('id','player')
.addClass(song)
.append('<source src="/x/get/' + type + '/' + song + '.m4a" type="audio/mp4" />')
.append('<source src="/x/get/' + type + '/' + song + '.oga" type="audio/ogg" />');
} else {
audiotag.remove();
var createNewAudio = $('<object></object>')
.attr('id','player')
.attr('width',0)
.attr('height',0)
.hide()
.addClass(song)
.attr('classid','clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B')
.attr('codebase','https://www.apple.com/qtactivex/qtplugin.cab')
.append('<param name="src" value="/x/get/' + type + '/' + song + '.m4a">')
.append('<param name="autoplay" value="true">')
.append('<param name="controller" value="false">');
}
return createNewAudio;
}
As you can see, I’m setting the class of the <audio>
(or <object>
) element to the "song" so we can track which song is playing. it’s also worth mentioning that by using .get(0)
it allows me to get the first DOM element in the jQuery collection and access the HTML5 audio API…
Now, Here’s how I’m using that function and controlling playback on the page:
// Play our Audio Samples and Chart Samples (audio) on click & hash load
$('.samples .icon a, .audio .icon a').click(function (event) {
var $icon = $(this).parent();
var $article = $(this).parents('article');
var song = $article.attr('id');
var $audioElement = $('#player');
// don’t collapse our article if it’s already open (but allow it to open if it isn’t)
if ( ! $article.hasClass('collapse') || $icon.hasClass('pause')) { event.stopPropagation(); }
// now check if our audio element exists, if it does, pause/play it, or create a new one…
if ($audioElement.length) { // if the audio element exists
if ($audioElement.hasClass(song)) { // if the audio element is set to the clicked song
if ($icon.hasClass('pause')) { // if the icon is set to pause, we should just pause the song
if ($audioElement.is('audio')) {
$audioElement.get(0).pause();
} else {
$audioElement.get(0).Stop();
}
event.preventDefault();
} else { // if the icon is not set to pause, we should resume the song (play)
if ($audioElement.is('audio')) {
$audioElement.get(0).play();
} else {
$audioElement.get(0).Play();
}
event.preventDefault();
}
} else { // the song playing is different from what was just clicked
$audioElement.remove();
$audioElement = createNewAudio(song).appendTo('body');
}
} else { // if the audio element doesn’t exist, always create a new one
$audioElement = createNewAudio(song).appendTo('body');
}
// nothing else can be paused, and toggle this button to pause on/off
$('article .icon').not($icon).removeClass('pause');
$icon.toggleClass('pause');
});
if ($(hash).length && ($('#charts-samples').length || $('#media-audio').length)) { $(hash + ' .icon a').trigger('click'); }
Here’s what’s going on: (It should be easy to follow along since the code is commented pretty well, but you may have to scroll to see the comments…)
<article>
) aren’t already expanded/open, then they get expanded. (But don’t close it if it’s already open.)<audio>
element or a QuickTime <object>
.It looks more complex than it is, doesn’t it? Yup, that’s all there is to it!
The videos on the site use the same concept as the audio except that I obviously don’t want to hide the video… Also, the videos have their own playback controls so I don’t need to worry about controlling playback via JavaScript.
// This is the function that returns the video element
function createNewVideo (video) {
var videotag = $('<video></video>');
if (videotag.get(0).canPlayType && (videotag.get(0).canPlayType('video/mp4; codecs="avc1.42401E, mp4a.40.2"').replace(/no/, '') || videotag.get(0).canPlayType('video/ogg; codecs="theora, vorbis"').replace(/no/, ''))) {
videotag.remove();
var createNewVideo = $('<video autoplay controls></video>')
.attr('width','480')
.attr('height','360')
.append('<source src="/x/get/videos/' + video + '.mp4" type="video/mp4" />')
.append('<source src="/x/get/videos/' + video + '.ogv" type="video/ogg" />');
} else {
videotag.remove();
var createNewVideo = $('<object></object>')
.attr('classid','clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B')
.attr('codebase','https://www.apple.com/qtactivex/qtplugin.cab')
.attr('width','480')
.attr('height','376')
.append('<param name="src" value="/x/get/videos/' + video + '.mp4">')
.append('<param name="autoplay" value="true">')
.append('<param name="showlogo" value="false">');
}
return createNewVideo;
}
// Play our Video Samples on click & hash load
$('.videos .icon a').click(function (event) {
var $article = $(this).parents('article');
var video = $article.attr('id');
// don’t collapse our article if it’s already open (but allow it to open if it isn’t)
if ( ! $article.hasClass('collapse')) { event.stopPropagation(); }
$.fancybox(createNewVideo(video), {
'transitionIn' : 'elastic',
'transitionOut' : 'elastic',
'centerOnScroll' : true,
'orig' : $(this),
'type' : 'inline'
});
});
if ($(hash).length && $('#media-videos').length) { $(hash + ' .icon a').trigger('click'); }
Like the videos, the photos and chart samples images are displayed using FancyBox as well. I’ve also used FancyBox on the iPhone JavaScripts (which are slightly different than the examples above) to render the main navigation/menu.
On the iPhone version of the site, there are two notable differences – instead of being normal web links, the Twitter icon and the Facebook icon on the contact page are links to my profile in the official iPhone apps. With this change, every contact icon (Facebook, Twitter, AIM, Skype) opens in the respective app. Here’s the code for that:
// change the twiter icon’s link to use the official twitter app
$('#twitter-link').click(function(event) {
var tweeter = $(this).attr('href').split('/');
tweeter = tweeter[tweeter.length – 1];
var url = 'twitter://user?screen_name=' + tweeter;
$(this).attr('href',url);
});
// change the facebook icon’s link to use the official facebook app
$('#facebook-link').click(function(event) {
var profile = $(this).attr('href').split('/');
profile = profile[profile.length – 1];
var url = 'fb://profile?id=' + profile;
$(this).attr('href',url);
});
Lastly, incase you’ve noticed the search button on the events page, it’s actually not controlled using JavaScript – only CSS! (More on that later.) The search results are not loaded by JavaScript either, but loading #more events does indeed use AJAX.
OK, that’s it for now! I hope you’ve found something useful on here. Love it? Hate it? Have a better way? Let me know!