// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/*globals SC */
/** @class
(Document Your View Here)
@extends SC.View
*/
SC.MediaSlider = SC.SliderView.extend({
mediaView: null,
leftHandleInsetBinding: "*owner.leftHandleInset",
rightHandleInsetBinding: "*owner.rightHandleInset",
handleWidthBinding: "*owner.handleWidth",
//for some reason, when the observer was hooked to this bound property,
//it wouldn't fire even when the original setter was in an SC run loop!
//what gives and/or what was I doing wrong? The observer is now direct.
// loadedTimeRangesBinding: "*mediaView.loadedTimeRanges",
loadedRangesSelector: "span.sc-loaded-ranges",
_defaults: {
//these are necessary because I couldn't get SC.Binding.notEmpty to
//work without values defined in the parent, which is a situation
//I'd rather leave optional.
leftHandleInset:-1,
rightHandleInset:-8,
handleWidth:16
},
render: function(context, firstTime) {
//sc_super(); we are overriding the ENTIRE previous render method,
//so instead of calling sc_super, we want to skip the function
//immediately above this one and go two levels up.
arguments.callee.base.base.apply(this,arguments);
//some variables
var min = this.get('minimum'),
max = this.get('maximum'),
value = this.get('value'),
step = this.get('step'),
width = this.get('frame').width,
left, loc, minLoc, maxLoc, i,
blankImage = SC.BLANK_IMAGE_URL;
//process the defaults if we haven't.
if(!this._defaultsEnsured) {
this._defaultsEnsured = YES;
for(i in this._defaults) {
if(SC.none(this.get(i))) {
this.set(i,this._defaults[i]);
}
}
}
// determine the constrained value. Must fit within min & max
value = Math.min(Math.max(value, min), max);
// limit to step value
if (!SC.none(step) && step !== 0) {
value = Math.round(value / step) * step;
}
// get a pointer to the actual handle DOM element, and, if we have no
// width information for it, try to use its offsetWidth.
if(!firstTime) {
var handle = this.handleElement ||
(this.handleElement=this.$(this.get('handleSelector'))[0]);
if(!this.handleWidth){
this.handleWidth = this.handleElement.offsetWidth || this.handleWidth;
}
}
//derive the extremes
maxLoc = width-this.handleWidth/2-this.rightHandleInset;
minLoc = this.handleWidth/2+this.leftHandleInset;
if(min==max) loc=minLoc;
else loc = (value - min) / (max - min) * (maxLoc - minLoc) + minLoc;
left = loc + this.leftHandleInset - this.handleWidth/2;
//console.log("RENDERING: max is "+max+" and min is "+min+" and value is "+value+" out of "+max+", left is "+left+" and loc is "+loc+" and handleWidth is "+this.handleWidth);
if(!firstTime) {
//if all our elements are already created, just edit them instead of
//blowing them away and starting over.
handle.style.left = left + "px";
} else {
context.push('',
'',
'',
'',
'',
'',
'');
}
//next, we take care of the loaded ranges of time that will darken
//the slider. This should happen whether it's the first render or not!
this.renderLoadedTimeRanges();
},
renderLoadedTimeRanges: function() {
//first, get our quick variables we know we'll need...
var max = this.get('maximum'),
min = this.get('minimum'),
width = this.get('frame').width,
i;
//get our ranges, which will be an array of Numbers with twice as many
//elements as there are ranges. odd indices are starts, and evens are the
//ends that correspond to the start times immediately preceding them.
//only act if we have the ranges array. if it doesn't exist, don't bother
//to render the loaded ranges.
var mediaView = this.get('mediaView');
if(!mediaView) return;
var ranges = mediaView.get('loadedTimeRanges');
if(!ranges) return;
//make sure we have the pointer to our element which will house all the
//clones of the loaded-style background:
if(!this.loadedRangesElement){
this.loadedRangesElement = this.$(this.get('loadedRangesSelector'))[0];
}
//now, make sure we have a loaded-style slider background template, which
//will be cloned as many times as is necessary to render all loaded ranges:
if(!this.loadedBGTemplate) {
this.loadedBGTemplate = document.createElement('span');
this.loadedBGTemplate.className = "sc-loaded-range";
this.loadedBGTemplate.innerHTML = [
'',
'',
'',
'',
''
].join('');
}
//now, make sure we have our cache of clones of the slider background
//template. we don't want to be spawning new HTML elements every time
//we render, so we will use this pool of them.
if(!this.loadedRangeElements) this.loadedRangeElements = [];
//now, for each range, obtain or create a clone of the background
//template, and style it such that the inner element lands
//exactly on top of the original slider background, but clipped
//by the outer element.
for(i=0; i=0.01) this.set('value', loc); // adjust
return YES ;
},
mouseDown: function(evt) {
var media=this.get('mediaView');
if(media) media.startSeek();
return sc_super();
},
mouseUp: function(evt) {
var media=this.get('mediaView');
if(media) media.endSeek();
return sc_super();
},
mouseWheel: function(){
var media, ret;
SC.RunLoop.begin();
media=this.get('mediaView');
if(media) media.startSeek();
ret = sc_super();
SC.RunLoop.end();
SC.RunLoop.begin();
if(media) media.endSeek();
SC.RunLoop.end();
return ret;
}
});