blob: 5ec36aa4d68a1aaa1ca5ae8f924cd86e26fdc3a3 [file] [log] [blame]
// 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;
})();