1 /** The osmplayer namespace. */
  2 var osmplayer = osmplayer || {};
  3 
  4 /**
  5  * @constructor
  6  * @extends minplayer.display
  7  * @class This class provides the scroll functionality for the playlists.
  8  *
  9  * We can calculate how the scrollbar controls the playlist using the
 10  * following diagram / equations.
 11  *  ___ ____________
 12  *  |  |            |\
 13  *  |  |    list    | \
 14  *  |  |            |y \
 15  *  |  |            |   \
 16  *  |  |____________|    \ _ _____
 17  *  |  |            |\    | |    |
 18  *  |  |            | \   | |    |
 19  *  |  |            |  \  | |x   |
 20  *  |  |            |   \ | |    |
 21  *  |  |            |    \|_|_   |
 22  *  |  |            |     | | |  |
 23  *  l  |   window   |     | | h  w
 24  *  |  |            |     |_|_|  |
 25  *  |  |            |    /| |    |
 26  *  |  |            |   / | |    |
 27  *  |  |            |  / v| |    |
 28  *  |  |            | /   | |    |
 29  *  |  |____________|/    |_|____|
 30  *  |  |            |    /
 31  *  |  |            |   /
 32  *  |  |            |  /
 33  *  |  |            | /
 34  *  |__|____________|/
 35  *
 36  *  l - The list height.
 37  *  h - Handle Bar height.
 38  *  w - Window height.
 39  *  x - The distance from top of window to the top of the handle.
 40  *  y - The disatnce from the top of the list to the top of the window.
 41  *  v - The distance from bottom of window to the bottom of the handle.
 42  *
 43  *  jQuery UI provides "v".  We already know "l", "h", "w".  We can then
 44  *  calculate the relationship between the scroll bar handle position to the
 45  *  list position using the following equations.
 46  *
 47  *  x = (w - (v + h))
 48  *  y = ((l - w)/(w - h)) * x
 49  *
 50  *   -- or --
 51  *
 52  *  y = ((l - w)/(w - h)) * (w - (v + h))
 53  *
 54  *  We can statically calculate the ((l - w)/(w - h)) as a ratio and use
 55  *  that to speed up calculations as follows.
 56  *
 57  *  ratio = ((l - w)/(w - h));
 58  *
 59  *  So, our translation equations are as follows...
 60  *
 61  *    y = ratio * (w - (v + h))
 62  *    v = w - (h + (y / ratio))
 63  *
 64  *
 65  * @param {object} context The jQuery context.
 66  * @param {object} options This components options.
 67  */
 68 osmplayer.scroll = function(context, options) {
 69 
 70   // Derive from display
 71   minplayer.display.call(this, 'scroll', context, options);
 72 };
 73 
 74 /** Derive from minplayer.display. */
 75 osmplayer.scroll.prototype = new minplayer.display();
 76 
 77 /** Reset the constructor. */
 78 osmplayer.scroll.prototype.constructor = osmplayer.scroll;
 79 
 80 /**
 81  * @see minplayer.plugin#construct
 82  */
 83 osmplayer.scroll.prototype.construct = function() {
 84 
 85   // Make sure we provide default options...
 86   this.options = jQuery.extend({
 87     vertical: true,
 88     hysteresis: 40,
 89     scrollSpeed: 20,
 90     scrollMode: 'auto'
 91   }, this.options);
 92 
 93   // Call the minplayer plugin constructor.
 94   minplayer.display.prototype.construct.call(this);
 95 
 96   // Make this component orientation agnostic.
 97   this.pos = this.options.vertical ? 'pageY' : 'pageX';
 98   this.offset = this.options.vertical ? 'top' : 'left';
 99   this.margin = this.options.vertical ? 'marginTop' : 'marginLeft';
100   this.size = this.options.vertical ? 'height' : 'width';
101   this.outer = this.options.vertical ? 'outerHeight' : 'outerWidth';
102 
103   this.getMousePos = function(event) {
104     return (event[this.pos] - this.display.offset()[this.offset]);
105   };
106   this.getPos = function(handlePos) {
107     if (this.options.vertical) {
108       return this.ratio * (this.scrollSize - (handlePos + this.handleSize));
109     }
110     else {
111       return this.ratio * handlePos;
112     }
113   };
114   this.getHandlePos = function(pos) {
115     if (this.options.vertical) {
116       return this.scrollSize - (this.handleSize + (pos / this.ratio));
117     }
118     else {
119       return (pos / this.ratio);
120     }
121   };
122 
123   // If they have a scroll bar.
124   if (this.elements.scroll) {
125 
126     // Get the values of our variables.
127     var scroll = this.elements.scroll;
128     this.handleSize = 0;
129     this.scrollTop = 0;
130     this.mousePos = 0;
131 
132     // Refresh the scroll.
133     this.refresh();
134 
135     // Create the scroll bar slider control.
136     this.scroll = scroll.slider({
137       orientation: this.options.vertical ? 'vertical' : 'horizontal',
138       max: this.scrollSize,
139       create: (function(scroll, vertical) {
140         return function(event, ui) {
141           var handle = jQuery('.ui-slider-handle', event.target);
142           scroll.handleSize = handle[scroll.outer]();
143           scroll.scrollTop = (scroll.scrollSize - scroll.handleSize);
144           var initValue = vertical ? scroll.scrollTop : 0;
145           jQuery(this).slider('option', 'value', initValue);
146         };
147       })(this, this.options.vertical),
148       slide: (function(scroll, vertical) {
149         return function(event, ui) {
150           // Get the new position.
151           var pos = scroll.getPos(ui.value);
152 
153           // Ensure it doesn't go over the limits.
154           if (vertical && (pos < 0)) {
155             scroll.scroll.slider('option', 'value', scroll.scrollTop);
156             return false;
157           }
158           else if (!vertical && (ui.value > scroll.scrollTop)) {
159             scroll.scroll.slider('option', 'value', scroll.scrollTop);
160             return false;
161           }
162 
163           // Set our list position.
164           scroll.elements.list.css(scroll.margin, -pos + 'px');
165           return true;
166         };
167       })(this, this.options.vertical)
168     });
169 
170     // If they wish to have auto scroll mode.
171     if (this.options.scrollMode == 'auto') {
172 
173       // Bind to the mouse events.
174       this.elements.list.bind('mousemove', (function(scroll) {
175 
176         // Return our event function.
177         return function(event) {
178           event.preventDefault();
179           scroll.mousePos = event[scroll.pos];
180           scroll.mousePos -= scroll.display.offset()[scroll.offset];
181         };
182 
183       })(this)).bind('mouseenter', (function(scroll) {
184 
185         // Return our event function.
186         return function(event) {
187           event.preventDefault();
188           scroll.scrolling = true;
189           setTimeout(function setScroll() {
190             if (scroll.scrolling) {
191 
192               // Get the delta.
193               var delta = scroll.mousePos - scroll.scrollMid;
194 
195               // Determine if we are within our hysteresis.
196               if (Math.abs(delta) > scroll.options.hysteresis) {
197 
198                 // Get the hysteresis and delta.
199                 var hyst = scroll.options.hysteresis;
200                 hyst *= (delta > 0) ? -1 : 0;
201                 delta = (scroll.options.scrollSpeed * (delta + hyst));
202                 delta /= scroll.scrollMid;
203 
204                 // Get the scroll position.
205                 var pos = scroll.elements.list.css(scroll.margin);
206                 pos = parseFloat(pos) - delta;
207                 pos = (pos > 0) ? 0 : pos;
208 
209                 // Get the maximum top position.
210                 var top = -scroll.listSize + scroll.scrollSize;
211                 pos = (pos < top) ? top : pos;
212 
213                 // Set the new scroll position.
214                 scroll.elements.list.css(scroll.margin, pos + 'px');
215 
216                 // Set the scroll position.
217                 pos = scroll.getHandlePos(-pos);
218                 scroll.scroll.slider('option', 'value', pos);
219               }
220 
221               // Set timeout to try again.
222               setTimeout(setScroll, 20);
223             }
224           }, 20);
225         };
226 
227       })(this)).bind('mouseleave', (function(scroll) {
228 
229         // Return our event function.
230         return function(event) {
231           event.preventDefault();
232           scroll.scrolling = false;
233         };
234 
235       })(this));
236     }
237   }
238 };
239 
240 /**
241  * Refreshes the scroll list.
242  */
243 osmplayer.scroll.prototype.refresh = function() {
244 
245   // The list size.
246   if (this.options.vertical) {
247     this.listSize = this.elements.list.height();
248   }
249   else {
250     this.listSize = 0;
251     jQuery.each(this.elements.list.children(), (function(scroll) {
252       return function() {
253         scroll.listSize += jQuery(this)[scroll.outer]();
254       };
255     })(this));
256 
257     // Set the list size.
258     this.elements.list[this.size](this.listSize);
259   }
260 
261   // Refresh the list.
262   this.onResize();
263 
264   // Set the scroll position.
265   if (this.scroll) {
266     this.elements.list.css(this.margin, '0px');
267     this.scroll.slider('option', 'value', this.getHandlePos(0));
268   }
269 };
270 
271 /**
272  * Refresh all the variables that may change.
273  */
274 osmplayer.scroll.prototype.onResize = function() {
275   this.scrollSize = this.elements.scroll[this.size]();
276   this.scrollMid = this.scrollSize / 2;
277   this.scrollTop = (this.scrollSize - this.handleSize);
278   this.ratio = (this.listSize - this.scrollSize);
279   this.ratio /= (this.scrollSize - this.handleSize);
280   if (this.scroll) {
281     this.scroll.slider('option', 'max', this.scrollSize);
282   }
283 };
284