1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * This view consists of two nested divs. The outer one has a horizontal 7 * scrollbar and the inner one has a height of 1 pixel and a width set to 8 * allow an appropriate scroll range. The view reports scroll events to 9 * a callback specified on construction. 10 * 11 * All this funkiness is necessary because there is no HTML scroll control. 12 * TODO(mmenke): Consider implementing our own scrollbar directly. 13 */ 14var HorizontalScrollbarView = (function() { 15 'use strict'; 16 17 // We inherit from DivView. 18 var superClass = DivView; 19 20 /** 21 * @constructor 22 */ 23 function HorizontalScrollbarView(divId, innerDivId, callback) { 24 superClass.call(this, divId); 25 this.callback_ = callback; 26 this.innerDiv_ = $(innerDivId); 27 $(divId).onscroll = this.onScroll_.bind(this); 28 29 // The current range and position of the scrollbar. Because DOM updates 30 // are asynchronous, the current state cannot be read directly from the DOM 31 // after updating the range. 32 this.range_ = 0; 33 this.position_ = 0; 34 35 // The DOM updates asynchronously, so sometimes we need a timer to update 36 // the current scroll position after resizing the scrollbar. 37 this.updatePositionTimerId_ = null; 38 } 39 40 HorizontalScrollbarView.prototype = { 41 // Inherit the superclass's methods. 42 __proto__: superClass.prototype, 43 44 setGeometry: function(left, top, width, height) { 45 superClass.prototype.setGeometry.call(this, left, top, width, height); 46 this.setRange(this.range_); 47 }, 48 49 show: function(isVisible) { 50 superClass.prototype.show.call(this, isVisible); 51 }, 52 53 /** 54 * Sets the range of the scrollbar. The scrollbar can have a value 55 * anywhere from 0 to |range|, inclusive. The width of the drag area 56 * on the scrollbar will generally be based on the width of the scrollbar 57 * relative to the size of |range|, so if the scrollbar is about the size 58 * of the thing we're scrolling, we get fairly nice behavior. 59 * 60 * If |range| is less than the original position, |position_| is set to 61 * |range|. Otherwise, it is not modified. 62 */ 63 setRange: function(range) { 64 this.range_ = range; 65 setNodeWidth(this.innerDiv_, this.getWidth() + range); 66 if (range < this.position_) 67 this.position_ = range; 68 this.setPosition(this.position_); 69 }, 70 71 /** 72 * Sets the position of the scrollbar. |position| must be between 0 and 73 * |range_|, inclusive. 74 */ 75 setPosition: function(position) { 76 this.position_ = position; 77 this.updatePosition_(); 78 }, 79 80 /** 81 * Updates the visible position of the scrollbar to be |position_|. 82 * On failure, calls itself again after a timeout. This is needed because 83 * setRange does not synchronously update the DOM. 84 */ 85 updatePosition_: function() { 86 // Clear the timer if we have one, so we don't have two timers running at 87 // once. This is safe even if we were just called from the timer, in 88 // which case clearTimeout will silently fail. 89 if (this.updatePositionTimerId_ !== null) { 90 window.clearTimeout(this.updatePositionTimerId_); 91 this.updatePositionTimerId_ = null; 92 } 93 94 this.getNode().scrollLeft = this.position_; 95 if (this.getNode().scrollLeft != this.position_) { 96 this.updatePositionTimerId_ = 97 window.setTimeout(this.updatePosition_.bind(this)); 98 } 99 }, 100 101 getRange: function() { 102 return this.range_; 103 }, 104 105 getPosition: function() { 106 return this.position_; 107 }, 108 109 onScroll_: function() { 110 // If we're waiting to update the range, ignore messages from the 111 // scrollbar. 112 if (this.updatePositionTimerId_ !== null) 113 return; 114 var newPosition = this.getNode().scrollLeft; 115 if (newPosition == this.position_) 116 return; 117 this.position_ = newPosition; 118 this.callback_(); 119 } 120 }; 121 122 return HorizontalScrollbarView; 123})(); 124 125