/*
    ScrollableLayer: class for scroll areas
    The function of the ScrollableLayer is threefold:
    - provide content scrolling behaviour, both horizontal and vertical
    - provide content switching behaviour with inline content blocks as source
    - provide content switching behaviour with URls as source; with history
    Remark: content switching by URL or inline blocks are mutually exclusive
    TODO: the content switching should be refactored to another class

    Author: Theo M. Gerrits (tmg)

    Dependencies: Logger.js
                  Timer.js
                  Layer.js

    JavaScript1.5

    Copyright (C) 2005 Ontwerpfontein
*/

/*
Description     : constructor
                  A ScrollableLayer consists of
                  - a base Layer with id <id>,
                  - a viewport Layer with id <id>Viewport,
                  - a content Layer with id <id>Content,
                  - optional border Layers with id <id>Border<X>, where <X> is T, L, B, or R.
                  - Instead of the content layer, there may be content Layers with id <id>Block<n>, where n is a non-negative integer.
                    These 'Block' Layers are used to switch the scrollable content dynamically.
                  - For dynamic URL loading, there may be a hidden IFRAME, to load the HTML content, which can then be used to set the content layer
                  The border of the Scrollable layer is defined as lying inside the base layer and outside the margin area
                  The margin (area) is defined as lying inside the border and outside the viewport layer
                  The viewport layer always has a clipping area equal to the layer area itself
Remark:         : The base, viewport and border layers are not needed; ### these should be refactored out!
Parameters      : id: the ScrollableLayer object id
                  x: the left position of the topleft corner of the base Layer in pixels
                  y: the top position of the topleft corner of the base Layer in pixels
                  width: the width of the base Layer in pixels
                  height: the height of the base Layer in pixels
Properties      : id: the ScrollableLayer object id
                  x: the left position of the topleft corner of the base Layer in pixels
                  y: the top position of the topleft corner of the base Layer in pixels
                  width: the width of the base Layer in pixels
                  height: the height of the base Layer in pixels
                  globalName: globally stored id (in the top window)

    set by setScrollSpeed():
                  scrollStep: the distance to scroll per scroll step in pixels
                  scrollStepDuration: the duration of 1 scroll step in milliseconds

    set by createComponents():
                  baseLayer: the base Layer
                  viewportLayer: the viewport Layer
                  contentLayer: the optional content Layer
                  blockLayers: array of content Layers
                  activeBlockIndex: index in the content layer array of the active block
                  numberOfInlineBlocks: total number of inline content Layers

    set by setMargins():
                  topMargin: top margin
                  rightMargin: right margin
                  bottomMargin: bottom margin
                  leftMargin: left margin

    set by setBorders():
                  topBorder: top border
                  rightBorder: right border
                  bottomBorder: bottom border
                  leftBorder: left border

    set by activate():
                  contentWidth: width of the content in pixels
                  contentHeight: height of the content in pixels
                  scrollableWidth: total horizontally available scrolling distance in pixels
                  scrollableHeight: total vertically available scrolling distance in pixels
                  canScrollHorizontally: ### not needed: refactor to member functions
                  canScrollVertically: ### not needed: refactor to member functions
                  
    set by initDynamicUrlLoading():
                  contentFrame: reference to a hidden IFRAME in which dynamic URLs can be loaded
                  url: the currently loaded url
                  currentIndexInHistory: index of current content URL in the history array
                  latestIndexInHistory: the index of the latest loaded URL in the history array (may be less than the last element index of the history, since it maintains the current number of remembered URLs up to and including the latest requested URL by the user)
                  history: array of requested URLs

    introduced in buildHTML():
                  css
                  div
*/
function ScrollableLayer (id, x, y, width, height)
{
    LOG.debug("Constructing ScrollableLayer: " + id);
    this.id = (id != null) ? id : ScrollableLayer.prototype.DEFAULT_ID_PREFIX + (ScrollableLayer.prototype.count);
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.setScrollSpeed();

    this.createComponents();
    this.initDynamicUrlLoading();
    this.baseLayer.moveTo(this.x, this.y);
    
    // don't use the setMargins or setBorders funtions here, since they call resize and therefore depend on defined values for margins AND borders
    this.topMargin = 0;
    this.rightMargin = 0;
    this.bottomMargin = 0;
    this.leftMargin = 0;
    this.topBorder = 0;
    this.rightBorder = 0;
    this.bottomBorder = 0;
    this.leftBorder = 0;

    this.globalName = this.id + ScrollableLayer.prototype.GLOBAL_SUFFIX;
    eval("top." + this.globalName + "=this;");
    ScrollableLayer.prototype.count++;
    
    this.resize();

    LOG.debug("Global ScrollableLayer reference created: " + this.globalName);
}


