# Froogaloopelise - A Vimeo Wrapper

I have developed a tiny bit of JavaScript to quickly output a Froogaloop Vimeo video and some basic controls attached with almost no effort. You don't have to include the script from Vimeo (the script will add it for you) and it will create only the very basics of controls for now - although they can be amended. I also have a basic CSS (SASS) file that you can choose to omit as the output is simply a <ul> element you can style and nested <span> elements for scrubbers.

I was wondering what could be improved in this code. Is it a smart idea to not make this a Constructor but simply a function that you can keep calling? My thoughts were that you only want one controlling instance, although I guess it would be nice if you could programmatically undo your changes. Is it bad that there are no options or is it fine if people use CSS to hide the unneeded elements? Is the code well commented or is it lacking information? (For example, I wanted to comment on the DOM creating code, but I find it speaks for itself and adding anything would make it harder to read instead of clearer).

I cannot include a video here as Stack Overflow throws Cross origin errors, so I have not included the CSS below, but you can find more about this code at GitHub.

// Froogaloopelise(querySelectorLString) -> true;
// Initialise a request for a Froogaloopelise wrapper
function Froogaloopelise(querySelector){
// If the API has not yet loaded, create and add to queue
Froogaloopelise.queue.push(querySelector);
return;
}

// Initialise iFrames
for(
var i = 0, iframes = document.querySelectorAll(querySelector);
i < iframes.length;
i++
){

var iframe, id, src;
iframe = iframes.item(i);
// Set an id;
id  = Froogaloopelise.getId(iframe);
iframe.setAttribute('id', id);

// Set the api and id for the player
src = iframe.getAttribute('src');
src = src.indexOf('?') >= 0 ? src + '&' : src + '?';
src = src + 'api=1&player_id=' + id;

iframe.setAttribute('src', src);

// Create the meaty DOM
iframe = Froogaloopelise.addUI(iframe, id);

}
return true;
}
// Basic Stored Variables
Froogaloopelise.id = 1;
Froogaloopelise.queue = [];

// Froogaloopelise.getId([element:<iframe>]) -> String
// Get a unique Id or return the already existing id of the element
Froogaloopelise.getId = function(element){
return element && element.getAttribute('id')
? element.getAttribute('id')
: 'froogaloop-' + Froogaloopelise.id++;
};

// Froogaloopelise.executeQueue() -> Void
// Execute whatever is stacked in the queue since start
Froogaloopelise.executeQueue = function(){
Froogaloopelise.queue.forEach(Froogaloopelise);
}

// Froogaloopelise.addScript() -> Void
// Add the froogaloop api script to the <head>, wait for it to load

return false;

var script          = document.createElement('script');
script.id       = 'froogaloop-script';
script.type     = 'text/javascript';
script.src      = 'https://f.vimeocdn.com/js/froogaloop2.min.js';
Froogaloopelise.executeQueue();
}, false);

}
// Froogaloopelise.FullScreenAPI
// Fullscreen API with prefixing.
Froogaloopelise.FullScreenAPI = {
fullScreenElement: false,
requestFulLScreen: function(element){
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else {
return false;
}
this.fullScreenElement = element;
this.fullScreenElement.removeAttribute('data-is-fullscreen');
this.fullScreenElement = false;
}.bind(this), false)
this.fullScreenElement.setAttribute('data-is-fullscreen', 'true');
return true;
},
exitFullScreen: function(){
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else {
return false;
}
return true;
}
}
// Froogaloopelise.addUI(iframe:<node>) -> <node>
// Adds DOM elements and wraps into wrapper

var wrapper, list, froogaloop, duration;

wrapper = document.createElement('div');
wrapper .className = '[ froogaloop froogaloop-wrapper ]'
iframe  .parentNode.insertBefore(wrapper, iframe);
iframe  .parentNode.removeChild(iframe);
wrapper .appendChild(iframe);

function resizeIframe(){
iframe.width = wrapper.clientWidth;
iframe.height = wrapper.clientWidth / 16 * 9;
}

resizeIframe();

list = document.createElement('ul');
wrapper .appendChild(list);

$f(iframe).addEvent('ready', function(id){ froogaloop =$f(id);
froogaloop.api('getDuration', function(v){ duration = v; });
var playpause;

// Play/Pause Button
playpause = document.createElement('li');
playpause .className = '[ froogaloop play-pause state: paused ]';
playpause .textContent = 'Play';
froogaloop.api('paused', function(paused){
if(paused)
froogaloop.api('play');
else
froogaloop.api('pause');
});
});
list.appendChild(playpause);

playpause.textContent = 'Pause';
playpause.className = '[ froogaloop play-pause state: playing ]';
});
playpause.textContent = 'Play';
playpause.className = '[ froogaloop play-pause state: paused ]';
});

var rewind;

rewind = document.createElement('li');
rewind .className = '[ froogaloop rewind ]';
rewind .textContent = 'Rewind';
froogaloop.api('seekTo', 0);
});
list.appendChild(rewind);

var durationScrubber, durationScrubberPassed, durationMouseEvent;

function durationScrubHandler(event){
if(!durationMouseEvent || !duration || !durationScrubber)
return;
var offsetParent = durationScrubber,
left = event.pageX - durationScrubber.offsetLeft;
while(offsetParent.offsetParent){
offsetParent = offsetParent.offsetParent;
left -= offsetParent.offsetLeft;
}
froogaloop.api('seekTo', duration * left / durationScrubber.offsetWidth);
}

