3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('scrollview-paginator', function(Y) {
10 * Provides a plugin, which adds pagination support to ScrollView instances
12 * @module scrollview-paginator
15 var UI = (Y.ScrollView) ? Y.ScrollView.UI_SRC : "ui",
17 PREVINDEX = "prevIndex",
21 BOUNDING_BOX = "boundingBox",
22 CONTENT_BOX = "contentBox",
23 MAX_PAGE_COUNT = 3; // @TODO: Make configurable?
26 * Scrollview plugin that adds support for paging
28 * @class ScrollViewPaginator
30 * @extends Plugin.Base
33 function PaginatorPlugin() {
34 PaginatorPlugin.superclass.constructor.apply(this, arguments);
38 * The identity of the plugin
42 * @default 'paginatorPlugin'
45 PaginatorPlugin.NAME = 'pluginScrollViewPaginator';
48 * The namespace on which the plugin will reside
55 PaginatorPlugin.NS = 'pages';
58 * The default attribute configuration for the plugin
64 PaginatorPlugin.ATTRS = {
67 * CSS selector for a page inside the scrollview. The scrollview
68 * will snap to the closest page.
78 * The active page number for a paged scrollview
89 * The active page number for a paged scrollview
100 * The total number of pages
111 Y.extend(PaginatorPlugin, Y.Plugin.Base, {
113 optimizeMemory: false,
118 * Designated initializer
120 * @method initializer
122 initializer: function(config) {
123 var paginator = this,
124 optimizeMemory = config.optimizeMemory || optimizeMemory;
126 paginator._host = paginator.get('host');
127 paginator.beforeHostMethod('_flickFrame', paginator._flickFrame);
128 paginator.afterHostMethod('_uiDimensionsChange', paginator._calcOffsets);
129 paginator.afterHostEvent('scrollEnd', paginator._scrollEnded);
130 paginator.afterHostEvent('render', paginator._afterRender);
131 paginator.after('indexChange', paginator._afterIndexChange);
132 paginator.optimizeMemory = optimizeMemory;
133 paginator._pageNodes = new Y.NodeList();
137 * Calculate the page boundary offsets
139 * @method _calcOffsets
142 _calcOffsets : function() {
143 var host = this._host,
144 cb = host.get(CONTENT_BOX),
145 bb = host.get(BOUNDING_BOX),
146 vert = host._scrollsVertical,
147 size = (vert) ? host._scrollHeight : host._scrollWidth,
148 pageSelector = this.get("selector"),
149 optimizeMemory = this.optimizeMemory,
150 currentIndex = this.get(INDEX),
155 pages = pageSelector ? cb.all(pageSelector) : cb.get("children");
156 this._pageNodes = pages;
158 //Set the total # of pages
159 this.set(TOTAL, pages.size());
161 // Determine the offsets
162 this._pageOffsets = pages.get((vert) ? "offsetTop" : "offsetLeft");
164 if (optimizeMemory) {
166 this.set(PREVINDEX, currentIndex);
168 // Reduce the scroll width to the size of (3) pages (or whatever MAX_PAGE_COUNT is)
169 host._maxScrollX = this._pageOffsets[MAX_PAGE_COUNT-1];
171 // Empty the content-box. @TODO: leave {MAX_PAGE_COUNT} items in?
174 // Now, fill it with the first set of items
175 for (var i=0; i < MAX_PAGE_COUNT; i++) {
176 node = pages.item(currentIndex + i);
182 * Return the offset value where scrollview should scroll to.
183 * Neccesary because index # doesn't nessecarily map up to location in the DOM because of this._manageDOM()
185 * @method _getTargetOffset
186 * @param index {Number}
191 _getTargetOffset: function(index) {
192 var previous = this.get(PREVINDEX),
193 current = this.get(INDEX),
194 total = this.get(TOTAL),
195 forward = (previous < current) ? true : false,
196 pageOffsets = this._pageOffsets,
197 optimizeMemory = this.optimizeMemory,
198 offset, currentPageLocation;
200 // @todo: Clean this up. Probably do current (currentOffset - previousoffset) instead of assuming they're all the same width
201 if (optimizeMemory) {
204 currentPageLocation = 2;
207 currentPageLocation = 1;
211 if (current == (total-2)) {
212 currentPageLocation = 1;
215 currentPageLocation = 0;
220 currentPageLocation = index;
223 offset = pageOffsets[currentPageLocation]
228 * Executed to respond to the flick event, by over-riding the default flickFrame animation.
229 * This is needed to determine if the next or prev page should be activated.
231 * @method _flickFrame
234 _flickFrame: function() {
235 var host = this._host,
236 velocity = host._currentVelocity,
238 pageIndex = this.get(INDEX),
239 pageCount = this.get(TOTAL);
242 if (inc && pageIndex < pageCount-1) {
244 } else if (!inc && pageIndex > 0) {
249 return this._prevent;
253 * After host render handler
255 * @method _afterRender
256 * @param {Event.Facade}
259 _afterRender: function(e) {
260 var host = this._host;
262 host.get("boundingBox").addClass(host.getClassName("paged"));
266 * scrollEnd handler detects if a page needs to change
268 * @method _scrollEnded
269 * @param {Event.Facade}
272 _scrollEnded: function(e) {
273 var host = this._host,
274 pageIndex = this.get(INDEX),
275 pageCount = this.get(TOTAL),
276 trans = PaginatorPlugin.SNAP_TO_CURRENT,
277 optimizeMemory = this.optimizeMemory;
279 if(e.onGestureMoveEnd && !host._flicking) {
280 if(host._scrolledHalfway) {
281 if(host._scrolledForward && pageIndex < pageCount-1) {
283 } else if (pageIndex > 0) {
286 this.snapToCurrent(trans.duration, trans.easing);
289 this.snapToCurrent(trans.duration, trans.easing);
293 if (!e.onGestureMoveEnd){
294 if (optimizeMemory) {
298 this.set(PREVINDEX, pageIndex);
303 * Manages adding & removing slides from the DOM, to improve performance & memory usage
309 _manageDOM: function(){
310 var newSlide, addSlideMethod, nodeToRemove,
312 cb = host.get(CONTENT_BOX),
313 currentIndex = this.get(INDEX),
314 previousIndex = this.get(PREVINDEX),
315 total = this.get(TOTAL),
316 isForward = (previousIndex < currentIndex) ? true : false,
317 cbChildren = cb.get('children'),
318 pageNodes = this._pageNodes,
321 if (pageNodes && pageNodes.size() > 0) {
323 newSlide = pageNodes.item(currentIndex+1);
324 addSlideMethod = cb.append;
327 newSlide = pageNodes.item(currentIndex-1);
328 addSlideMethod = cb.prepend;
331 // Append/Prepend the new item to the DOM
332 if (cbChildren.indexOf(newSlide) === -1) {
333 addSlideMethod.call(cb, newSlide);
336 // Since we modified the DOM, get an updated reference
337 cbChildren = cb.get('children');
340 // Are we over the max number of items allowed?
341 if (cbChildren.size() > MAX_PAGE_COUNT) {
342 nodeToRemove = (isForward) ? cb.one('li:first-of-type') : cb.one('li:last-of-type');
343 nodeToRemove.remove();
344 targetOffset = (currentIndex == total ? 2 : 1);
345 host.set('scrollX', this._pageOffsets[targetOffset]); // Center
350 * index attr change handler
352 * @method _afterIndexChange
353 * @param {Event.Facade}
356 _afterIndexChange: function(e) {
358 this._uiIndex(e.newVal);
363 * Update the UI based on the current page index
366 * @param index {Number}
369 _uiIndex: function(index) {
370 this.scrollTo(index, 350, 'ease-out');
374 * Scroll to the next page in the scrollview, with animation
379 var index = this.get(INDEX);
380 if(index < this.get(TOTAL)-1) {
381 this.set(INDEX, index+1);
386 * Scroll to the previous page in the scrollview, with animation
391 var index = this.get(INDEX);
393 this.set(INDEX, index-1);
398 * Scroll to a given page in the scrollview, with animation.
401 * @param index {Number} The index of the page to scroll to
402 * @param duration {Number} The number of ms the animation should last
403 * @param easing {String} The timing function to use in the animation
405 scrollTo: function(index, duration, easing) {
406 var host = this._host,
407 vert = host._scrollsVertical,
408 scrollAxis = (vert) ? SCROLL_Y : SCROLL_X,
409 scrollVal = this._getTargetOffset(index);
411 host.set(scrollAxis, scrollVal, {
418 * Snaps the scrollview to the currently selected page
420 * @method snapToCurrent
421 * @param duration {Number} The number of ms the animation should last
422 * @param easing {String} The timing function to use in the animation
424 snapToCurrent: function(duration, easing) {
425 var host = this._host,
426 vert = host._scrollsVertical;
430 host.set((vert) ? SCROLL_Y : SCROLL_X, this._getTargetOffset(this.get(INDEX)), {
436 _prevent: new Y.Do.Prevent()
441 * The default snap to current duration and easing values used on scroll end.
443 * @property SNAP_TO_CURRENT
446 PaginatorPlugin.SNAP_TO_CURRENT = {
451 Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
454 }, '3.5.1' ,{requires:['plugin']});