Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @param {WebInspector.ViewportControl.Provider} provider
     34  */
     35 WebInspector.ViewportControl = function(provider)
     36 {
     37     this.element = document.createElement("div");
     38     this.element.className = "fill";
     39     this.element.style.overflow = "auto";
     40     this._topGapElement = this.element.createChild("div");
     41     this._contentElement = this.element.createChild("div");
     42     this._bottomGapElement = this.element.createChild("div");
     43 
     44     this._provider = provider;
     45     this.element.addEventListener("scroll", this._onScroll.bind(this), false);
     46     this._firstVisibleIndex = 0;
     47     this._lastVisibleIndex = -1;
     48 }
     49 
     50 /**
     51  * @interface
     52  */
     53 WebInspector.ViewportControl.Provider = function()
     54 {
     55 }
     56 
     57 WebInspector.ViewportControl.Provider.prototype = {
     58     /**
     59      * @return {number}
     60      */
     61     itemCount: function() { return 0; },
     62 
     63     /**
     64      * @param {number} index
     65      * @return {Element}
     66      */
     67     itemElement: function(index) { return null; }
     68 }
     69 
     70 WebInspector.ViewportControl.prototype = {
     71     /**
     72      * @return {Element}
     73      */
     74     contentElement: function()
     75     {
     76         return this._contentElement;
     77     },
     78 
     79     refresh: function()
     80     {
     81         if (!this.element.clientHeight)
     82             return;  // Do nothing for invisible controls.
     83 
     84         // Secure scroller.
     85         this._contentElement.style.setProperty("height", "100000px");
     86         this._contentElement.removeChildren();
     87         var itemCount = this._provider.itemCount();
     88         if (!itemCount) {
     89             this._firstVisibleIndex = -1;
     90             this._lastVisibleIndex = -1;
     91             return;
     92         }
     93 
     94         if (!this._rowHeight) {
     95             var firstElement = this._provider.itemElement(0);
     96             this._rowHeight = firstElement.measurePreferredSize(this._contentElement).height;
     97         }
     98 
     99         var visibleFrom = this.element.scrollTop;
    100         var visibleTo = visibleFrom + this.element.clientHeight;
    101 
    102         this._firstVisibleIndex = Math.floor(visibleFrom / this._rowHeight);
    103         this._lastVisibleIndex = Math.min(Math.ceil(visibleTo / this._rowHeight), itemCount) - 1;
    104 
    105         this._topGapElement.style.height = (this._rowHeight * this._firstVisibleIndex) + "px";
    106         this._bottomGapElement.style.height = (this._rowHeight * (itemCount - this._lastVisibleIndex - 1)) + "px";
    107 
    108         for (var i = this._firstVisibleIndex; i <= this._lastVisibleIndex; ++i)
    109             this._contentElement.appendChild(this._provider.itemElement(i));
    110         // Release scroller protection.
    111         this._contentElement.style.removeProperty("height");
    112     },
    113 
    114     /**
    115      * @param {Event} event
    116      */
    117     _onScroll: function(event)
    118     {
    119         this.refresh();
    120     },
    121 
    122     /**
    123      * @return {number}
    124      */
    125     rowsPerViewport: function()
    126     {
    127         return Math.floor(this.element.clientHeight / this._rowHeight);
    128     },
    129 
    130     /**
    131      * @return {number}
    132      */
    133     firstVisibleIndex: function()
    134     {
    135         return this._firstVisibleIndex;
    136     },
    137 
    138     /**
    139      * @return {number}
    140      */
    141     lastVisibleIndex: function()
    142     {
    143         return this._lastVisibleIndex;
    144     },
    145 
    146     /**
    147      * @return {?Element}
    148      */
    149     renderedElementAt: function(index)
    150     {
    151         if (index < this._firstVisibleIndex)
    152             return null;
    153         if (index > this._lastVisibleIndex)
    154             return null;
    155         return this._contentElement.childNodes[index - this._firstVisibleIndex];
    156     },
    157 
    158     /**
    159      * @param {number} index
    160      * @param {boolean=} makeLast
    161      */
    162     scrollItemIntoView: function(index, makeLast)
    163     {
    164         if (index > this._firstVisibleIndex && index < this._lastVisibleIndex)
    165             return;
    166 
    167         if (makeLast)
    168             this.element.scrollTop = this._rowHeight * (index + 1) - this.element.clientHeight;
    169         else
    170             this.element.scrollTop = this._rowHeight * index;
    171     }
    172 }
    173