#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: scrollview/js/scrollbars-plugin.js

    /**
     * Provides a plugin, which adds support for a scroll indicator to ScrollView instances
     *
     * @module scrollview
     * @submodule scrollview-scrollbars
     */

    var getClassName = Y.ClassNameManager.getClassName,
        _classNames,

        Transition = Y.Transition,
        NATIVE_TRANSITIONS = Transition.useNative,
        SCROLLBAR = 'scrollbar',
        SCROLLVIEW = 'scrollview',

        VERTICAL_NODE = "verticalNode",
        HORIZONTAL_NODE = "horizontalNode",

        CHILD_CACHE = "childCache",

        TOP = "top",
        LEFT = "left",
        WIDTH = "width",
        HEIGHT = "height",

        HORIZ_CACHE = "_sbh",
        VERT_CACHE = "_sbv",

        TRANSITION_PROPERTY = Y.ScrollView._TRANSITION.PROPERTY,
        TRANSFORM = "transform",

        TRANSLATE_X = "translateX(",
        TRANSLATE_Y = "translateY(",

        SCALE_X = "scaleX(",
        SCALE_Y = "scaleY(",

        SCROLL_X = "scrollX",
        SCROLL_Y = "scrollY",

        PX = "px",
        CLOSE = ")",
        PX_CLOSE = PX + CLOSE;

    /**
     * ScrollView plugin that adds scroll indicators to ScrollView instances
     *
     * @class ScrollViewScrollbars
     * @namespace Plugin
     * @extends Plugin.Base
     * @constructor
     */
    function ScrollbarsPlugin() {
        ScrollbarsPlugin.superclass.constructor.apply(this, arguments);
    }

    ScrollbarsPlugin.CLASS_NAMES = {
        showing: getClassName(SCROLLVIEW, SCROLLBAR, 'showing'),
        scrollbar: getClassName(SCROLLVIEW, SCROLLBAR),
        scrollbarV: getClassName(SCROLLVIEW, SCROLLBAR, 'vert'),
        scrollbarH: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz'),
        scrollbarVB: getClassName(SCROLLVIEW, SCROLLBAR, 'vert', 'basic'),
        scrollbarHB: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz', 'basic'),
        child: getClassName(SCROLLVIEW, 'child'),
        first: getClassName(SCROLLVIEW, 'first'),
        middle: getClassName(SCROLLVIEW, 'middle'),
        last: getClassName(SCROLLVIEW, 'last')
    };

    _classNames = ScrollbarsPlugin.CLASS_NAMES;

    /**
     * The identity of the plugin
     *
     * @property NAME
     * @type String
     * @default 'pluginScrollViewScrollbars'
     * @static
     */
    ScrollbarsPlugin.NAME = 'pluginScrollViewScrollbars';

    /**
     * The namespace on which the plugin will reside.
     *
     * @property NS
     * @type String
     * @default 'scrollbars'
     * @static
     */
    ScrollbarsPlugin.NS = 'scrollbars';

    /**
     * HTML template for the scrollbar
     *
     * @property SCROLLBAR_TEMPLATE
     * @type Object
     * @static
     */
    ScrollbarsPlugin.SCROLLBAR_TEMPLATE = [
        '<div>',
        '<span class="' + _classNames.child + ' ' + _classNames.first + '"></span>',
        '<span class="' + _classNames.child + ' ' + _classNames.middle + '"></span>',
        '<span class="' + _classNames.child + ' ' + _classNames.last + '"></span>',
        '</div>'
    ].join('');

    /**
     * The default attribute configuration for the plugin
     *
     * @property ATTRS
     * @type Object
     * @static
     */
    ScrollbarsPlugin.ATTRS = {

        /**
         * Vertical scrollbar node
         *
         * @attribute verticalNode
         * @type Y.Node
         */
        verticalNode: {
            setter: '_setNode',
            valueFn: '_defaultNode'
        },

        /**
         * Horizontal scrollbar node
         *
         * @attribute horizontalNode
         * @type Y.Node
         */
        horizontalNode: {
            setter: '_setNode',
            valueFn: '_defaultNode'
        }
    };

    Y.namespace("Plugin").ScrollViewScrollbars = Y.extend(ScrollbarsPlugin, Y.Plugin.Base, {

        /**
         * Designated initializer
         *
         * @method initializer
         */
        initializer: function() {
            this._host = this.get("host");

            this.afterHostEvent('scrollEnd', this._hostScrollEnd);
            this.afterHostMethod('scrollTo', this._update);
            this.afterHostMethod('_uiDimensionsChange', this._hostDimensionsChange);
        },

        /**
         * Set up the DOM nodes for the scrollbars. This method is invoked whenever the
         * host's _uiDimensionsChange fires, giving us the opportunity to remove un-needed
         * scrollbars, as well as add one if necessary.
         *
         * @method _hostDimensionsChange
         * @protected
         */
        _hostDimensionsChange: function() {
            var host = this._host,
                axis = host._cAxis,
                scrollX = host.get(SCROLL_X),
                scrollY = host.get(SCROLL_Y);

            this._dims = host._getScrollDims();

            if (axis && axis.y) {
                this._renderBar(this.get(VERTICAL_NODE), true, 'vert');
            }

            if (axis && axis.x) {
                this._renderBar(this.get(HORIZONTAL_NODE), true, 'horiz');
            }

            this._update(scrollX, scrollY);

            Y.later(500, this, 'flash', true);
        },

        /**
         * Handler for the scrollEnd event fired by the host. Default implementation flashes the scrollbar
         *
         * @method _hostScrollEnd
         * @param {Event.Facade} e The event facade.
         * @protected
         */
        _hostScrollEnd : function() {
            var host = this._host,
                scrollX = host.get(SCROLL_X),
                scrollY = host.get(SCROLL_Y);

            this.flash();

            this._update(scrollX, scrollY);
        },

        /**
         * Adds or removes a scrollbar node from the document.
         *
         * @method _renderBar
         * @private
         * @param {Node} bar The scrollbar node
         * @param {boolean} add true, to add the node, false to remove it
         */
        _renderBar: function(bar, add) {
            var inDoc = bar.inDoc(),
                bb = this._host._bb,
                className = bar.getData("isHoriz") ? _classNames.scrollbarHB : _classNames.scrollbarVB;

            if (add && !inDoc) {
                bb.append(bar);
                bar.toggleClass(className, this._basic);
                this._setChildCache(bar);
            } else if(!add && inDoc) {
                bar.remove();
                this._clearChildCache(bar);
            }
        },

        /**
         * Caches scrollbar child element information,
         * to optimize _update implementation
         *
         * @method _setChildCache
         * @private
         * @param {Node} node
         */
        _setChildCache : function(node) {
            var c = node.get("children"),
                fc = c.item(0),
                mc = c.item(1),
                lc = c.item(2),
                size = node.getData("isHoriz") ? "offsetWidth" : "offsetHeight";

            node.setStyle(TRANSITION_PROPERTY, TRANSFORM);
            mc.setStyle(TRANSITION_PROPERTY, TRANSFORM);
            lc.setStyle(TRANSITION_PROPERTY, TRANSFORM);

            node.setData(CHILD_CACHE, {
                fc : fc,
                lc : lc,
                mc : mc,
                fcSize : fc && fc.get(size),
                lcSize : lc && lc.get(size)
            });
        },

        /**
         * Clears child cache
         *
         * @method _clearChildCache
         * @private
         * @param {Node} node
         */
        _clearChildCache : function(node) {
            node.clearData(CHILD_CACHE);
        },

        /**
         * Utility method, to move/resize either vertical or horizontal scrollbars
         *
         * @method _updateBar
         * @private
         *
         * @param {Node} scrollbar The scrollbar node.
         * @param {Number} current The current scroll position.
         * @param {Number} duration The transition duration.
         * @param {boolean} horiz true if horizontal, false if vertical.
         */
        _updateBar : function(scrollbar, current, duration, horiz) {

            var host = this._host,
                basic = this._basic,

                scrollbarSize = 0,
                scrollbarPos = 1,

                childCache = scrollbar.getData(CHILD_CACHE),
                lastChild = childCache.lc,
                middleChild = childCache.mc,
                firstChildSize = childCache.fcSize,
                lastChildSize = childCache.lcSize,
                middleChildSize,
                lastChildPosition,

                transition,
                translate,
                scale,

                dim,
                dimOffset,
                dimCache,
                widgetSize,
                contentSize;

            if (horiz) {
                dim = WIDTH;
                dimOffset = LEFT;
                dimCache = HORIZ_CACHE;
                widgetSize = this._dims.offsetWidth;
                contentSize = this._dims.scrollWidth;
                translate = TRANSLATE_X;
                scale = SCALE_X;
                current = (current !== undefined) ? current : host.get(SCROLL_X);
            } else {
                dim = HEIGHT;
                dimOffset = TOP;
                dimCache = VERT_CACHE;
                widgetSize = this._dims.offsetHeight;
                contentSize = this._dims.scrollHeight;
                translate = TRANSLATE_Y;
                scale = SCALE_Y;
                current = (current !== undefined) ? current : host.get(SCROLL_Y);
            }

            scrollbarSize = Math.floor(widgetSize * (widgetSize/contentSize));
            scrollbarPos = Math.floor((current/(contentSize - widgetSize)) * (widgetSize - scrollbarSize));
            if (scrollbarSize > widgetSize) {
                scrollbarSize = 1;
            }

            if (scrollbarPos > (widgetSize - scrollbarSize)) {
                scrollbarSize = scrollbarSize - (scrollbarPos - (widgetSize - scrollbarSize));
            } else if (scrollbarPos < 0) {
                scrollbarSize = scrollbarPos + scrollbarSize;
                scrollbarPos = 0;
            } else if (isNaN(scrollbarPos)) {
                scrollbarPos = 0;
            }

            middleChildSize = (scrollbarSize - (firstChildSize + lastChildSize));

            if (middleChildSize < 0) {
                middleChildSize = 0;
            }

            if (middleChildSize === 0 && scrollbarPos !== 0) {
                scrollbarPos = widgetSize - (firstChildSize + lastChildSize) - 1;
            }

            if (duration !== 0) {
                // Position Scrollbar
                transition = {
                    duration : duration
                };

                if (NATIVE_TRANSITIONS) {
                    transition.transform = translate + scrollbarPos + PX_CLOSE;
                } else {
                    transition[dimOffset] = scrollbarPos + PX;
                }

                scrollbar.transition(transition);

            } else {
                if (NATIVE_TRANSITIONS) {
                    scrollbar.setStyle(TRANSFORM, translate + scrollbarPos + PX_CLOSE);
                } else {
                    scrollbar.setStyle(dimOffset, scrollbarPos + PX);
                }
            }

            // Resize Scrollbar Middle Child
            if (this[dimCache] !== middleChildSize) {
                this[dimCache] = middleChildSize;

                if (middleChildSize > 0) {

                    if (duration !== 0) {
                        transition = {
                            duration : duration
                        };

                        if(NATIVE_TRANSITIONS) {
                            transition.transform = scale + middleChildSize + CLOSE;
                        } else {
                            transition[dim] = middleChildSize + PX;
                        }

                        middleChild.transition(transition);
                    } else {
                        if (NATIVE_TRANSITIONS) {
                            middleChild.setStyle(TRANSFORM, scale + middleChildSize + CLOSE);
                        } else {
                            middleChild.setStyle(dim, middleChildSize + PX);
                        }
                    }

                    // Position Last Child
                    if (!horiz || !basic) {

                        lastChildPosition = scrollbarSize - lastChildSize;

                        if(duration !== 0) {
                            transition = {
                                duration : duration
                            };

                            if (NATIVE_TRANSITIONS) {
                                transition.transform = translate + lastChildPosition + PX_CLOSE;
                            } else {
                                transition[dimOffset] = lastChildPosition;
                            }

                            lastChild.transition(transition);
                        } else {
                            if (NATIVE_TRANSITIONS) {
                                lastChild.setStyle(TRANSFORM, translate + lastChildPosition + PX_CLOSE);
                            } else {
                                lastChild.setStyle(dimOffset, lastChildPosition + PX);
                            }
                        }
                    }
                }
            }
        },

        /**
         * AOP method, invoked after the host's _uiScrollTo method,
         *  to position and resize the scroll bars
         *
         * @method _update
         * @param x {Number} The current scrollX value
         * @param y {Number} The current scrollY value
         * @param duration {Number} Number of ms of animation (optional) - used when snapping to bounds
         * @param easing {String} Optional easing equation to use during the animation, if duration is set
         * @protected
         */
        _update: function(x, y, duration) {
            var vNode = this.get(VERTICAL_NODE),
                hNode = this.get(HORIZONTAL_NODE),
                host = this._host,
                axis = host._cAxis;

            duration = (duration || 0)/1000;

            if (!this._showing) {
                this.show();
            }

            if (axis && axis.y && vNode && y !== null) {
                this._updateBar(vNode, y, duration, false);
            }

            if (axis && axis.x && hNode && x !== null) {
                this._updateBar(hNode, x, duration, true);
            }
        },

        /**
         * Show the scroll bar indicators
         *
         * @method show
         * @param animated {Boolean} Whether or not to animate the showing
         */
        show: function(animated) {
            this._show(true, animated);
        },

        /**
         * Hide the scroll bar indicators
         *
         * @method hide
         * @param animated {Boolean} Whether or not to animate the hiding
         */
        hide: function(animated) {
            this._show(false, animated);
        },

        /**
         * Internal hide/show implementation utility method
         *
         * @method _show
         * @param {boolean} show Whether to show or hide the scrollbar
         * @param {bolean} animated Whether or not to animate while showing/hide
         * @protected
         */
        _show : function(show, animated) {

            var verticalNode = this.get(VERTICAL_NODE),
                horizontalNode = this.get(HORIZONTAL_NODE),

                duration = (animated) ? 0.6 : 0,
                opacity = (show) ? 1 : 0,

                transition;

            this._showing = show;

            if (this._flashTimer) {
                this._flashTimer.cancel();
            }

            transition = {
                duration : duration,
                opacity : opacity
            };

            if (verticalNode && verticalNode._node) {
                verticalNode.transition(transition);
            }

            if (horizontalNode && horizontalNode._node) {
                horizontalNode.transition(transition);
            }
        },

        /**
         * Momentarily flash the scroll bars to indicate current scroll position
         *
         * @method flash
         */
        flash: function() {
            this.show(true);
            this._flashTimer = Y.later(800, this, 'hide', true);
        },

        /**
         * Setter for the verticalNode and horizontalNode attributes
         *
         * @method _setNode
         * @param node {Node} The Y.Node instance for the scrollbar
         * @param name {String} The attribute name
         * @return {Node} The Y.Node instance for the scrollbar
         *
         * @protected
         */
        _setNode: function(node, name) {
            var horiz = (name === HORIZONTAL_NODE);
                node = Y.one(node);

            if (node) {
                node.addClass(_classNames.scrollbar);
                node.addClass( (horiz) ? _classNames.scrollbarH : _classNames.scrollbarV );
                node.setData("isHoriz", horiz);
            }

            return node;
        },

        /**
         * Creates default node instances for scrollbars
         *
         * @method _defaultNode
         * @return {Node} The Y.Node instance for the scrollbar
         *
         * @protected
         */
        _defaultNode: function() {
            return Y.Node.create(ScrollbarsPlugin.SCROLLBAR_TEMPLATE);
        },

        _basic: Y.UA.ie && Y.UA.ie <= 8

    });