March 23, 2012

Javascript: Remove duplicate values from an array

I did a Google search for different methods of removing duplicate values from an array, and I found a bunch of crappy algorithms that all returned a new array, rather than paring down the original array. So I wrote this, which wouldn't work on identical complex objects, but works great for primitive data types and objects stored by reference.
function removeDuplicates(arr) {
  arr.sort();
  var i = arr.length - 1;
  while (i > 0) {
    if (arr[i] === arr[i - 1]) arr.splice(i, 1);
    i--;
  }
}

March 22, 2012

Javascript: Get the original size of an image

Sometimes you need to know the original dimensions of an image that you've loaded via javascript, or that's already loaded in the DOM. Here's a little class that will take the location of an image, and a callback function that receives the original width and height:
var getImageSizeWithCallback = function( src, callback ) {
  var image = new Image();
  image.onload = function () {
    if( callback ) callback( image.width, image.height );
    image.onload = image.onerror = null;
  };
  image.onerror = function () {
    if( callback ) callback( -1, -1 );
    image.onload = image.onerror = null;
  };
  // load it
  image.src = src;
};

Usage:
getImageSizeWithCallback( 'images/path/to/img.jpg', function( w, h ) {
  console.log( 'image size:', w, h );
});

March 6, 2012

Bookmarklet: Scrape a web page for YouTube videos and generate a playlist