// Constants
ScrollableLayer.prototype.DEFAULT_ID_PREFIX = "ScrollableLayer";
ScrollableLayer.prototype.GLOBAL_SUFFIX = "Scrollable";
ScrollableLayer.prototype.VIEWPORT_LAYER_SUFFIX = "Viewport";
ScrollableLayer.prototype.CONTENT_LAYER_SUFFIX = "Content";
ScrollableLayer.prototype.BORDER_LAYER_SUFFIX = "Border";
ScrollableLayer.prototype.BLOCK_LAYER_SUFFIX = "Block";
ScrollableLayer.prototype.CONTENT_FRAME_SUFFIX = "Frame";

// configuration constants
ScrollableLayer.prototype.scrollSpeed = 100; // the scroll speed in pixels per second
ScrollableLayer.prototype.borderColor = "Gray"; // color of the border
ScrollableLayer.prototype.viewportBackgroundColor = "Olive"; // default background color for viewport layer

// static member
ScrollableLayer.prototype.count = 0; // total number of ScrollableLayer objects


// Description     : set the margins between the content and the border
// Parameters      : the scroll speed in pixels per second
// Returns         : void
ScrollableLayer.prototype.setScrollSpeed = function (speed)
{
    if (speed != null && speed > 0)
        this.scrollSpeed = speed;
    this.scrollStep = this.scrollSpeed*Layer.prototype.DEFAULT_STEP_DURATION;
    this.scrollStepDuration = 1000*this.scrollStep/this.scrollSpeed;
}

// Description     : set the margins between the content and the border
// Parameters      : the margin widths in pixels
// Returns         : void
ScrollableLayer.prototype.setMargins = function (top, right, bottom, left)
{
    if (top != null)
        this.topMargin = top;
    if (right != null)
        this.rightMargin = right;
    if (bottom != null)
        this.bottomMargin = bottom;
    if (left != null)
        this.leftMargin = left;
    
    this.resize();
}

// Description     : set the border around the viewport
// Parameters      : the border widths in pixels; the widths should be non-negative
// Returns         : void
ScrollableLayer.prototype.setBorders = function (top, right, bottom, left)
{
    if (top != null && top >= 0)
    {
        this.topBorder = top;
        this.borderTopLayer.style.backgroundColor = this.borderColor;
    }
    if (right != null && right >= 0)
    {
        this.rightBorder = right;
        this.borderRightLayer.style.backgroundColor = this.borderColor;
    }
    if (bottom != null && bottom >= 0)
    {
        this.bottomBorder = bottom;
        this.borderBottomLayer.style.backgroundColor = this.borderColor;
    }
    if (left != null && left >= 0)
    {
        this.leftBorder = left;
        this.borderLeftLayer.style.backgroundColor = this.borderColor;
    }
        
    this.resize();
}

/*
Description     : build a HTML STYLE tag or CSS style content
Parameters      : id: id of CSS id selector, or special values:
                      "START": means build opening STYLE tag
                      "END": means build closing STYLE tag
                  left: left position in pixels
                  top: top position in pixels
                  width: width in pixels
                  height: height in pixels
                  backgroundColor: background color
                  visibility: visibility style definition
                  zIndex: z-index
                  moreStyles: other style definitions, or special value:
                      "clip": means build a clipping rectangle equal to the layer
Returns         : the result of the executed function or void if it does not exist
*/
ScrollableLayer.prototype.buildCSS = function (id, left, top, width, height, backgroundColor, visibility, zIndex, moreStyles)
{
    // handle start or end tag of style
    if (id == "START")
        return '<style type="text/css">\n';
    else if (id == "END")
        return "</style>";
    
    var s = "#" + id + " { ";
    if (left != null || top != null)
        s += "position:absolute; left:" + Number(left) + "px; top:" + Number(top) + "px; ";
    
    if (width != null)
        s += "width:" + width + "px; "
    
    if (height != null)
    {
        s += "height:" + height + "px; ";
        if (moreStyles != null && moreStyles.indexOf("clip") != -1)
            s += "clip:rect(0px " + width + "px " + height + "px 0px); ";
    }
    
    if (backgroundColor != null)
        s += "background-color:" + backgroundColor + "; ";
    if (visibility != null)
        s += "visibility:" + visibility + "; ";
    if (zIndex != null)
        s += "z-index:" + zIndex + "; ";
    if (moreStyles != null && moreStyles != "clip")
        s += moreStyles + " ";
    
    s += "}\n";

    return s;
}

