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