I wrote this little script to capture about 50 YouTube videos from a music blog I was browsing. I figured it would be useful for others, so I turned it into a bookmarklet:
// get html source
var src = document.getElementsByTagName('html')[0].innerHTML;
// handle old youtube vids, and https
var vids = src.replace(/\"/g).replace(/http:\/\/www.youtube.com\/v\//gi,'http://www.youtube.com/embed/').replace(/https/gi,'http');
// parse the source for youtube embeds
var vidArr = vids.split('http://www.youtube.com/embed/')
var links = '';
// build string for playlist
var playlist = 'http://www.ytplaylist.com/?pl=';
// build links
if(vidArr.length > 1) {
// keep track of IDs so we don't get duplicates
var videos = [];
for(var i=1; i < vidArr.length; i++) {
// grab 11-character YouTube ID
var ytID = vidArr[i].substr(0,11);
if(videos.indexOf(ytID) == -1) {
// add link to video, and to playlist link
var link = 'http://www.youtube.com/watch?v='+ytID;
links += '<a target="_blank" href="'+link+'">'+link+'</a><br/>';
playlist += ytID+';';
videos.push(ytID);
}
}
playlist = playlist.substr(0,playlist.length-1);
// draw it to the page
var container = document.createElement('div');
container.innerHTML = 'Videos Found:<br/><br/>'+links+'<br/><br/>'+'<a target="_blank" href="'+playlist+'">View as Playlist</a><br/>';
container.style.position='fixed';
container.style.background='#fff';
container.style.border='5px solid black';
container.style.padding='10px';
container.style.top='0';
container.style.right='0';
container.style.zIndex='999';
document.body.appendChild(container);
} else {
alert('No YouTube videos found');
}
And here's the bookmarklet link text:
javascript:(function(){var%20src%20%3D%20document.getElementsByTagName('html')%5B0%5D.innerHTML%3B%0Avar%20vids%20%3D%20src.replace(%2F%5C%22%2Fg).replace(%2Fhttp%3A%5C%2F%5C%2Fwww.youtube.com%5C%2Fv%5C%2F%2Fgi%2C'http%3A%2F%2Fwww.youtube.com%2Fembed%2F').replace(%2Fhttps%2Fgi%2C'http')%3B%0Avar%20vidArr%20%3D%20vids.split('http%3A%2F%2Fwww.youtube.com%2Fembed%2F')%0Avar%20links%20%3D%20''%3B%20%0Avar%20playlist%20%3D%20'http%3A%2F%2Fwww.ytplaylist.com%2F%3Fpl%3D'%3B%20%0A%2F%2F%20build%20links%0Aif(vidArr.length%20%3E%201)%20%7B%0A%20%20var%20videos%20%3D%20%5B%5D%3B%0A%20%20for(var%20i%3D1%3B%20i%20%3C%20vidArr.length%3B%20i%2B%2B)%20%7B%20%0A%20%20%20%20var%20ytID%20%3D%20vidArr%5Bi%5D.substr(0%2C11)%3B%0A%20%20%20%20if(videos.indexOf(ytID)%20%3D%3D%20-1)%20%7B%0A%20%20%20%20%20%20var%20link%20%3D%20'http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D'%2BytID%3B%0A%20%20%20%20%20%20links%20%2B%3D%20'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Blink%2B'%22%3E'%2Blink%2B'%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20%20%20%20%20playlist%20%2B%3D%20ytID%2B'%3B'%3B%0A%20%20%20%20%20%20videos.push(ytID)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20playlist%20%3D%20playlist.substr(0%2Cplaylist.length-1)%3B%0A%20%20var%20container%20%3D%20document.createElement('div')%3B%0A%20%20container.innerHTML%20%3D%20'Videos%20Found%3A%3Cbr%2F%3E%3Cbr%2F%3E'%2Blinks%2B'%3Cbr%2F%3E%3Cbr%2F%3E'%2B'%3Ca%20target%3D%22_blank%22%20href%3D%22'%2Bplaylist%2B'%22%3EView%20as%20Playlist%3C%2Fa%3E%3Cbr%2F%3E'%3B%0A%20%20container.style.position%3D'fixed'%3B%0A%20%20container.style.background%3D'%23fff'%3B%0A%20%20container.style.border%3D'5px%20solid%20black'%3B%0A%20%20container.style.padding%3D'10px'%3B%0A%20%20container.style.top%3D'0'%3B%0A%20%20container.style.right%3D'0'%3B%0A%20%20container.style.zIndex%3D'999'%3B%0A%20%20document.body.appendChild(container)%3B%0A%7D%20else%20%7B%0A%20%20alert('No%20YouTube%20videos%20found')%3B%0A%7D}());

February 29, 2012

iPad bug: using CSS transitions on :hover prevents child element from showing

While building a CSS-powered drop-down navigation, I found a little iOS bug that prevented my main nav elements' sub-menus from showing on :hover. Consider the following markup and css, where a rollover on the outer <li> will reveal the inner <ul>.
<header class="admin_nav">
  <ul>
    <li>
      <a href="#" title="Users">
        <span class="label">Users</span>
      </a>
      <ul>
        <li>
          <a href="#" title="Map">Map</a>
        </li>
      </ul>
    </li>
  </ul>
</header>
header.admin_nav ul li ul {
  visibility:hidden;
  opacity:0;
  -webkit-transition: opacity 0.35s linear;
}

header.admin_nav ul li:hover ul {
  visibility:visible;
  opacity:1;
}
On desktop browsers, this is a nice way to fade in a sub-menu on :hover of the main menu item. But, having the -webkit-transition definition breaks the iPad's handling of this nicely-animated menu. Get rid of the transition, and it works just fine, albeit not quite as slickly.

January 5, 2012

Coffeescript: HTML5 <input> placeholder attribute fallback

You want to use the new HTML5 placeholder attribute on input fields, but you also want to support older browsers. Here's a little Coffeescript class that takes an HTML element in the constructor, and uses jQuery to bind the focus and blur events to swap out text like a modern browser would without the script.
# ## HTML5 placeholder feature fallback class
class PlaceholderFallback

  constructor: (el) ->
    @el = $(el)
    @initialize()
  
  # HTML5 <input> placeholder feature detection
  browserHasPlaceholder: =>
    "placeholder" of document.createElement("input")
  
  # Reads the placeholder attribute and uses it in a javascript fallback, if needed
  initialize: =>
    if @browserHasPlaceholder() == false
      placeholderText = @el.attr 'placeholder' 
      @el.removeAttr 'placeholder'
      @el.val(placeholderText)
      @el.focus (e) ->
        if this.value == placeholderText
          this.value = ''
      @el.blur (e) ->
        if this.value == ''
          this.value = placeholderText
    else
      @el = null

# Usage:
placeholderFallback = new PlaceholderFallback( element )