// Description     : build 2 HTML strings containing the STYLE tag and the DIV tags representing the ScrollableLayer
//                   useful to build a template for the actual HTML document
//                   or to generate ScrollableLayers dynamically
// Parameters      : none
// Returns         : void
//                   the resulting HTML strings are stored in the members .css and .div
ScrollableLayer.prototype.buildHTML = function ()
{
    this.css = this.buildCSS("START");
    
    // build style of base layer and viewport layer
    this.css += this.buildCSS(this.id, this.x, this.y, this.width, this.height);
    this.css += this.buildCSS(this.id + ScrollableLayer.prototype.VIEWPORT_LAYER_SUFFIX,
                              this.leftBorder + this.leftMargin,
                              this.topBorder + this.topMargin,
                              this.width - this.leftBorder - this.leftMargin - this.rightBorder - this.rightMargin,
                              this.height - this.topBorder - this.topMargin - this.bottomBorder - this.bottomMargin,
                              this.viewportBackgroundColor, null, null, "clip");
    
    // build styles for the border layers
    if (this.topBorder > 0)
        this.css += this.buildCSS(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "T", 0, 0, this.width, this.topBorder, this.borderColor);
    if (this.rightBorder > 0)
        this.css += this.buildCSS(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "R", this.width - this.rightBorder, 0, this.rightBorder, this.height, this.borderColor);
    if (this.bottomBorder > 0)
        this.css += this.buildCSS(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "B", 0, this.height - this.bottomBorder, this.width, this.bottomBorder, this.borderColor);
    if (this.leftBorder > 0)
        this.css += this.buildCSS(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "L", 0, 0, this.leftBorder, this.height, this.borderColor);
    
    // create content layer
    this.css += this.buildCSS(this.id + ScrollableLayer.prototype.CONTENT_LAYER_SUFFIX, 0, 0);
    
    // if inline blocks, create layers for each block of content; hide all blocks
    if (this.numberOfInlineBlocks)
    {
        for (var i = 0; i < this.numberOfInlineBlocks; i++)
            this.css += this.buildCSS(this.id + ScrollableLayer.prototype.BLOCK_LAYER_SUFFIX + i, null, null, null, null, null, "hidden");
    }

    this.css += this.buildCSS("END");
    LOG.debug("Styles:\n" + this.css);
    
    // build correct HTML for the necessary layers, but don't build the block layers: they should be present in the original document
    this.div = '<div id="' + this.id + '">';
    this.div += '<div id="' + this.id + ScrollableLayer.prototype.VIEWPORT_LAYER_SUFFIX + '">';
    this.div += '<div id="' + this.id + ScrollableLayer.prototype.CONTENT_LAYER_SUFFIX + '">';
    
    // then the correct HTML end tags to close the layers
    this.div += "</div>"; // close viewport layer
    this.div += "</div>"; // close content layer

    // if border layers, open and close them
    if (this.topBorder > 0)
        this.div += '<div id="' + this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + 'T"></div>';
    if (this.rightBorder > 0)
        this.div += '<div id="' + this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + 'R"></div>';
    if (this.bottomBorder > 0)
        this.div += '<div id="' + this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + 'B"></div>';
    if (this.leftBorder > 0)
        this.div += '<div id="' + this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + 'L"></div>';

    this.div += "</div>"; // close base layer
    
    LOG.debug("Scrollable HTML:\n" + this.div);
} // buildHTML

