NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / scrollview-paginator / scrollview-paginator-debug.js
blob3278817b0abb0a6a47ee02cfdbca6ad9440e3462
1 /*
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/
6 */
8 YUI.add('scrollview-paginator', function (Y, NAME) {
10 /**
11  * Provides a plugin that adds pagination support to ScrollView instances
12  *
13  * @module scrollview-paginator
14  */
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',
20     INDEX = 'index',
21     SCROLL_X = 'scrollX',
22     SCROLL_Y = 'scrollY',
23     TOTAL = 'total',
24     DISABLED = 'disabled',
25     HOST = 'host',
26     SELECTOR = 'selector',
27     AXIS = 'axis',
28     DIM_X = 'x',
29     DIM_Y = 'y';
31 /**
32  * Scrollview plugin that adds support for paging
33  *
34  * @class ScrollViewPaginator
35  * @namespace Plugin
36  * @extends Plugin.Base
37  * @constructor
38  */
39 function PaginatorPlugin() {
40     PaginatorPlugin.superclass.constructor.apply(this, arguments);
43 Y.extend(PaginatorPlugin, Y.Plugin.Base, {
45     /**
46      * Designated initializer
47      *
48      * @method initializer
49      * @param {config} Configuration object for the plugin
50      */
51     initializer: function (config) {
52         var paginator = this,
53             host = paginator.get(HOST);
55         // Initialize & default
56         paginator._pageDims = [];
57         paginator._pageBuffer = 1;
58         paginator._optimizeMemory = false;
60         // Cache some values
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);
67         // Apply configs
68         if (config._optimizeMemory) {
69             paginator._optimizeMemory = config._optimizeMemory;
70         }
72         if (config._pageBuffer) {
73             paginator._pageBuffer = config._pageBuffer;
74         }
76         // Attach event bindings
77         paginator._bindAttrs();
78     },
80     /**
81      *
82      *
83      * @method _bindAttrs
84      * @private
85      */
86     _bindAttrs: function () {
87         var paginator = this;
89         // Event listeners
90         paginator.after({
91             'indexChange': paginator._afterIndexChange,
92             'axisChange': paginator._afterAxisChange
93         });
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);
106     },
108     /**
109      * After host render
110      *
111      * @method _afterHostRender
112      * @param e {Event.Facade} The event facade
113      * @protected
114      */
115     _afterHostRender: function () {
116         var paginator = this,
117             bb = paginator._bb,
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;
127         }
128         else if (paginatorAxis[DIM_X]) {
129             host._maxScrollY = pageDim.maxScrollY;
130         }
132         // Set the page count
133         paginator.set(TOTAL, size);
135         // Jump to the index
136         if (index !== 0) {
137             paginator.scrollToIndex(index, 0);
138         }
140         // Add the paginator class
141         bb.addClass(CLASS_PAGED);
143         // Trigger the optimization process
144         paginator._optimize();
145     },
147     /**
148      * After host syncUI
149      *
150      * @method _afterHostSyncUI
151      * @param e {Event.Facade} The event facade
152      * @protected
153      */
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));
166         }
167     },
169     /**
170      * After host _uiDimensionsChange
171      *
172      * @method _afterHostUIDimensionsChange
173      * @param e {Event.Facade} The event facade
174      * @protected
175      */
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)
198                     scrollX: 0,
199                     scrollY: 0,
201                     // Maximum scrollable values
202                     maxScrollX: maxScrollX,
203                     maxScrollY: maxScrollY,
205                     // Height & width of the page
206                     width: scrollWidth,
207                     height: scrollHeight
208                 };
210             } else {
211                 paginator._pageDims[i].maxScrollX = maxScrollX;
212                 paginator._pageDims[i].maxScrollY = maxScrollY;
213             }
215         });
216     },
218     /**
219      * Executed before host.scrollTo
220      *
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
227      * @protected
228      */
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(),
236             gestureAxis;
238         if (gesture) {
239             gestureAxis = gesture.axis;
241             // Null the opposite axis so it won't be modified by host.scrollTo
242             if (gestureAxis === DIM_Y) {
243                 x = null;
244             } else {
245                 y = null;
246             }
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);
251             }
253         }
255         // Return the modified argument list
256         return new Y.Do.AlterArgs("new args", [x, y, duration, easing, node]);
257     },
259     /**
260      * Executed after host._gestureMoveEnd
261      * Determines if the gesture should page prev or next (if at all)
262      *
263      * @method _afterHostGestureMoveEnd
264      * @param e {Event.Facade} The event facade
265      * @protected
266      */
267     _afterHostGestureMoveEnd: function () {
269         // This was a flick, so we don't need to do anything here
270         if (this._host._gesture.flick) {
271             return;
272         }
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],
287             rtl = host.rtl;
289         if (canScroll) {
290             if (isHalfway) { // TODO: This condition should probably be configurable
291                 // Fire next()/prev()
292                 paginator[(rtl === isForward ? 'prev' : 'next')]();
293             }
294             // Scrollback
295             else {
296                 paginator.scrollToIndex(paginator.get(INDEX));
297             }
298         }
299     },
301     /**
302      * Executed before host._mousewheel
303      * Prevents mousewheel events in some conditions
304      *
305      * @method _beforeHostMousewheel
306      * @param e {Event.Facade} The event facade
307      * @protected
308      */
309     _beforeHostMousewheel: function (e) {
310         var paginator = this,
311             host = paginator._host,
312             bb = host._bb,
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
323             e.preventDefault();
325             // Block host._mousewheel from running
326             return new Y.Do.Prevent();
327         }
328     },
330     /**
331      * Executed before host._flick
332      * Prevents flick events in some conditions
333      *
334      * @method _beforeHostFlick
335      * @param e {Event.Facade} The event facade
336      * @protected
337      */
338     _beforeHostFlick: function (e) {
340         // If the widget is disabled
341         if (this._host.get(DISABLED)) {
342             return false;
343         }
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();
348         }
350         var paginator = this,
351             host = paginator._host,
352             gesture = host._gesture,
353             paginatorAxis = paginator.get(AXIS),
354             flick = e.flick,
355             velocity = flick.velocity,
356             flickAxis = flick.axis || false,
357             isForward = (velocity < 0),
358             canScroll = paginatorAxis[flickAxis],
359             rtl = host.rtl;
361         // Store the flick data in the this._host._gesture object so it knows this was a flick
362         if (gesture) {
363             gesture.flick = flick;
364         }
366         // Can we scroll along this axis?
367         if (canScroll) {
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();
375             }
376         }
377     },
379     /**
380      * Executes after host's 'scrollEnd' event
381      * Runs cleanup operations
382      *
383      * @method _afterHostScrollEnded
384      * @param e {Event.Facade} The event facade
385      * @protected
386      */
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;
397         } else {
398             paginator._pageDims[index].scrollY = scrollY;
399         }
401         paginator._optimize();
402     },
404     /**
405      * index attr change handler
406      *
407      * @method _afterIndexChange
408      * @param e {Event.Facade} The event facade
409      * @protected
410      */
411     _afterIndexChange: function (e) {
412         var paginator = this,
413             host = paginator._host,
414             index = e.newVal,
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 });
428             }
429             else if (paginatorAxis[DIM_X]) {
430                 host._maxScrollY = pageDims.maxScrollY;
431                 host.set(SCROLL_Y, pageDims.scrollY, { src: UI });
432             }
433         }
435         if (e.src !== UI) {
436             paginator.scrollToIndex(index);
437         }
438     },
440     /**
441      * Optimization: Hides the pages not near the viewport
442      *
443      * @method _optimize
444      * @protected
445      */
446     _optimize: function () {
448         if (!this._optimizeMemory) {
449             return false;
450         }
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);
459     },
461     /**
462      * Optimization: Determines which nodes should be visible, and which should be hidden.
463      *
464      * @method _getStage
465      * @param index {Number} The page index # intended to be in focus.
466      * @return {object}
467      * @protected
468      */
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
477         return {
478             visible: pageNodes.splice(start, end - start),
479             hidden: pageNodes
480         };
481     },
483     /**
484      * A utility method to show node(s)
485      *
486      * @method _showNodes
487      * @param nodeList {Object} The list of nodes to show
488      * @protected
489      */
490     _showNodes: function (nodeList) {
491         if (nodeList) {
492             nodeList.removeClass(CLASS_HIDDEN).setStyle('visibility', '');
493         }
494     },
496     /**
497      * A utility method to hide node(s)
498      *
499      * @method _hideNodes
500      * @param nodeList {Object} The list of nodes to hide
501      * @protected
502      */
503     _hideNodes: function (nodeList) {
504         if (nodeList) {
505             nodeList.addClass(CLASS_HIDDEN).setStyle('visibility', 'hidden');
506         }
507     },
509     /**
510      * Gets a nodeList for the "pages"
511      *
512      * @method _getPageNodes
513      * @protected
514      * @return {nodeList}
515      */
516     _getPageNodes: function () {
517         var paginator = this,
518             host = paginator._host,
519             cb = host._cb,
520             pageSelector = paginator.get(SELECTOR),
521             pageNodes = (pageSelector ? cb.all(pageSelector) : cb.get('children'));
523         return pageNodes;
524     },
526     /**
527      * Scroll to the next page, with animation
528      *
529      * @method next
530      */
531     next: function () {
532         var paginator = this,
533             scrollview = paginator._host,
534             index = paginator._cIndex,
535             target = index + 1,
536             total = paginator.get(TOTAL);
538         // If the widget is disabled, ignore
539         if (scrollview.get(DISABLED)) {
540             return;
541         }
543         // If the target index is greater than the page count, ignore
544         if (target >= total) {
545             return;
546         }
548         // Update the index
549         paginator.set(INDEX, target);
550     },
552     /**
553      * Scroll to the previous page, with animation
554      *
555      * @method prev
556      */
557     prev: function () {
558         var paginator = this,
559             scrollview = paginator._host,
560             index = paginator._cIndex,
561             target = index - 1;
563         // If the widget is disabled, ignore
564         if (scrollview.get(DISABLED)) {
565             return;
566         }
568         // If the target index is before the first page, ignore
569         if (target < 0) {
570             return;
571         }
573         // Update the index
574         paginator.set(INDEX, target);
575     },
577     /**
578      * Deprecated for 3.7.0.
579      * @method scrollTo
580      * @deprecated
581      */
582     scrollTo: function () {
583         return this.scrollToIndex.apply(this, arguments);
584     },
586     /**
587      * Scroll to a given page in the scrollview
588      *
589      * @method scrollToIndex
590      * @since 3.7.0
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
594      */
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, {
613             duration: duration,
614             easing: easing
615         });
616     },
618     /**
619      * Setter for 'axis' attribute
620      *
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
625      *
626      * @protected
627      */
628     _axisSetter: function (val) {
630         // Turn a string into an axis object
631         if (Y.Lang.isString(val)) {
632             return {
633                 x: (val.match(/x/i) ? true : false),
634                 y: (val.match(/y/i) ? true : false)
635             };
636         }
637     },
640     /**
641      * After listener for the axis attribute
642      *
643      * @method _afterAxisChange
644      * @param e {Event.Facade} The event facade
645      * @protected
646      */
647     _afterAxisChange: function (e) {
648         this._cAxis = e.newVal;
649     }
651     // End prototype properties
653 }, {
655     // Static properties
657     /**
658      * The identity of the plugin
659      *
660      * @property NAME
661      * @type String
662      * @default 'pluginScrollViewPaginator'
663      * @readOnly
664      * @protected
665      * @static
666      */
667     NAME: 'pluginScrollViewPaginator',
669     /**
670      * The namespace on which the plugin will reside
671      *
672      * @property NS
673      * @type String
674      * @default 'pages'
675      * @static
676      */
677     NS: 'pages',
679     /**
680      * The default attribute configuration for the plugin
681      *
682      * @property ATTRS
683      * @type {Object}
684      * @static
685      */
686     ATTRS: {
688         /**
689          * Specifies ability to scroll on x, y, or x and y axis/axes.
690          *  If unspecified, it inherits from the host instance.
691          *
692          * @attribute axis
693          * @type String
694          */
695         axis: {
696             setter: '_axisSetter',
697             writeOnce: 'initOnly'
698         },
700         /**
701          * CSS selector for a page inside the scrollview. The scrollview
702          * will snap to the closest page.
703          *
704          * @attribute selector
705          * @type {String}
706          * @default null
707          */
708         selector: {
709             value: null
710         },
712         /**
713          * The active page number for a paged scrollview
714          *
715          * @attribute index
716          * @type {Number}
717          * @default 0
718          */
719         index: {
720             value: 0
721         },
723         /**
724          * The total number of pages
725          *
726          * @attribute total
727          * @type {Number}
728          * @default 0
729          */
730         total: {
731             value: 0
732         }
733     },
735     /**
736      * The default snap to current duration and easing values used on scroll end.
737      *
738      * @property SNAP_TO_CURRENT
739      * @static
740      */
741     TRANSITION: {
742         duration: 300,
743         easing: 'ease-out'
744     }
746     // End static properties
750 Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
753 }, '3.13.0', {"requires": ["plugin", "classnamemanager"]});