// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * This view consists of two nested divs. The outer one has a horizontal * scrollbar and the inner one has a height of 1 pixel and a width set to * allow an appropriate scroll range. The view reports scroll events to * a callback specified on construction. * * All this funkiness is necessary because there is no HTML scroll control. * TODO(mmenke): Consider implementing our own scrollbar directly. */ var HorizontalScrollbarView = (function() { 'use strict'; // We inherit from DivView. var superClass = DivView; /** * @constructor */ function HorizontalScrollbarView(divId, innerDivId, callback) { superClass.call(this, divId); this.callback_ = callback; this.innerDiv_ = $(innerDivId); $(divId).onscroll = this.onScroll_.bind(this); // The current range and position of the scrollbar. Because DOM updates // are asynchronous, the current state cannot be read directly from the DOM // after updating the range. this.range_ = 0; this.position_ = 0; // The DOM updates asynchronously, so sometimes we need a timer to update // the current scroll position after resizing the scrollbar. this.updatePositionTimerId_ = null; } HorizontalScrollbarView.prototype = { // Inherit the superclass's methods. __proto__: superClass.prototype, setGeometry: function(left, top, width, height) { superClass.prototype.setGeometry.call(this, left, top, width, height); this.setRange(this.range_); }, show: function(isVisible) { superClass.prototype.show.call(this, isVisible); }, /** * Sets the range of the scrollbar. The scrollbar can have a value * anywhere from 0 to |range|, inclusive. The width of the drag area * on the scrollbar will generally be based on the width of the scrollbar * relative to the size of |range|, so if the scrollbar is about the size * of the thing we're scrolling, we get fairly nice behavior. * * If |range| is less than the original position, |position_| is set to * |range|. Otherwise, it is not modified. */ setRange: function(range) { this.range_ = range; setNodeWidth(this.innerDiv_, this.getWidth() + range); if (range < this.position_) this.position_ = range; this.setPosition(this.position_); }, /** * Sets the position of the scrollbar. |position| must be between 0 and * |range_|, inclusive. */ setPosition: function(position) { this.position_ = position; this.updatePosition_(); }, /** * Updates the visible position of the scrollbar to be |position_|. * On failure, calls itself again after a timeout. This is needed because * setRange does not synchronously update the DOM. */ updatePosition_: function() { // Clear the timer if we have one, so we don't have two timers running at // once. This is safe even if we were just called from the timer, in // which case clearTimeout will silently fail. if (this.updatePositionTimerId_ !== null) { window.clearTimeout(this.updatePositionTimerId_); this.updatePositionTimerId_ = null; } this.getNode().scrollLeft = this.position_; if (this.getNode().scrollLeft != this.position_) { this.updatePositionTimerId_ = window.setTimeout(this.updatePosition_.bind(this)); } }, getRange: function() { return this.range_; }, getPosition: function() { return this.position_; }, onScroll_: function() { // If we're waiting to update the range, ignore messages from the // scrollbar. if (this.updatePositionTimerId_ !== null) return; var newPosition = this.getNode().scrollLeft; if (newPosition == this.position_) return; this.position_ = newPosition; this.callback_(); } }; return HorizontalScrollbarView; })();