// Description     : create the ScrollableLayer block layer objects if there are any and hide them all
// Parameters      : none
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.createBlockLayers = function ()
{
    this.blockLayers = new Array();
    this.activeBlockIndex = -1;
    this.numberOfInlineBlocks = 0;

    // loop through all "DIV" tags to find the layers named <id>Block...
    var divElements = document.getElementsByTagName("DIV");
    var prefix = this.id + ScrollableLayer.prototype.BLOCK_LAYER_SUFFIX;
    for (var i = 0; i < divElements.length; i++)
    {
        var blockId = divElements[i].id;
        if (blockId.substring(0, prefix.length) == prefix)
        {
            this.blockLayers[this.numberOfInlineBlocks] = new Layer(blockId);
            // ###this.blockLayers[this.numberOfInlineBlocks].hide();
            this.numberOfInlineBlocks++;
        }
    }
    
    LOG.debug(this.numberOfInlineBlocks + " Layers created from DIVs with id starting with " + prefix);
}

// Description     : create the ScrollableLayer component objects
// Parameters      : none
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.createComponents = function ()
{
    LOG.debug("Creating components for " + this.id);

    this.baseLayer = new Layer(this.id);
    this.viewportLayer = new Layer(this.id + ScrollableLayer.prototype.VIEWPORT_LAYER_SUFFIX);
    this.viewportLayer.style.backgroundColor = this.viewportBackgroundColor;
    this.borderTopLayer = new Layer(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "T")
    this.borderRightLayer = new Layer(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "R")
    this.borderBottomLayer = new Layer(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "B")
    this.borderLeftLayer = new Layer(this.id + ScrollableLayer.prototype.BORDER_LAYER_SUFFIX + "L")
    this.contentLayer = new Layer(this.id + ScrollableLayer.prototype.CONTENT_LAYER_SUFFIX);
    this.createBlockLayers();
}

// Description     : initialise dynamic URL loading members
// Parameters      : none
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.initDynamicUrlLoading = function ()
{
    LOG.debug("Initialise dynamic URL loading");

    var frameId = this.id + ScrollableLayer.prototype.CONTENT_FRAME_SUFFIX;
    this.contentFrame = document.getElementById(frameId);
    if (this.contentFrame)
    {
        // Don't create a Layer from an IFRAME: this creates some undesirable visual rendering problems
        // var frame = new Layer(frameId);
        this.contentFrame.style.visibility = "hidden";
        // ### the onload handler doesn't work - behaviour by design :-( - in Internet Explorer 6
        //     with a timer the document.readyState should be queried
        this.contentFrame.onload = this.updateContentFromIFrame;
        // copy reference for use in onload handler
        this.contentFrame.scrollableLayer = this;
    }
    this.url = null;
    this.currentIndexInHistory = -1;
    this.latestIndexInHistory = -1;
    this.history = new Array();

    LOG.debug("Dynamic URL loading initialised");
}

// Description     : initialise scrolling parameters
// Parameters      : optional new width and height of the content layer in pixels
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.activate = function (w, h)
{
    this.contentLayer.resize(w, h);
    
    this.contentHeight = this.contentLayer.height;
    this.contentWidth = this.contentLayer.width;
        
    // calculate maximum scrolling space
    this.scrollableWidth = this.contentWidth - this.viewportLayer.width;
    this.scrollableHeight = this.contentHeight - this.viewportLayer.height;

    this.canScrollHorizontally = (this.scrollableWidth > 0);
    this.canScrollVertically = (this.scrollableHeight > 0);
}

// Description     : copy the content of the content IFRAME to the content layer
// Parameters      : none
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.updateContentFromIFrame = function ()
{
    LOG.debug("Writing Content from src: " + this.src);
    if (this.contentDocument) // Mozilla
        this.scrollableLayer.contentLayer.write(this.contentDocument.body.innerHTML);
    /* ### check if this is needed/correct
    else if (this.document) // Internet Explorer 6
    {
        LOG.info("Setting content: " + this.document.body.innerHTML);
        this.scrollableLayer.contentLayer.write(this.document.body.innerHTML);
    }
    */

    LOG.debug("content size: " + this.contentDocument.body.offsetWidth + "x" + this.contentDocument.body.offsetHeight);

    this.scrollableLayer.initContent(true);
}

// Description     : load the body of an HTML document into the content layer and add it to the history array
// Parameters      : url: the URL of the document
// Returns         : void
ScrollableLayer.prototype.loadNewPage = function (url)
{
    if (url != this.url)
    {
        this.currentIndexInHistory++;
        this.latestIndexInHistory = this.currentIndexInHistory;
        this.history[this.currentIndexInHistory] = url;
    }
    this.reload();
}

