scrollview-paginator.js revision 0e232b16e640a3801393ca223d42fd1e0e9e83c3
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTrippYUI.add('scrollview-paginator', function(Y) {
0fdefaa9ca017edfb76b736c825b34186f33045aTripp
0fdefaa9ca017edfb76b736c825b34186f33045aTripp/**
0fdefaa9ca017edfb76b736c825b34186f33045aTripp * Provides a plugin, which adds pagination support to ScrollView instances
0fdefaa9ca017edfb76b736c825b34186f33045aTripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @module scrollview-paginator
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp */
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Trippvar BOUNCE_DECELERATION_CONST = 0.5,
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp UI = Y.ScrollView.UI_SRC;
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp/**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * Scrollview plugin that adds support for paging
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @class ScrollViewPaginatorPlugin
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @namespace Plugin
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @extends Plugin.Base
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @constructor
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
828c58761d90445b8b9d20a82d85dc1479317f71Trippfunction PaginatorPlugin() {
828c58761d90445b8b9d20a82d85dc1479317f71Tripp PaginatorPlugin.superclass.constructor.apply(this, arguments);
828c58761d90445b8b9d20a82d85dc1479317f71Tripp}
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
04f886d0ad2a12c3c0e4ec29a1c42e8732e9327fTripp/**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * The identity of the plugin
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @property ScrollViewPaginator.NAME
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @type String
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @default 'paginatorPlugin'
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @static
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
828c58761d90445b8b9d20a82d85dc1479317f71TrippPaginatorPlugin.NAME = 'pluginScrollViewPaginator';
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp/**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * The namespace on which the plugin will reside
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @property ScrollViewPaginator.NS
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @type String
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @default 'pages'
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @static
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
828c58761d90445b8b9d20a82d85dc1479317f71TrippPaginatorPlugin.NS = 'pages';
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp/**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * The default attribute configuration for the plugin
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @property ScrollViewPaginator.ATTRS
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @type Object
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @static
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
828c58761d90445b8b9d20a82d85dc1479317f71TrippPaginatorPlugin.ATTRS = {
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * CSS selector for a page inside the scrollview. The scrollview
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * will snap to the closest page.
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp *
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp * @attribute selector
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp * @type {String}
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp */
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp selector: {
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp value: null
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp },
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp /**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * The active page number for a paged scrollview
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @attribute index
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @type {Number}
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @default 0
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
828c58761d90445b8b9d20a82d85dc1479317f71Tripp index: {
828c58761d90445b8b9d20a82d85dc1479317f71Tripp value: 0
828c58761d90445b8b9d20a82d85dc1479317f71Tripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * The total number of pages
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @attribute total
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @type {Number}
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @default 0
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp */
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp total: {
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp value: 0
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp }
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp};
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp
a75ebc38c1de401b679953a9b87bd323f0f48d02TrippY.extend(PaginatorPlugin, Y.Plugin.Base, {
a75ebc38c1de401b679953a9b87bd323f0f48d02Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp /**
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * Designated initializer
828c58761d90445b8b9d20a82d85dc1479317f71Tripp *
828c58761d90445b8b9d20a82d85dc1479317f71Tripp * @method initializer
828c58761d90445b8b9d20a82d85dc1479317f71Tripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp initializer: function() {
828c58761d90445b8b9d20a82d85dc1479317f71Tripp var host;
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp host = this._host = this.get('host');
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp this.afterHostMethod('_uiDimensionsChange', this._calculatePageOffsets);
828c58761d90445b8b9d20a82d85dc1479317f71Tripp this.afterHostMethod('_onGestureMoveStart', this._setBoundaryPoints);
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp this.afterHostMethod('_flick', this._afterFlick);
828c58761d90445b8b9d20a82d85dc1479317f71Tripp this.afterHostEvent('scrollEnd', this._scrollEnded);
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp this.after('indexChange', this._afterIndexChange);
828c58761d90445b8b9d20a82d85dc1479317f71Tripp
828c58761d90445b8b9d20a82d85dc1479317f71Tripp if(host.get('bounce') !== 0) {
828c58761d90445b8b9d20a82d85dc1479317f71Tripp // Change bounce constant to increase friction
this._originalHostBounce = host.get('bounce');
host.set('bounce', BOUNCE_DECELERATION_CONST);
}
},
/**
* Destructor removes anything added by the plugin
*
* @method destroy
*/
destroy: function() {
var host = this._host;
if(host.get('bounce') !== 0) {
host.set('bounce', this._originalHostBounce);
}
},
/**
* Pre-calculate the min/max boundary points when the contentBox changes
*
* @method _calculatePageOffsets
* @protected
*/
_calculatePageOffsets: function() {
var host = this._host,
cb = host.get('contentBox'),
pageSelector = this.get('selector'),
pages,
points = [];
// Pre-calculate min/max values for each page
pages = pageSelector ? cb.all(pageSelector) : cb.get('children');
pages.each(function(node, i) {
points.push(node.get('offsetLeft'));
}, this);
points.push(host._scrollWidth - host.get('width'));
this._minPoints = points;
this.set('total', pages.size());
},
/**
* After host movestart handler, reset min/max scroll values on the host
* based on the page elements
*
* @method _setBoundaryPoints
* @param e {Event.Facade} The gesturemovestart event
*/
_setBoundaryPoints: function(e) {
var host = this._host,
pageIndex = this.get('index');
// Set min/max points
if(host._scrollsHorizontal) {
if(Y.Lang.isNumber(this._minPoints[pageIndex-1])) {
host._minScrollX = this._minPoints[pageIndex-1];
} else {
host._minScrollX = this._minPoints[pageIndex];
}
host._maxScrollX = this._minPoints[pageIndex+1];
}
},
/**
* Executed as soon as the flick event occurs. This is needed to
* determine if the next or prev page should be activated.
*
* @method _afterFlick
* @param e {Event.Facade} The flick event facade.
* @protected
*/
_afterFlick: function(e) {
var host = this._host,
velocity = host._currentVelocity,
positive = velocity > 0,
speed = Math.abs(velocity),
pageIndex = this.get('index'),
pageCount = this.get('total');
// @TODO: find the right minimum velocity to turn the page.
// Right now, hard-coding at 1.
if(speed < 1) {
host._currentVelocity = positive ? 1 : -1;
}
if(positive && pageIndex < pageCount-1) {
this.set('index', pageIndex+1, { src: UI });
} else if(!positive && pageIndex > 0){
this.set('index', pageIndex-1, { src: UI });
}
},
/**
* scrollEnd handler detects if a page needs to change
*
* @method _scrollEnded
* @param {Event.Facade}
* @protected
*/
_scrollEnded: function(e) {
var host = this._host,
pageIndex = this.get('index'),
pageCount = this.get('total');
// Stale scroll - snap to current/next/prev page
if(e.staleScroll) {
if(host._scrolledHalfway) {
if(host._scrolledForward && pageIndex < pageCount-1) {
this.set('index', pageIndex+1);
} else if(pageIndex > 0) {
this.set('index', pageIndex-1);
} else {
this.snapToCurrent();
}
} else {
this.snapToCurrent();
}
}
},
/**
* index attr change handler
*
* @method _afterIndexChange
* @protected
*/
_afterIndexChange: function(e) {
if(e.src !== UI) {
this._uiIndex(e.newVal);
}
},
/**
* Update the UI based on the current page index
*
* @method _uiIndex
* @protected
*/
_uiIndex: function(index) {
this.scrollTo(index, 350, 'ease-out');
},
/**
* Scroll to the next page in the scrollview, with animation
*
* @method next
* @param disableAnim {Boolean} If true, no animation is used
*/
next: function(disableAnim) {
var index = this.get('index');
if(index < this.get('total')-1) {
this.set('index', index+1);
}
},
/**
* Scroll to the previous page in the scrollview, with animation
*
* @method prev
* @param disableAnim {Boolean} If true, no animation is used
*/
prev: function(disableAnim) {
var index = this.get('index');
if(index > 0) {
this.set('index', index-1);
}
},
/**
* Scroll to a given page in the scrollview, with animation.
*
* @method scrollTo
* @param index {Number} The index of the page to scroll to
* @param duration {Number} The number of ms the animation should last
* @param easing {String} The timing function to use in the animation
*/
scrollTo: function(index, duration, easing) {
var host = this._host,
x = host.get('scrollX');
if(host._scrollsHorizontal) {
x = this._minPoints[index];
host.set('scrollX', x, {
duration: duration,
easing: easing
});
}
},
/**
* Snaps the scrollview to the currently selected page
*
* @method snapToCurrent
*/
snapToCurrent: function() {
this._host.set('scrollX', this._minPoints[this.get('index')], {
duration: 300,
easing: 'ease-out'
});
}
});
Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
}, '@VERSION@' ,{skinnable:true, requires:['plugin']});