2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('scrollview-paginator', function (Y, NAME) {
11 * Provides a plugin that adds pagination support to ScrollView instances
13 * @module scrollview-paginator
15 var getClassName = Y.ClassNameManager.getClassName,
16 SCROLLVIEW = 'scrollview',
17 CLASS_HIDDEN = getClassName(SCROLLVIEW, 'hidden'),
18 CLASS_PAGED = getClassName(SCROLLVIEW, 'paged'),
19 UI = (Y.ScrollView) ? Y.ScrollView.UI_SRC : 'ui',
24 DISABLED = 'disabled',
26 SELECTOR = 'selector',
32 * Scrollview plugin that adds support for paging
34 * @class ScrollViewPaginator
36 * @extends Plugin.Base
39 function PaginatorPlugin() {
40 PaginatorPlugin.superclass.constructor.apply(this, arguments);
43 Y.extend(PaginatorPlugin, Y.Plugin.Base, {
46 * Designated initializer
49 * @param {config} Configuration object for the plugin
51 initializer: function (config) {
53 host = paginator.get(HOST);
55 // Initialize & default
56 paginator._pageDims = [];
57 paginator._pageBuffer = 1;
58 paginator._optimizeMemory = false;
61 paginator._host = host;
62 paginator._bb = host._bb;
63 paginator._cb = host._cb;
64 paginator._cIndex = paginator.get(INDEX);
65 paginator._cAxis = paginator.get(AXIS);
68 if (config._optimizeMemory) {
69 paginator._optimizeMemory = config._optimizeMemory;
72 if (config._pageBuffer) {
73 paginator._pageBuffer = config._pageBuffer;
76 // Attach event bindings
77 paginator._bindAttrs();
86 _bindAttrs: function () {
91 'indexChange': paginator._afterIndexChange,
92 'axisChange': paginator._afterAxisChange
95 // Host method listeners
96 paginator.beforeHostMethod('scrollTo', paginator._beforeHostScrollTo);
97 paginator.beforeHostMethod('_mousewheel', paginator._beforeHostMousewheel);
98 paginator.beforeHostMethod('_flick', paginator._beforeHostFlick);
99 paginator.afterHostMethod('_onGestureMoveEnd', paginator._afterHostGestureMoveEnd);
100 paginator.afterHostMethod('_uiDimensionsChange', paginator._afterHostUIDimensionsChange);
101 paginator.afterHostMethod('syncUI', paginator._afterHostSyncUI);
103 // Host event listeners
104 paginator.afterHostEvent('render', paginator._afterHostRender);
105 paginator.afterHostEvent('scrollEnd', paginator._afterHostScrollEnded);
111 * @method _afterHostRender
112 * @param e {Event.Facade} The event facade
115 _afterHostRender: function () {
116 var paginator = this,
118 host = paginator._host,
119 index = paginator._cIndex,
120 paginatorAxis = paginator._cAxis,
121 pageNodes = paginator._getPageNodes(),
122 size = pageNodes.size(),
123 pageDim = paginator._pageDims[index];
125 if (paginatorAxis[DIM_Y]) {
126 host._maxScrollX = pageDim.maxScrollX;
128 else if (paginatorAxis[DIM_X]) {
129 host._maxScrollY = pageDim.maxScrollY;
132 // Set the page count
133 paginator.set(TOTAL, size);
137 paginator.scrollToIndex(index, 0);
140 // Add the paginator class
141 bb.addClass(CLASS_PAGED);
143 // Trigger the optimization process
144 paginator._optimize();
150 * @method _afterHostSyncUI
151 * @param e {Event.Facade} The event facade
154 _afterHostSyncUI: function () {
155 var paginator = this,
156 host = paginator._host,
157 pageNodes = paginator._getPageNodes(),
158 size = pageNodes.size();
160 // Set the page count
161 paginator.set(TOTAL, size);
163 // If paginator's 'axis' property is to be automatically determined, inherit host's property
164 if (paginator._cAxis === undefined) {
165 paginator._set(AXIS, host.get(AXIS));
170 * After host _uiDimensionsChange
172 * @method _afterHostUIDimensionsChange
173 * @param e {Event.Facade} The event facade
176 _afterHostUIDimensionsChange: function () {
178 var paginator = this,
179 host = paginator._host,
180 dims = host._getScrollDims(),
181 widgetWidth = dims.offsetWidth,
182 widgetHeight = dims.offsetHeight,
183 pageNodes = paginator._getPageNodes();
185 // Inefficient. Should not reinitialize every page every syncUI
186 pageNodes.each(function (node, i) {
187 var scrollWidth = node.get('scrollWidth'),
188 scrollHeight = node.get('scrollHeight'),
189 maxScrollX = Math.max(0, scrollWidth - widgetWidth), // Math.max to ensure we don't set it to a negative value
190 maxScrollY = Math.max(0, scrollHeight - widgetHeight);
192 // Don't initialize any page _pageDims that already have been.
193 if (!paginator._pageDims[i]) {
195 paginator._pageDims[i] = {
197 // Current scrollX & scrollY positions (default to 0)
201 // Maximum scrollable values
202 maxScrollX: maxScrollX,
203 maxScrollY: maxScrollY,
205 // Height & width of the page
211 paginator._pageDims[i].maxScrollX = maxScrollX;
212 paginator._pageDims[i].maxScrollY = maxScrollY;
219 * Executed before host.scrollTo
221 * @method _beforeHostScrollTo
222 * @param x {Number} The x-position to scroll to. (null for no movement)
223 * @param y {Number} The y-position to scroll to. (null for no movement)
224 * @param {Number} [duration] Duration, in ms, of the scroll animation (default is 0)
225 * @param {String} [easing] An easing equation if duration is set
226 * @param {String} [node] The node to move
229 _beforeHostScrollTo: function (x, y, duration, easing, node) {
230 var paginator = this,
231 host = paginator._host,
232 gesture = host._gesture,
233 index = paginator._cIndex,
234 paginatorAxis = paginator._cAxis,
235 pageNodes = paginator._getPageNodes(),
239 gestureAxis = gesture.axis;
241 // Null the opposite axis so it won't be modified by host.scrollTo
242 if (gestureAxis === DIM_Y) {
248 // If they are scrolling against the specified axis, pull out the page's node to have its own offset
249 if (paginatorAxis[gestureAxis] === false) {
250 node = pageNodes.item(index);
255 // Return the modified argument list
256 return new Y.Do.AlterArgs("new args", [x, y, duration, easing, node]);
260 * Executed after host._gestureMoveEnd
261 * Determines if the gesture should page prev or next (if at all)
263 * @method _afterHostGestureMoveEnd
264 * @param e {Event.Facade} The event facade
267 _afterHostGestureMoveEnd: function () {
269 // This was a flick, so we don't need to do anything here
270 if (this._host._gesture.flick) {
274 var paginator = this,
275 host = paginator._host,
276 gesture = host._gesture,
277 index = paginator._cIndex,
278 paginatorAxis = paginator._cAxis,
279 gestureAxis = gesture.axis,
280 isHorizontal = (gestureAxis === DIM_X),
281 delta = gesture[(isHorizontal ? 'deltaX' : 'deltaY')],
282 isForward = (delta > 0),
283 pageDims = paginator._pageDims[index],
284 halfway = pageDims[(isHorizontal ? 'width' : 'height')] / 2,
285 isHalfway = (Math.abs(delta) >= halfway),
286 canScroll = paginatorAxis[gestureAxis],
290 if (isHalfway) { // TODO: This condition should probably be configurable
291 // Fire next()/prev()
292 paginator[(rtl === isForward ? 'prev' : 'next')]();
296 paginator.scrollToIndex(paginator.get(INDEX));
302 * Executed before host._mousewheel
303 * Prevents mousewheel events in some conditions
305 * @method _beforeHostMousewheel
306 * @param e {Event.Facade} The event facade
309 _beforeHostMousewheel: function (e) {
310 var paginator = this,
311 host = paginator._host,
313 isForward = (e.wheelDelta < 0),
314 paginatorAxis = paginator._cAxis;
316 // Only if the mousewheel event occurred on a DOM node inside the BB
317 if (bb.contains(e.target) && paginatorAxis[DIM_Y]) {
319 // Fire next()/prev()
320 paginator[(isForward ? 'next' : 'prev')]();
322 // prevent browser default behavior on mousewheel
325 // Block host._mousewheel from running
326 return new Y.Do.Prevent();
331 * Executed before host._flick
332 * Prevents flick events in some conditions
334 * @method _beforeHostFlick
335 * @param e {Event.Facade} The event facade
338 _beforeHostFlick: function (e) {
340 // If the widget is disabled
341 if (this._host.get(DISABLED)) {
345 // The drag was out of bounds, so do nothing (which will cause a snapback)
346 if (this._host._isOutOfBounds()){
347 return new Y.Do.Prevent();
350 var paginator = this,
351 host = paginator._host,
352 gesture = host._gesture,
353 paginatorAxis = paginator.get(AXIS),
355 velocity = flick.velocity,
356 flickAxis = flick.axis || false,
357 isForward = (velocity < 0),
358 canScroll = paginatorAxis[flickAxis],
361 // Store the flick data in the this._host._gesture object so it knows this was a flick
363 gesture.flick = flick;
366 // Can we scroll along this axis?
369 // Fire next()/prev()
370 paginator[(rtl === isForward ? 'prev' : 'next')]();
372 // Prevent flicks on the paginated axis
373 if (paginatorAxis[flickAxis]) {
374 return new Y.Do.Prevent();
380 * Executes after host's 'scrollEnd' event
381 * Runs cleanup operations
383 * @method _afterHostScrollEnded
384 * @param e {Event.Facade} The event facade
387 _afterHostScrollEnded: function () {
388 var paginator = this,
389 host = paginator._host,
390 index = paginator._cIndex,
391 scrollX = host.get(SCROLL_X),
392 scrollY = host.get(SCROLL_Y),
393 paginatorAxis = paginator._cAxis;
395 if (paginatorAxis[DIM_Y]) {
396 paginator._pageDims[index].scrollX = scrollX;
398 paginator._pageDims[index].scrollY = scrollY;
401 paginator._optimize();
405 * index attr change handler
407 * @method _afterIndexChange
408 * @param e {Event.Facade} The event facade
411 _afterIndexChange: function (e) {
412 var paginator = this,
413 host = paginator._host,
415 pageDims = paginator._pageDims[index],
416 hostAxis = host._cAxis,
417 paginatorAxis = paginator._cAxis;
419 // Cache the new index value
420 paginator._cIndex = index;
422 // For dual-axis instances, we need to hack some host properties to the
423 // current page's max height/width and current stored offset
424 if (hostAxis[DIM_X] && hostAxis[DIM_Y]) {
425 if (paginatorAxis[DIM_Y]) {
426 host._maxScrollX = pageDims.maxScrollX;
427 host.set(SCROLL_X, pageDims.scrollX, { src: UI });
429 else if (paginatorAxis[DIM_X]) {
430 host._maxScrollY = pageDims.maxScrollY;
431 host.set(SCROLL_Y, pageDims.scrollY, { src: UI });
436 paginator.scrollToIndex(index);
441 * Optimization: Hides the pages not near the viewport
446 _optimize: function () {
448 if (!this._optimizeMemory) {
452 var paginator = this,
453 currentIndex = paginator._cIndex,
454 pageNodes = paginator._getStage(currentIndex);
456 // Show the pages in/near the viewport & hide the rest
457 paginator._showNodes(pageNodes.visible);
458 paginator._hideNodes(pageNodes.hidden);
462 * Optimization: Determines which nodes should be visible, and which should be hidden.
465 * @param index {Number} The page index # intended to be in focus.
469 _getStage: function (index) {
470 var paginator = this,
471 pageBuffer = paginator._pageBuffer,
472 pageCount = paginator.get(TOTAL),
473 pageNodes = paginator._getPageNodes(),
474 start = Math.max(0, index - pageBuffer),
475 end = Math.min(pageCount, index + 1 + pageBuffer); // noninclusive
478 visible: pageNodes.splice(start, end - start),
484 * A utility method to show node(s)
487 * @param nodeList {Object} The list of nodes to show
490 _showNodes: function (nodeList) {
492 nodeList.removeClass(CLASS_HIDDEN).setStyle('visibility', '');
497 * A utility method to hide node(s)
500 * @param nodeList {Object} The list of nodes to hide
503 _hideNodes: function (nodeList) {
505 nodeList.addClass(CLASS_HIDDEN).setStyle('visibility', 'hidden');
510 * Gets a nodeList for the "pages"
512 * @method _getPageNodes
516 _getPageNodes: function () {
517 var paginator = this,
518 host = paginator._host,
520 pageSelector = paginator.get(SELECTOR),
521 pageNodes = (pageSelector ? cb.all(pageSelector) : cb.get('children'));
527 * Scroll to the next page, with animation
532 var paginator = this,
533 scrollview = paginator._host,
534 index = paginator._cIndex,
536 total = paginator.get(TOTAL);
538 // If the widget is disabled, ignore
539 if (scrollview.get(DISABLED)) {
543 // If the target index is greater than the page count, ignore
544 if (target >= total) {
549 paginator.set(INDEX, target);
553 * Scroll to the previous page, with animation
558 var paginator = this,
559 scrollview = paginator._host,
560 index = paginator._cIndex,
563 // If the widget is disabled, ignore
564 if (scrollview.get(DISABLED)) {
568 // If the target index is before the first page, ignore
574 paginator.set(INDEX, target);
578 * Deprecated for 3.7.0.
582 scrollTo: function () {
583 return this.scrollToIndex.apply(this, arguments);
587 * Scroll to a given page in the scrollview
589 * @method scrollToIndex
591 * @param index {Number} The index of the page to scroll to
592 * @param {Number} [duration] The number of ms the animation should last
593 * @param {String} [easing] The timing function to use in the animation
595 scrollToIndex: function (index, duration, easing) {
596 var paginator = this,
597 host = paginator._host,
598 pageNode = paginator._getPageNodes().item(index),
599 scrollAxis = (paginator._cAxis[DIM_X] ? SCROLL_X : SCROLL_Y),
600 scrollOffset = pageNode.get(scrollAxis === SCROLL_X ? 'offsetLeft' : 'offsetTop');
602 duration = (duration !== undefined) ? duration : PaginatorPlugin.TRANSITION.duration;
603 easing = (easing !== undefined) ? easing : PaginatorPlugin.TRANSITION.easing;
605 // Set the index ATTR to the specified index value
606 paginator.set(INDEX, index, { src: UI });
608 // Makes sure the viewport nodes are visible
609 paginator._showNodes(pageNode);
611 // Scroll to the offset
612 host.set(scrollAxis, scrollOffset, {
619 * Setter for 'axis' attribute
621 * @method _axisSetter
622 * @param val {Mixed} A string ('x', 'y', 'xy') to specify which axis/axes to allow scrolling on
623 * @param name {String} The attribute name
624 * @return {Object} An object to specify scrollability on the x & y axes
628 _axisSetter: function (val) {
630 // Turn a string into an axis object
631 if (Y.Lang.isString(val)) {
633 x: (val.match(/x/i) ? true : false),
634 y: (val.match(/y/i) ? true : false)
641 * After listener for the axis attribute
643 * @method _afterAxisChange
644 * @param e {Event.Facade} The event facade
647 _afterAxisChange: function (e) {
648 this._cAxis = e.newVal;
651 // End prototype properties
658 * The identity of the plugin
662 * @default 'pluginScrollViewPaginator'
667 NAME: 'pluginScrollViewPaginator',
670 * The namespace on which the plugin will reside
680 * The default attribute configuration for the plugin
689 * Specifies ability to scroll on x, y, or x and y axis/axes.
690 * If unspecified, it inherits from the host instance.
696 setter: '_axisSetter',
697 writeOnce: 'initOnly'
701 * CSS selector for a page inside the scrollview. The scrollview
702 * will snap to the closest page.
704 * @attribute selector
713 * The active page number for a paged scrollview
724 * The total number of pages
736 * The default snap to current duration and easing values used on scroll end.
738 * @property SNAP_TO_CURRENT
746 // End static properties
750 Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
753 }, '3.13.0', {"requires": ["plugin", "classnamemanager"]});