// Description     : (re)load the current URL from history into the content layer
// Parameters      : none
// Returns         : void
// Remark          : works only if an IFRAME element was included in the HTML page
// Remark 2        : private function
ScrollableLayer.prototype.reload = function ()
{
    if (this.contentFrame)
    {
        this.url = this.history[this.currentIndexInHistory];
        // ### does this work in Internet Explorer?
        this.contentFrame.src = this.url;
        this.contentLayer.moveTo(0, 0);
    }
}

// Description     : reload the previous URL from history, if available
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.back = function ()
{
    if (this.currentIndexInHistory > 0)
        this.reload(--this.currentIndexInHistory);
}

// Description     : reload the next URL from history, if available
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.forward = function ()
{
    if (this.currentIndexInHistory < this.latestIndexInHistory)
        this.reload(++this.currentIndexInHistory);
}

// Description     : scroll content completely up, if possible
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.scrollUp = function ()
{
    LOG.debug(this.canScrollVertically ? "Start up" : "Do not start up");
    if (this.canScrollVertically)
        this.contentLayer.slideTo(null, -this.scrollableHeight, this.scrollStep, this.scrollStepDuration);
}

// Description     : scroll content completely down, if possible
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.scrollDown = function ()
{
    LOG.debug(this.canScrollVertically ? "Start down" : "Do not start down");
    if (this.canScrollVertically)
        this.contentLayer.slideTo(null, 0, this.scrollStep, this.scrollStepDuration);
}

// Description     : scroll content completely left, if possible
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.scrollLeft = function ()
{
    LOG.debug(this.canScrollHorizontally ? "Start left" : "Do not start left");
    if (this.canScrollHorizontally)
        this.contentLayer.slideTo(-this.scrollableWidth, null, this.scrollStep, this.scrollStepDuration);
}

// Description     : scroll content completely right, if possible
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.scrollRight = function ()
{
    LOG.debug(this.canScrollHorizontally ? "Start right" : "Do not start right");
    if (this.canScrollHorizontally)
        this.contentLayer.slideTo(0, null, this.scrollStep, this.scrollStepDuration);
}

// Description     : scroll the content layer by a given vector, if possible
// Parameters      : dx: the horizontal distance to scroll in pixels
//                   dy: the vertical distance to scroll in pixels
// Returns         : void
ScrollableLayer.prototype.scrollBy = function (dx, dy)
{
    dx = (dx != null) ? dx : 0;
    dy = (dy != null) ? dy : 0;
    
    this.scrollTo(this.contentLayer.left + dx, this.contentLayer.top + dy);
}

// Description     : set the scroll position of the content layer immediately
// Parameters      : coordinates of the target position in pixels
//                   if x or y are null, the respective direction will not be affected
//                   if x or y are negative, the zero scroll position will be set for the respective direction
//                   if x or y are larger than the maximum scrolling distance, the maximum scroll position will be set for that direction
// Returns         : void
ScrollableLayer.prototype.jumpTo = function (x, y)
{
    x = (x != null) ? Math.max(Math.min(x, 0), -this.scrollableWidth) : null;
    y = (y != null) ? Math.max(Math.min(y, 0), -this.scrollableHeight) : null;
    
    this.contentLayer.moveTo(x, y);
}

// Description     : set the relative scroll position of the content layer immediately
// Parameters      : relative position (0 <= pos <= 1)
//                   if relativePosX or relativePosY are null, the respective direction will not be affected
//                   if relativePosX or relativePosY are negative, the zero scroll position will be set for the respective direction
//                   if relativePosX or relativePosY are larger than 1, the maximum scroll position will be set for that direction
// Returns         : void
ScrollableLayer.prototype.jumpRelativeTo = function (relativePosX, relativePosY)
{
    var x = (relativePosX != null) ? -relativePosX*this.scrollableWidth : null;
    var y = (relativePosY != null) ? -relativePosY*this.scrollableHeight : null;
    
    this.jumpTo(x, y);
}

// Description     : scroll the content layer to a given position
// Parameters      : coordinates of the target position in pixels
//                   if x or y are null, the respective direction will not be affected
//                   if x or y are negative, the zero scroll position will be set for the respective direction
//                   if x or y are larger than the maximum scrolling distance, the maximum scroll position will be set for that direction
// Returns         : void
ScrollableLayer.prototype.scrollTo = function (x, y)
{
    x = (x != null) ? Math.max(Math.min(x, 0), -this.scrollableWidth) : null;
    y = (y != null) ? Math.max(Math.min(y, 0), -this.scrollableHeight) : null;
    
    this.contentLayer.slideTo(x, y, this.scrollStep, this.scrollStepDuration);
}

