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 )

December 19, 2011

CSS: Disable text selection highlighting

When working with touch & mouse events to drag html elements, I've occasionally resorted to disabling clicking/touching on child elements by doing something like this:
elem.onmousedown = function(e){ return false; };
elem.onselectstart = function(){ return false; };
But sometimes you want to be able to interact with those child elements... I found some nice CSS over here that prevents text & element highlighting when you're dragging with a mousemove or a touchmove event:
p {
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -o-user-select: none;
  user-select: none;
}

December 11, 2011

Coffeescript: MapsLoader class for asynchronous loading of Google Maps API

I'm using CoffeeScript for my current project, and we needed a way to load the Google Maps API when a user hits a particular view. This static class is auto-initialized, and all you need to call is: MapsLoader.load(callbackFunction,true). If the API has already loaded, it will invoke your callback immediately. Make sure to pass the appropriate boolean if the user is on a mobile device (true), or a desktop browser (false).
class MapsLoader

  constructor: ->

  load: (successCallback,isMobileDevice) ->
    @isMobileDevice = isMobileDevice
    @successCallback = successCallback
    if @hasLoaded != true
      @loadGoogle()
    else
      @mapsLoaded()

  loadGoogle: =>
    # reference google loader callback to local method - clean up after callback
    window.loadMaps = @loadMaps 
    apiKey = "-----your-api-key-here-----"
    script = document.createElement("script")
    script.src = "https://www.google.com/jsapi?key=#{apiKey}&callback=loadMaps"
    script.type = "text/javascript"
    document.getElementsByTagName("head")[0].appendChild(script)

  loadMaps: =>
    otherParams = if @isMobileDevice then "sensor=true" else "sensor=false"
    google.load("maps", "3", {other_params: otherParams, "callback" : @mapsLoaded});

  mapsLoaded: =>
    @hasLoaded = true
    window.loadMaps = null
    if @successCallback
      @successCallback()
    @successCallback = null

@MapsLoader = new MapsLoader()