2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('scrollview-scrollbars', function(Y) {
11 * Provides a plugin, which adds support for a scroll indicator to ScrollView instances
13 * @module scrollview-scrollbars
16 var getClassName = Y.ClassNameManager.getClassName,
19 NATIVE_TRANSITIONS = Y.Transition.useNative,
20 SCROLLBAR = 'scrollbar',
21 SCROLLVIEW = 'scrollview',
23 VERTICAL_NODE = "verticalNode",
24 HORIZONTAL_NODE = "horizontalNode",
26 CHILD_CACHE = "childCache",
32 SCROLL_WIDTH = "scrollWidth",
33 SCROLL_HEIGHT = "scrollHeight",
38 TRANSITION_PROPERTY = "transitionProperty",
39 TRANSFORM = "transform",
41 TRANSLATE_X = "translateX(",
42 TRANSLATE_Y = "translateY(",
52 PX_CLOSE = PX + CLOSE;
55 * ScrollView plugin that adds scroll indicators to ScrollView instances
57 * @class ScrollViewScrollbars
59 * @extends Plugin.Base
62 function ScrollbarsPlugin() {
63 ScrollbarsPlugin.superclass.constructor.apply(this, arguments);
66 ScrollbarsPlugin.CLASS_NAMES = {
67 showing: getClassName(SCROLLVIEW, SCROLLBAR, 'showing'),
68 scrollbar: getClassName(SCROLLVIEW, SCROLLBAR),
69 scrollbarV: getClassName(SCROLLVIEW, SCROLLBAR, 'vert'),
70 scrollbarH: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz'),
71 scrollbarVB: getClassName(SCROLLVIEW, SCROLLBAR, 'vert', 'basic'),
72 scrollbarHB: getClassName(SCROLLVIEW, SCROLLBAR, 'horiz', 'basic'),
73 child: getClassName(SCROLLVIEW, 'child'),
74 first: getClassName(SCROLLVIEW, 'first'),
75 middle: getClassName(SCROLLVIEW, 'middle'),
76 last: getClassName(SCROLLVIEW, 'last')
79 _classNames = ScrollbarsPlugin.CLASS_NAMES;
82 * The identity of the plugin
84 * @property ScrollViewScrollbars.NAME
86 * @default 'pluginScrollViewScrollbars'
89 ScrollbarsPlugin.NAME = 'pluginScrollViewScrollbars';
92 * The namespace on which the plugin will reside.
94 * @property ScrollViewScrollbars.NS
96 * @default 'scrollbars'
99 ScrollbarsPlugin.NS = 'scrollbars';
102 * HTML template for the scrollbar
104 * @property ScrollViewScrollbars.SCROLLBAR_TEMPLATE
108 ScrollbarsPlugin.SCROLLBAR_TEMPLATE = [
110 '<span class="' + _classNames.child + ' ' + _classNames.first + '"></span>',
111 '<span class="' + _classNames.child + ' ' + _classNames.middle + '"></span>',
112 '<span class="' + _classNames.child + ' ' + _classNames.last + '"></span>',
117 * The default attribute configuration for the plugin
119 * @property ScrollViewScrollbars.ATTRS
123 ScrollbarsPlugin.ATTRS = {
126 * Vertical scrollbar node
128 * @attribute verticalNode
133 valueFn: '_defaultNode'
137 * Horizontal scrollbar node
139 * @attribute horizontalNode
144 valueFn: '_defaultNode'
148 Y.namespace("Plugin").ScrollViewScrollbars = Y.extend(ScrollbarsPlugin, Y.Plugin.Base, {
151 * Designated initializer
153 * @method initializer
155 initializer: function() {
156 this._host = this.get("host");
158 this.afterHostEvent('scrollEnd', this._hostScrollEnd);
159 this.afterHostMethod('_uiScrollTo', this._update);
160 this.afterHostMethod('_uiDimensionsChange', this._hostDimensionsChange);
164 * Set up the DOM nodes for the scrollbars. This method is invoked whenever the
165 * host's _uiDimensionsChange fires, giving us the opportunity to remove un-needed
166 * scrollbars, as well as add one if necessary.
168 * @method _hostDimensionsChange
171 _hostDimensionsChange: function() {
172 var host = this._host;
174 this._renderBar(this.get(VERTICAL_NODE), host._scrollsVertical);
175 this._renderBar(this.get(HORIZONTAL_NODE), host._scrollsHorizontal);
179 Y.later(500, this, 'flash', true);
183 * Handler for the scrollEnd event fired by the host. Default implementation flashes the scrollbar
185 * @method _hostScrollEnd
186 * @param {Event.Facade} e The event facade.
188 _hostScrollEnd : function(e) {
189 if (!this._host._flicking) {
195 * Adds or removes a scrollbar node from the document.
199 * @param {Node} bar The scrollbar node
200 * @param {boolean} add true, to add the node, false to remove it
202 _renderBar: function(bar, add) {
203 var inDoc = bar.inDoc(),
205 className = bar.getData("isHoriz") ? _classNames.scrollbarHB : _classNames.scrollbarVB;
209 bar.toggleClass(className, this._basic);
210 this._setChildCache(bar);
211 } else if(!add && inDoc) {
213 this._clearChildCache(bar);
218 * Caches scrollbar child element information,
219 * to optimize _update implementation
221 * @method _setChildCache
225 _setChildCache : function(node) {
227 var c = node.get("children"),
231 size = node.getData("isHoriz") ? "offsetWidth" : "offsetHeight";
233 node.setStyle(TRANSITION_PROPERTY, TRANSFORM);
234 mc.setStyle(TRANSITION_PROPERTY, TRANSFORM);
235 lc.setStyle(TRANSITION_PROPERTY, TRANSFORM);
237 node.setData(CHILD_CACHE, {
241 fcSize : fc && fc.get(size),
242 lcSize : lc && lc.get(size)
249 * @method _clearChildCache
253 _clearChildCache : function(node) {
254 node.clearData(CHILD_CACHE);
258 * Utility method, to move/resize either vertical or horizontal scrollbars
263 * @param {Node} scrollbar The scrollbar node.
264 * @param {Number} current The current scroll position.
265 * @param {Number} duration The transition duration.
266 * @param {boolean} horiz true if horizontal, false if vertical.
268 _updateBar : function(scrollbar, current, duration, horiz) {
270 var host = this._host,
277 childCache = scrollbar.getData(CHILD_CACHE),
278 lastChild = childCache.lc,
279 middleChild = childCache.mc,
280 firstChildSize = childCache.fcSize,
281 lastChildSize = childCache.lcSize,
298 dimCache = HORIZ_CACHE;
299 widgetSize = host.get(dim);
300 contentSize = host._scrollWidth || cb.get(SCROLL_WIDTH);
301 translate = TRANSLATE_X;
303 current = (current !== undefined) ? current : host.get(SCROLL_X);
307 dimCache = VERT_CACHE;
308 widgetSize = host.get(dim);
309 contentSize = host._scrollHeight || cb.get(SCROLL_HEIGHT);
310 translate = TRANSLATE_Y;
312 current = (current !== undefined) ? current : host.get(SCROLL_Y);
315 scrollbarSize = Math.floor(widgetSize * (widgetSize/contentSize));
316 scrollbarPos = Math.floor((current/(contentSize - widgetSize)) * (widgetSize - scrollbarSize));
318 if (scrollbarSize > widgetSize) {
322 if (scrollbarPos > (widgetSize - scrollbarSize)) {
323 scrollbarSize = scrollbarSize - (scrollbarPos - (widgetSize - scrollbarSize));
324 } else if (scrollbarPos < 0) {
325 scrollbarSize = scrollbarPos + scrollbarSize;
329 middleChildSize = (scrollbarSize - (firstChildSize + lastChildSize));
331 if (middleChildSize < 0) {
335 if (middleChildSize === 0 && scrollbarPos !== 0) {
336 scrollbarPos = widgetSize - (firstChildSize + lastChildSize) - 1;
339 if (duration !== 0) {
340 // Position Scrollbar
345 if (NATIVE_TRANSITIONS) {
346 transition.transform = translate + scrollbarPos + PX_CLOSE;
348 transition[dimOffset] = scrollbarPos + PX;
351 scrollbar.transition(transition);
354 if (NATIVE_TRANSITIONS) {
355 scrollbar.setStyle(TRANSFORM, translate + scrollbarPos + PX_CLOSE);
357 scrollbar.setStyle(dimOffset, scrollbarPos + PX);
361 // Resize Scrollbar Middle Child
362 if (this[dimCache] !== middleChildSize) {
363 this[dimCache] = middleChildSize;
365 if (middleChildSize > 0) {
367 if (duration !== 0) {
372 if(NATIVE_TRANSITIONS) {
373 transition.transform = scale + middleChildSize + CLOSE;
375 transition[dim] = middleChildSize + PX;
378 middleChild.transition(transition);
380 if (NATIVE_TRANSITIONS) {
381 middleChild.setStyle(TRANSFORM, scale + middleChildSize + CLOSE);
383 middleChild.setStyle(dim, middleChildSize + PX);
387 // Position Last Child
388 if (!horiz || !basic) {
390 lastChildPosition = scrollbarSize - lastChildSize;
397 if (NATIVE_TRANSITIONS) {
398 transition.transform = translate + lastChildPosition + PX_CLOSE;
400 transition[dimOffset] = lastChildPosition;
403 lastChild.transition(transition);
405 if (NATIVE_TRANSITIONS) {
406 lastChild.setStyle(TRANSFORM, translate + lastChildPosition + PX_CLOSE);
408 lastChild.setStyle(dimOffset, lastChildPosition + PX);
417 * AOP method, invoked after the host's _uiScrollTo method,
418 * to position and resize the scroll bars
421 * @param x {Number} The current scrollX value
422 * @param y {Number} The current scrollY value
423 * @param duration {Number} Number of ms of animation (optional) - used when snapping to bounds
424 * @param easing {String} Optional easing equation to use during the animation, if duration is set
427 _update: function(x, y, duration, easing) {
429 var vNode = this.get(VERTICAL_NODE),
430 hNode = this.get(HORIZONTAL_NODE),
433 duration = (duration || 0)/1000;
435 if (!this._showing) {
439 if (host._scrollsVertical && vNode) {
440 this._updateBar(vNode, y, duration, false);
443 if (host._scrollsHorizontal && hNode) {
444 this._updateBar(hNode, x, duration, true);
449 * Show the scroll bar indicators
452 * @param animated {Boolean} Whether or not to animate the showing
454 show: function(animated) {
455 this._show(true, animated);
459 * Hide the scroll bar indicators
462 * @param animated {Boolean} Whether or not to animate the hiding
464 hide: function(animated) {
465 this._show(false, animated);
469 * Internal hide/show implementation utility method
472 * @param {boolean} show Whether to show or hide the scrollbar
473 * @param {bolean} animated Whether or not to animate while showing/hide
476 _show : function(show, animated) {
478 var verticalNode = this.get(VERTICAL_NODE),
479 horizontalNode = this.get(HORIZONTAL_NODE),
481 duration = (animated) ? 0.6 : 0,
482 opacity = (show) ? 1 : 0,
486 this._showing = show;
488 if (this._flashTimer) {
489 this._flashTimer.cancel();
498 verticalNode.transition(transition);
501 if (horizontalNode) {
502 horizontalNode.transition(transition);
507 * Momentarily flash the scroll bars to indicate current scroll position
512 var shouldFlash = false,
515 if (host._scrollsVertical && (host._scrollHeight > host.get(HEIGHT))) {
519 if (host._scrollsHorizontal && (host._scrollWidth > host.get(WIDTH))) {
525 this._flashTimer = Y.later(800, this, 'hide', true);
530 * Setter for the verticalNode and horizontalNode attributes
533 * @param node {Node} The Y.Node instance for the scrollbar
534 * @param name {String} The attribute name
535 * @return {Node} The Y.Node instance for the scrollbar
539 _setNode: function(node, name) {
540 var horiz = (name == HORIZONTAL_NODE);
545 node.addClass(_classNames.scrollbar);
546 node.addClass( (horiz) ? _classNames.scrollbarH : _classNames.scrollbarV );
547 node.setData("isHoriz", horiz);
554 * Creates default node instances for scrollbars
556 * @method _defaultNode
557 * @return {Node} The Y.Node instance for the scrollbar
561 _defaultNode: function() {
562 return Y.Node.create(ScrollbarsPlugin.SCROLLBAR_TEMPLATE);
565 _basic: Y.UA.ie && Y.UA.ie <= 8
570 }, '3.3.0' ,{skinnable:true, requires:['plugin']});