- /**
- * Provides a plugin that adds pagination support to ScrollView instances
- *
- * @module scrollview-paginator
- */
- var getClassName = Y.ClassNameManager.getClassName,
- SCROLLVIEW = 'scrollview',
- CLASS_HIDDEN = getClassName(SCROLLVIEW, 'hidden'),
- CLASS_PAGED = getClassName(SCROLLVIEW, 'paged'),
- UI = (Y.ScrollView) ? Y.ScrollView.UI_SRC : 'ui',
- INDEX = 'index',
- SCROLL_X = 'scrollX',
- SCROLL_Y = 'scrollY',
- TOTAL = 'total',
- DISABLED = 'disabled',
- HOST = 'host',
- SELECTOR = 'selector',
- AXIS = 'axis',
- DIM_X = 'x',
- DIM_Y = 'y';
-
- /**
- * Scrollview plugin that adds support for paging
- *
- * @class ScrollViewPaginator
- * @namespace Plugin
- * @extends Plugin.Base
- * @constructor
- */
- function PaginatorPlugin() {
- PaginatorPlugin.superclass.constructor.apply(this, arguments);
- }
-
- Y.extend(PaginatorPlugin, Y.Plugin.Base, {
-
- /**
- * Designated initializer
- *
- * @method initializer
- * @param {Object} Configuration object for the plugin
- */
- initializer: function (config) {
- var paginator = this,
- host = paginator.get(HOST);
-
- // Initialize & default
- paginator._pageDims = [];
- paginator._pageBuffer = 1;
- paginator._optimizeMemory = false;
-
- // Cache some values
- paginator._host = host;
- paginator._bb = host._bb;
- paginator._cb = host._cb;
- paginator._cIndex = paginator.get(INDEX);
- paginator._cAxis = paginator.get(AXIS);
-
- // Apply configs
- if (config._optimizeMemory) {
- paginator._optimizeMemory = config._optimizeMemory;
- }
-
- if (config._pageBuffer) {
- paginator._pageBuffer = config._pageBuffer;
- }
-
- // Attach event bindings
- paginator._bindAttrs();
- },
-
- /**
- *
- *
- * @method _bindAttrs
- * @private
- */
- _bindAttrs: function () {
- var paginator = this;
-
- // Event listeners
- paginator.after({
- 'indexChange': paginator._afterIndexChange,
- 'axisChange': paginator._afterAxisChange
- });
-
- // Host method listeners
- paginator.beforeHostMethod('scrollTo', paginator._beforeHostScrollTo);
- paginator.beforeHostMethod('_mousewheel', paginator._beforeHostMousewheel);
- paginator.beforeHostMethod('_flick', paginator._beforeHostFlick);
- paginator.afterHostMethod('_onGestureMoveEnd', paginator._afterHostGestureMoveEnd);
- paginator.afterHostMethod('_uiDimensionsChange', paginator._afterHostUIDimensionsChange);
- paginator.afterHostMethod('syncUI', paginator._afterHostSyncUI);
-
- // Host event listeners
- paginator.afterHostEvent('render', paginator._afterHostRender);
- paginator.afterHostEvent('scrollEnd', paginator._afterHostScrollEnded);
- },
-
- /**
- * After host render
- *
- * @method _afterHostRender
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterHostRender: function () {
- var paginator = this,
- bb = paginator._bb,
- host = paginator._host,
- index = paginator._cIndex,
- paginatorAxis = paginator._cAxis,
- pageNodes = paginator._getPageNodes(),
- size = pageNodes.size(),
- pageDim = paginator._pageDims[index];
-
- if (paginatorAxis[DIM_Y]) {
- host._maxScrollX = pageDim.maxScrollX;
- }
- else if (paginatorAxis[DIM_X]) {
- host._maxScrollY = pageDim.maxScrollY;
- }
-
- // Set the page count
- paginator.set(TOTAL, size);
-
- // Jump to the index
- if (index !== 0) {
- paginator.scrollToIndex(index, 0);
- }
-
- // Add the paginator class
- bb.addClass(CLASS_PAGED);
-
- // Trigger the optimization process
- paginator._optimize();
- },
-
- /**
- * After host syncUI
- *
- * @method _afterHostSyncUI
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterHostSyncUI: function () {
- var paginator = this,
- host = paginator._host,
- pageNodes = paginator._getPageNodes(),
- size = pageNodes.size();
-
- // Set the page count
- paginator.set(TOTAL, size);
-
- // If paginator's 'axis' property is to be automatically determined, inherit host's property
- if (paginator._cAxis === undefined) {
- paginator._set(AXIS, host.get(AXIS));
- }
- },
-
- /**
- * After host _uiDimensionsChange
- *
- * @method _afterHostUIDimensionsChange
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterHostUIDimensionsChange: function () {
-
- var paginator = this,
- host = paginator._host,
- dims = host._getScrollDims(),
- widgetWidth = dims.offsetWidth,
- widgetHeight = dims.offsetHeight,
- pageNodes = paginator._getPageNodes();
-
- // Inefficient. Should not reinitialize every page every syncUI
- pageNodes.each(function (node, i) {
- var scrollWidth = node.get('scrollWidth'),
- scrollHeight = node.get('scrollHeight'),
- maxScrollX = Math.max(0, scrollWidth - widgetWidth), // Math.max to ensure we don't set it to a negative value
- maxScrollY = Math.max(0, scrollHeight - widgetHeight);
-
- // Don't initialize any page _pageDims that already have been.
- if (!paginator._pageDims[i]) {
-
- paginator._pageDims[i] = {
-
- // Current scrollX & scrollY positions (default to 0)
- scrollX: 0,
- scrollY: 0,
-
- // Maximum scrollable values
- maxScrollX: maxScrollX,
- maxScrollY: maxScrollY,
-
- // Height & width of the page
- width: scrollWidth,
- height: scrollHeight
- };
-
- } else {
- paginator._pageDims[i].maxScrollX = maxScrollX;
- paginator._pageDims[i].maxScrollY = maxScrollY;
- }
-
- });
- },
-
- /**
- * Executed before host.scrollTo
- *
- * @method _beforeHostScrollTo
- * @param x {Number} The x-position to scroll to. (null for no movement)
- * @param y {Number} The y-position to scroll to. (null for no movement)
- * @param {Number} [duration] Duration, in ms, of the scroll animation (default is 0)
- * @param {String} [easing] An easing equation if duration is set
- * @param {String} [node] The node to move
- * @protected
- */
- _beforeHostScrollTo: function (x, y, duration, easing, node) {
- var paginator = this,
- host = paginator._host,
- gesture = host._gesture,
- index = paginator._cIndex,
- paginatorAxis = paginator._cAxis,
- pageNodes = paginator._getPageNodes(),
- gestureAxis;
-
- if (gesture) {
- gestureAxis = gesture.axis;
-
- // Null the opposite axis so it won't be modified by host.scrollTo
- if (gestureAxis === DIM_Y) {
- x = null;
- } else {
- y = null;
- }
-
- // If they are scrolling against the specified axis, pull out the page's node to have its own offset
- if (paginatorAxis[gestureAxis] === false) {
- node = pageNodes.item(index);
- }
-
- }
-
- // Return the modified argument list
- return new Y.Do.AlterArgs("new args", [x, y, duration, easing, node]);
- },
-
- /**
- * Executed after host._gestureMoveEnd
- * Determines if the gesture should page prev or next (if at all)
- *
- * @method _afterHostGestureMoveEnd
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterHostGestureMoveEnd: function () {
-
- // This was a flick, so we don't need to do anything here
- if (this._host._gesture.flick) {
- return;
- }
-
- var paginator = this,
- host = paginator._host,
- gesture = host._gesture,
- index = paginator._cIndex,
- paginatorAxis = paginator._cAxis,
- gestureAxis = gesture.axis,
- isHorizontal = (gestureAxis === DIM_X),
- delta = gesture[(isHorizontal ? 'deltaX' : 'deltaY')],
- isForward = (delta > 0),
- pageDims = paginator._pageDims[index],
- halfway = pageDims[(isHorizontal ? 'width' : 'height')] / 2,
- isHalfway = (Math.abs(delta) >= halfway),
- canScroll = paginatorAxis[gestureAxis],
- rtl = host.rtl;
-
- if (canScroll) {
- if (isHalfway) { // TODO: This condition should probably be configurable
- // Fire next()/prev()
- paginator[(rtl === isForward ? 'prev' : 'next')]();
- }
- // Scrollback
- else {
- paginator.scrollToIndex(paginator.get(INDEX));
- }
- }
- },
-
- /**
- * Executed before host._mousewheel
- * Prevents mousewheel events in some conditions
- *
- * @method _beforeHostMousewheel
- * @param e {EventFacade} The event facade
- * @protected
- */
- _beforeHostMousewheel: function (e) {
- var paginator = this,
- host = paginator._host,
- bb = host._bb,
- isForward = (e.wheelDelta < 0),
- paginatorAxis = paginator._cAxis;
-
- // Only if the mousewheel event occurred on a DOM node inside the BB
- if (bb.contains(e.target) && paginatorAxis[DIM_Y]) {
-
- // Fire next()/prev()
- paginator[(isForward ? 'next' : 'prev')]();
-
- // prevent browser default behavior on mousewheel
- e.preventDefault();
-
- // Block host._mousewheel from running
- return new Y.Do.Prevent();
- }
- },
-
- /**
- * Executed before host._flick
- * Prevents flick events in some conditions
- *
- * @method _beforeHostFlick
- * @param e {EventFacade} The event facade
- * @protected
- */
- _beforeHostFlick: function (e) {
-
- // If the widget is disabled
- if (this._host.get(DISABLED)) {
- return false;
- }
-
- // The drag was out of bounds, so do nothing (which will cause a snapback)
- if (this._host._isOutOfBounds()){
- return new Y.Do.Prevent();
- }
-
- var paginator = this,
- host = paginator._host,
- gesture = host._gesture,
- paginatorAxis = paginator.get(AXIS),
- flick = e.flick,
- velocity = flick.velocity,
- flickAxis = flick.axis || false,
- isForward = (velocity < 0),
- canScroll = paginatorAxis[flickAxis],
- rtl = host.rtl;
-
- // Store the flick data in the this._host._gesture object so it knows this was a flick
- if (gesture) {
- gesture.flick = flick;
- }
-
- // Can we scroll along this axis?
- if (canScroll) {
-
- // Fire next()/prev()
- paginator[(rtl === isForward ? 'prev' : 'next')]();
-
- // Prevent flicks on the paginated axis
- if (paginatorAxis[flickAxis]) {
- return new Y.Do.Prevent();
- }
- }
- },
-
- /**
- * Executes after host's 'scrollEnd' event
- * Runs cleanup operations
- *
- * @method _afterHostScrollEnded
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterHostScrollEnded: function () {
- var paginator = this,
- host = paginator._host,
- index = paginator._cIndex,
- scrollX = host.get(SCROLL_X),
- scrollY = host.get(SCROLL_Y),
- paginatorAxis = paginator._cAxis;
-
- if (paginatorAxis[DIM_Y]) {
- paginator._pageDims[index].scrollX = scrollX;
- } else {
- paginator._pageDims[index].scrollY = scrollY;
- }
-
- paginator._optimize();
- },
-
- /**
- * index attr change handler
- *
- * @method _afterIndexChange
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterIndexChange: function (e) {
- var paginator = this,
- host = paginator._host,
- index = e.newVal,
- pageDims = paginator._pageDims[index],
- hostAxis = host._cAxis,
- paginatorAxis = paginator._cAxis;
-
- // Cache the new index value
- paginator._cIndex = index;
-
- // For dual-axis instances, we need to hack some host properties to the
- // current page's max height/width and current stored offset
- if (hostAxis[DIM_X] && hostAxis[DIM_Y]) {
- if (paginatorAxis[DIM_Y]) {
- host._maxScrollX = pageDims.maxScrollX;
- host.set(SCROLL_X, pageDims.scrollX, { src: UI });
- }
- else if (paginatorAxis[DIM_X]) {
- host._maxScrollY = pageDims.maxScrollY;
- host.set(SCROLL_Y, pageDims.scrollY, { src: UI });
- }
- }
-
- if (e.src !== UI) {
- paginator.scrollToIndex(index);
- }
- },
-
- /**
- * Optimization: Hides the pages not near the viewport
- *
- * @method _optimize
- * @protected
- */
- _optimize: function () {
-
- if (!this._optimizeMemory) {
- return false;
- }
-
- var paginator = this,
- currentIndex = paginator._cIndex,
- pageNodes = paginator._getStage(currentIndex);
-
- // Show the pages in/near the viewport & hide the rest
- paginator._showNodes(pageNodes.visible);
- paginator._hideNodes(pageNodes.hidden);
- },
-
- /**
- * Optimization: Determines which nodes should be visible, and which should be hidden.
- *
- * @method _getStage
- * @param index {Number} The page index # intended to be in focus.
- * @return {object}
- * @protected
- */
- _getStage: function (index) {
- var paginator = this,
- pageBuffer = paginator._pageBuffer,
- pageCount = paginator.get(TOTAL),
- pageNodes = paginator._getPageNodes(),
- start = Math.max(0, index - pageBuffer),
- end = Math.min(pageCount, index + 1 + pageBuffer); // noninclusive
-
- return {
- visible: pageNodes.splice(start, end - start),
- hidden: pageNodes
- };
- },
-
- /**
- * A utility method to show node(s)
- *
- * @method _showNodes
- * @param nodeList {Object} The list of nodes to show
- * @protected
- */
- _showNodes: function (nodeList) {
- if (nodeList) {
- nodeList.removeClass(CLASS_HIDDEN).setStyle('visibility', '');
- }
- },
-
- /**
- * A utility method to hide node(s)
- *
- * @method _hideNodes
- * @param nodeList {Object} The list of nodes to hide
- * @protected
- */
- _hideNodes: function (nodeList) {
- if (nodeList) {
- nodeList.addClass(CLASS_HIDDEN).setStyle('visibility', 'hidden');
- }
- },
-
- /**
- * Gets a nodeList for the "pages"
- *
- * @method _getPageNodes
- * @protected
- * @return {nodeList}
- */
- _getPageNodes: function () {
- var paginator = this,
- host = paginator._host,
- cb = host._cb,
- pageSelector = paginator.get(SELECTOR),
- pageNodes = (pageSelector ? cb.all(pageSelector) : cb.get('children'));
-
- return pageNodes;
- },
-
- /**
- * Scroll to the next page, with animation
- *
- * @method next
- */
- next: function () {
- var paginator = this,
- scrollview = paginator._host,
- index = paginator._cIndex,
- target = index + 1,
- total = paginator.get(TOTAL);
-
- // If the widget is disabled, ignore
- if (scrollview.get(DISABLED)) {
- return;
- }
-
- // If the target index is greater than the page count, ignore
- if (target >= total) {
- return;
- }
-
- // Update the index
- paginator.set(INDEX, target);
- },
-
- /**
- * Scroll to the previous page, with animation
- *
- * @method prev
- */
- prev: function () {
- var paginator = this,
- scrollview = paginator._host,
- index = paginator._cIndex,
- target = index - 1;
-
- // If the widget is disabled, ignore
- if (scrollview.get(DISABLED)) {
- return;
- }
-
- // If the target index is before the first page, ignore
- if (target < 0) {
- return;
- }
-
- // Update the index
- paginator.set(INDEX, target);
- },
-
- /**
- * Deprecated for 3.7.0.
- * @method scrollTo
- * @deprecated
- */
- scrollTo: function () {
- return this.scrollToIndex.apply(this, arguments);
- },
-
- /**
- * Scroll to a given page in the scrollview
- *
- * @method scrollToIndex
- * @since 3.7.0
- * @param index {Number} The index of the page to scroll to
- * @param {Number} [duration] The number of ms the animation should last
- * @param {String} [easing] The timing function to use in the animation
- */
- scrollToIndex: function (index, duration, easing) {
- var paginator = this,
- host = paginator._host,
- pageNode = paginator._getPageNodes().item(index),
- scrollAxis = (paginator._cAxis[DIM_X] ? SCROLL_X : SCROLL_Y),
- scrollOffset = pageNode.get(scrollAxis === SCROLL_X ? 'offsetLeft' : 'offsetTop');
-
- duration = (duration !== undefined) ? duration : PaginatorPlugin.TRANSITION.duration;
- easing = (easing !== undefined) ? easing : PaginatorPlugin.TRANSITION.easing;
-
- // Set the index ATTR to the specified index value
- paginator.set(INDEX, index, { src: UI });
-
- // Makes sure the viewport nodes are visible
- paginator._showNodes(pageNode);
-
- // Scroll to the offset
- host.set(scrollAxis, scrollOffset, {
- duration: duration,
- easing: easing
- });
- },
-
- /**
- * Setter for 'axis' attribute
- *
- * @method _axisSetter
- * @param val {Mixed} A string ('x', 'y', 'xy') to specify which axis/axes to allow scrolling on
- * @param name {String} The attribute name
- * @return {Object} An object to specify scrollability on the x & y axes
- *
- * @protected
- */
- _axisSetter: function (val) {
-
- // Turn a string into an axis object
- if (Y.Lang.isString(val)) {
- return {
- x: (val.match(/x/i) ? true : false),
- y: (val.match(/y/i) ? true : false)
- };
- }
- },
-
-
- /**
- * After listener for the axis attribute
- *
- * @method _afterAxisChange
- * @param e {EventFacade} The event facade
- * @protected
- */
- _afterAxisChange: function (e) {
- this._cAxis = e.newVal;
- }
-
- // End prototype properties
-
- }, {
-
- // Static properties
-
- /**
- * The identity of the plugin
- *
- * @property NAME
- * @type String
- * @default 'pluginScrollViewPaginator'
- * @readOnly
- * @protected
- * @static
- */
- NAME: 'pluginScrollViewPaginator',
-
- /**
- * The namespace on which the plugin will reside
- *
- * @property NS
- * @type String
- * @default 'pages'
- * @static
- */
- NS: 'pages',
-
- /**
- * The default attribute configuration for the plugin
- *
- * @property ATTRS
- * @type {Object}
- * @static
- */
- ATTRS: {
-
- /**
- * Specifies ability to scroll on x, y, or x and y axis/axes.
- * If unspecified, it inherits from the host instance.
- *
- * @attribute axis
- * @type String
- */
- axis: {
- setter: '_axisSetter',
- writeOnce: 'initOnly'
- },
-
- /**
- * CSS selector for a page inside the scrollview. The scrollview
- * will snap to the closest page.
- *
- * @attribute selector
- * @type {String}
- * @default null
- */
- selector: {
- value: null
- },
-
- /**
- * The active page number for a paged scrollview
- *
- * @attribute index
- * @type {Number}
- * @default 0
- */
- index: {
- value: 0
- },
-
- /**
- * The total number of pages
- *
- * @attribute total
- * @type {Number}
- * @default 0
- */
- total: {
- value: 0
- }
- },
-
- /**
- * The default snap to current duration and easing values used on scroll end.
- *
- * @property SNAP_TO_CURRENT
- * @static
- */
- TRANSITION: {
- duration: 300,
- easing: 'ease-out'
- }
-
- // End static properties
-
- });
-
- Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
-
-