// Description     : stop scrolling immediately
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.stop = function ()
{
    this.contentLayer.stop();
}

// Description     : calculate relative position (0 <= pos <= 1) between no scroll and max horizontal scroll
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.getHorizontalScrollPercentage = function ()
{
    if (this.scrollableWidth <= 0)
        return 0;
    return -this.contentLayer.left/this.scrollableWidth;
}

// Description     : calculate relative position (0 <= pos <= 1) between no scroll and max vertical scroll
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.getVerticalScrollPercentage = function ()
{
    if (this.scrollableHeight <= 0)
        return 0;
    return -this.contentLayer.top/this.scrollableHeight;
}

// Description     : resize the ScrollableLayer and its components
// Parameters      : width and height of the ScrollableLayer in pixels (both should be non-negative)
//                   shouldSetContentWidth: boolean indicating if the width of the content should be reset, thus disabling horizontal scrolling
// Returns         : void
ScrollableLayer.prototype.resize = function (w, h)
{
    if (w != null && w > 0)
        this.width = w;
    if (h != null && h > 0)
        this.height = h;
    LOG.debug("Resizing to " + this.width + "x" + this.height);
    this.baseLayer.resize(this.width, this.height);

    this.viewportLayer.moveTo(this.leftBorder + this.leftMargin, this.topBorder + this.topMargin);
    this.viewportLayer.resize(this.width - this.leftBorder - this.leftMargin - this.rightBorder - this.rightMargin,
                              this.height - this.topBorder - this.topMargin - this.bottomBorder - this.bottomMargin);
    this.viewportLayer.setClipBorder(0);
    LOG.debug("viewportLayer: (" + this.viewportLayer.left + "," + this.viewportLayer.top + ") " + this.viewportLayer.width + "x" + this.viewportLayer.height);

    this.borderTopLayer.moveTo(0, 0);
    this.borderRightLayer.moveTo(this.width - this.rightBorder, 0);
    this.borderBottomLayer.moveTo(0, this.height - this.bottomBorder);
    this.borderLeftLayer.moveTo(0, 0);

    this.borderTopLayer.resize(this.width, this.topBorder);
    this.borderRightLayer.resize(this.rightBorder, this.height);
    this.borderBottomLayer.resize(this.width, this.bottomBorder);
    this.borderLeftLayer.resize(this.leftBorder, this.height);

    this.initContent();
}

// Description     : move the content layer to its start position and scrolling parameters
//                   optionally recalculate its size from the current content
// Parameters      : calculateSize: if true, recalculate the content layer size
// Returns         : void
ScrollableLayer.prototype.initContent = function (calculateSize)
{
    this.contentLayer.moveTo(0, 0);
    if (calculateSize)
    {
        this.contentLayer.resizeVerticallyToContent();
        LOG.debug("size after reloading: " + this.contentLayer.width + "x" + this.contentLayer.height);
    }
    
    this.activate();
}

// Description     : load the current block into the content layer
// Parameters      : none
// Returns         : void
// Remark          : private function
ScrollableLayer.prototype.loadBlock = function ()
{
    this.contentLayer.write(this.blockLayers[this.activeBlockIndex].htmlElement.innerHTML);
    this.initContent(true);
}

// Description     : load the given block into the content layer (if the index refers to an existing block)
// Parameters      : the index of the content block
// Returns         : void
ScrollableLayer.prototype.showBlock = function (i)
{
    if (i >= 0 && i < this.numberOfInlineBlocks && i != this.activeBlockIndex)
    {
        this.activeBlockIndex = i;
        this.loadBlock();
    }
}

// Description     : load the next block into the content layer, if one exists
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.nextBlock = function ()
{
    if (this.activeBlockIndex < this.numberOfInlineBlocks - 1)
    {
        this.activeBlockIndex++;
        this.loadBlock();
    }
}

// Description     : load the previous block into the content layer, if one exists
// Parameters      : none
// Returns         : void
ScrollableLayer.prototype.previousBlock = function ()
{
    if (this.activeBlockIndex > 0)
    {
        this.activeBlockIndex--;
        this.loadBlock();
    }
}
