Version 3.18.1
Show:

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 {EventFacade} 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
            
            });