durationScrubber = document.createElement('li');
durationScrubber .className = '[ froogaloop scrubber seek ]';
durationScrubberPassed = document.createElement('span');
durationScrubberPassed .className = '[ froogaloop scrubber seeker ]';
durationScrubberPassed .textContent = 'Seek';
durationScrubberPassed .style.width = '0%';
durationScrubber .appendChild(durationScrubberPassed);
durationMouseEvent = true;
durationScrubHandler(event);
}, false);
durationMouseEvent = false;
}, false);
durationMouseEvent = false;
}, false);
durationScrubber .addEventListener('mousemove', durationScrubHandler, false);
list.appendChild(durationScrubber);

durationScrubberPassed.style.width = (data.percent * 100) + '%';
});

var volumeScrubber, volumeScrubberPassed, volumeMouseEvent;

function volumeScrubHandler(event){
if(!volumeMouseEvent || !volumeScrubber) return;
var offsetParent = volumeScrubber,
left = event.pageX - volumeScrubber.offsetLeft;
while(offsetParent.offsetParent){
offsetParent = offsetParent.offsetParent;
left -= offsetParent.offsetLeft;
}
left = Math.round(left / volumeScrubber.offsetWidth * 10);
volumeScrubberPassed.style.width = (left * 10) + '%';
froogaloop.api('setVolume', left / 10);
}

volumeScrubber = document.createElement('li');
volumeScrubber .className = '[ froogaloop scrubber volume ]';
volumeScrubberPassed = document.createElement('span');
volumeScrubberPassed .className = '[ froogaloop scrubber volume ]';
volumeScrubberPassed .textContent = 'Volume';
volumeScrubberPassed .style.width = '50%';
volumeScrubber .appendChild(volumeScrubberPassed);
volumeMouseEvent = true;
volumeScrubHandler(event);
}, false);
volumeMouseEvent = false;
}, false);
volumeMouseEvent = false;
}, false);
volumeScrubber .addEventListener('mousemove', volumeScrubHandler, false);
list.appendChild(volumeScrubber);

var fullscreen, isFullScreen = false;

// Play/Pause Button
fullscreen = document.createElement('li');
fullscreen .className = '[ froogaloop fullscreen ]';
fullscreen .textContent = 'Fullscreen';
if(!isFullScreen && Froogaloopelise.FullScreenAPI.requestFulLScreen(wrapper))
isFullScreen = wrapper;
else if(isFullScreen && Froogaloopelise.FullScreenAPI.exitFullScreen(wrapper))
isFullScreen = false;
});
list.appendChild(fullscreen);

});

return wrapper;
}

• First thing I've noticed is you make this thing a constructor but then you invoke everything statically... this does not make sense. :P – Dan Pantry Sep 24 '15 at 11:59
• @Aredherring it's not actually a constructor, its a function with methods attached to it but it cannot be instantiated using 'new'. You call it with Froogaloopelise('iframe') but every one of those is not actually an instance, just a conversion to the new UI. – somethinghere Sep 24 '15 at 12:01
• Oh, excuse me, I was confused (because it was a capital function..) – Dan Pantry Sep 24 '15 at 12:01
• @Aredherring I didn't actually know that was a convention, I have never really noticed. I usually capitalise functions that are not part of custom code but of libraries, so when using them it's obvious that you declared it yourself or imported it as a separate file. – somethinghere Sep 24 '15 at 12:03
• @ARedHerring that's certainly a good way to do it. Something that I want to mention (I know, pernickety, but I keep hearing it) what you refer to is dromedaryCase (one bulge in the middle). This is CamelCase :) But thanks for the reply! (And don't take that thing about the case as critique, I just wanted to mention it :) ) – somethinghere Sep 24 '15 at 14:17

This isn't a comprehensive review, but a few things I noticed may be helpful:

There are a couple repetitive if else statements that can be condensed. For example, the following code:

   exitFullScreen: function(){
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else {
return false;
}
return true;
}


can be condensed:

exitFullScreen: function() {
var methodName = ['exitFullscreen', 'msExitFullscreen', 'mozExitFullScreen', 'webkitExitFullscreen']
.filter(function(x) { return document[x] })[0];
return methodName ? (!!document[methodName]() || true) : false;
}


Note that the possibly cryptic (!!document[methodName]() || true) simply calls the defined method, and then returns true, regardless of what the method itself returns. The double bang !! simply coerces the returned value to a boolean.

The array recreation on each call shouldn't matter from a performance perspective, and is worth the conciseness. You might also consider separating out the detection of vendor prefix into a method that could be reused by all the pieces of code which branch on vendor.

As a more general note, the functions are long, which makes their overall purpose hard to grasp. A simple way to improve this is to move the sub-parts into named inner functions. This allows you to keep your comments in code (via well named functions), and also to separate high-level logic and implementation. Here's just one small section rewritten in this style:

// Now you can see an outline of what the code does all

// And commented code sections are replaced with well-named functions
// encapsulating the implementation detail
// Play/Pause Button
playpause = document.createElement('li');
playpause .className = '[ froogaloop play-pause state: paused ]';
playpause .textContent = 'Play';
froogaloop.api('paused', function(paused){
// User ternary operator instead of 4 line if else
paused ? froogaloop.api('play') : froogaloop.api('pause');
});
});
list.appendChild(playpause);
}

playpause.textContent = 'Pause';
playpause.className = '[ froogaloop play-pause state: playing ]';
});
}


• Checking methodName !== undefined after doing document[methodName].call(); seems wrong. – 200_success Sep 25 '15 at 7:11
• Are you sure? If no method is found, it's supposed to return false. – 200_success Sep 25 '15 at 14:19