Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('scrollview-paginator', function (Y, NAME) {
2
 
3
/**
4
 * Provides a plugin that adds pagination support to ScrollView instances
5
 *
6
 * @module scrollview-paginator
7
 */
8
var getClassName = Y.ClassNameManager.getClassName,
9
    SCROLLVIEW = 'scrollview',
10
    CLASS_HIDDEN = getClassName(SCROLLVIEW, 'hidden'),
11
    CLASS_PAGED = getClassName(SCROLLVIEW, 'paged'),
12
    UI = (Y.ScrollView) ? Y.ScrollView.UI_SRC : 'ui',
13
    INDEX = 'index',
14
    SCROLL_X = 'scrollX',
15
    SCROLL_Y = 'scrollY',
16
    TOTAL = 'total',
17
    DISABLED = 'disabled',
18
    HOST = 'host',
19
    SELECTOR = 'selector',
20
    AXIS = 'axis',
21
    DIM_X = 'x',
22
    DIM_Y = 'y';
23
 
24
/**
25
 * Scrollview plugin that adds support for paging
26
 *
27
 * @class ScrollViewPaginator
28
 * @namespace Plugin
29
 * @extends Plugin.Base
30
 * @constructor
31
 */
32
function PaginatorPlugin() {
33
    PaginatorPlugin.superclass.constructor.apply(this, arguments);
34
}
35
 
36
Y.extend(PaginatorPlugin, Y.Plugin.Base, {
37
 
38
    /**
39
     * Designated initializer
40
     *
41
     * @method initializer
42
     * @param {Object} Configuration object for the plugin
43
     */
44
    initializer: function (config) {
45
        var paginator = this,
46
            host = paginator.get(HOST);
47
 
48
        // Initialize & default
49
        paginator._pageDims = [];
50
        paginator._pageBuffer = 1;
51
        paginator._optimizeMemory = false;
52
 
53
        // Cache some values
54
        paginator._host = host;
55
        paginator._bb = host._bb;
56
        paginator._cb = host._cb;
57
        paginator._cIndex = paginator.get(INDEX);
58
        paginator._cAxis = paginator.get(AXIS);
59
 
60
        // Apply configs
61
        if (config._optimizeMemory) {
62
            paginator._optimizeMemory = config._optimizeMemory;
63
        }
64
 
65
        if (config._pageBuffer) {
66
            paginator._pageBuffer = config._pageBuffer;
67
        }
68
 
69
        // Attach event bindings
70
        paginator._bindAttrs();
71
    },
72
 
73
    /**
74
     *
75
     *
76
     * @method _bindAttrs
77
     * @private
78
     */
79
    _bindAttrs: function () {
80
        var paginator = this;
81
 
82
        // Event listeners
83
        paginator.after({
84
            'indexChange': paginator._afterIndexChange,
85
            'axisChange': paginator._afterAxisChange
86
        });
87
 
88
        // Host method listeners
89
        paginator.beforeHostMethod('scrollTo', paginator._beforeHostScrollTo);
90
        paginator.beforeHostMethod('_mousewheel', paginator._beforeHostMousewheel);
91
        paginator.beforeHostMethod('_flick', paginator._beforeHostFlick);
92
        paginator.afterHostMethod('_onGestureMoveEnd', paginator._afterHostGestureMoveEnd);
93
        paginator.afterHostMethod('_uiDimensionsChange', paginator._afterHostUIDimensionsChange);
94
        paginator.afterHostMethod('syncUI', paginator._afterHostSyncUI);
95
 
96
        // Host event listeners
97
        paginator.afterHostEvent('render', paginator._afterHostRender);
98
        paginator.afterHostEvent('scrollEnd', paginator._afterHostScrollEnded);
99
    },
100
 
101
    /**
102
     * After host render
103
     *
104
     * @method _afterHostRender
105
     * @param e {EventFacade} The event facade
106
     * @protected
107
     */
108
    _afterHostRender: function () {
109
        var paginator = this,
110
            bb = paginator._bb,
111
            host = paginator._host,
112
            index = paginator._cIndex,
113
            paginatorAxis = paginator._cAxis,
114
            pageNodes = paginator._getPageNodes(),
115
            size = pageNodes.size(),
116
            pageDim = paginator._pageDims[index];
117
 
118
        if (paginatorAxis[DIM_Y]) {
119
            host._maxScrollX = pageDim.maxScrollX;
120
        }
121
        else if (paginatorAxis[DIM_X]) {
122
            host._maxScrollY = pageDim.maxScrollY;
123
        }
124
 
125
        // Set the page count
126
        paginator.set(TOTAL, size);
127
 
128
        // Jump to the index
129
        if (index !== 0) {
130
            paginator.scrollToIndex(index, 0);
131
        }
132
 
133
        // Add the paginator class
134
        bb.addClass(CLASS_PAGED);
135
 
136
        // Trigger the optimization process
137
        paginator._optimize();
138
    },
139
 
140
    /**
141
     * After host syncUI
142
     *
143
     * @method _afterHostSyncUI
144
     * @param e {EventFacade} The event facade
145
     * @protected
146
     */
147
    _afterHostSyncUI: function () {
148
        var paginator = this,
149
            host = paginator._host,
150
            pageNodes = paginator._getPageNodes(),
151
            size = pageNodes.size();
152
 
153
        // Set the page count
154
        paginator.set(TOTAL, size);
155
 
156
        // If paginator's 'axis' property is to be automatically determined, inherit host's property
157
        if (paginator._cAxis === undefined) {
158
            paginator._set(AXIS, host.get(AXIS));
159
        }
160
    },
161
 
162
    /**
163
     * After host _uiDimensionsChange
164
     *
165
     * @method _afterHostUIDimensionsChange
166
     * @param e {EventFacade} The event facade
167
     * @protected
168
     */
169
    _afterHostUIDimensionsChange: function () {
170
 
171
        var paginator = this,
172
            host = paginator._host,
173
            dims = host._getScrollDims(),
174
            widgetWidth = dims.offsetWidth,
175
            widgetHeight = dims.offsetHeight,
176
            pageNodes = paginator._getPageNodes();
177
 
178
        // Inefficient. Should not reinitialize every page every syncUI
179
        pageNodes.each(function (node, i) {
180
            var scrollWidth = node.get('scrollWidth'),
181
                scrollHeight = node.get('scrollHeight'),
182
                maxScrollX = Math.max(0, scrollWidth - widgetWidth), // Math.max to ensure we don't set it to a negative value
183
                maxScrollY = Math.max(0, scrollHeight - widgetHeight);
184
 
185
            // Don't initialize any page _pageDims that already have been.
186
            if (!paginator._pageDims[i]) {
187
 
188
                paginator._pageDims[i] = {
189
 
190
                    // Current scrollX & scrollY positions (default to 0)
191
                    scrollX: 0,
192
                    scrollY: 0,
193
 
194
                    // Maximum scrollable values
195
                    maxScrollX: maxScrollX,
196
                    maxScrollY: maxScrollY,
197
 
198
                    // Height & width of the page
199
                    width: scrollWidth,
200
                    height: scrollHeight
201
                };
202
 
203
            } else {
204
                paginator._pageDims[i].maxScrollX = maxScrollX;
205
                paginator._pageDims[i].maxScrollY = maxScrollY;
206
            }
207
 
208
        });
209
    },
210
 
211
    /**
212
     * Executed before host.scrollTo
213
     *
214
     * @method _beforeHostScrollTo
215
     * @param x {Number} The x-position to scroll to. (null for no movement)
216
     * @param y {Number} The y-position to scroll to. (null for no movement)
217
     * @param {Number} [duration] Duration, in ms, of the scroll animation (default is 0)
218
     * @param {String} [easing] An easing equation if duration is set
219
     * @param {String} [node] The node to move
220
     * @protected
221
     */
222
    _beforeHostScrollTo: function (x, y, duration, easing, node) {
223
        var paginator = this,
224
            host = paginator._host,
225
            gesture = host._gesture,
226
            index = paginator._cIndex,
227
            paginatorAxis = paginator._cAxis,
228
            pageNodes = paginator._getPageNodes(),
229
            gestureAxis;
230
 
231
        if (gesture) {
232
            gestureAxis = gesture.axis;
233
 
234
            // Null the opposite axis so it won't be modified by host.scrollTo
235
            if (gestureAxis === DIM_Y) {
236
                x = null;
237
            } else {
238
                y = null;
239
            }
240
 
241
            // If they are scrolling against the specified axis, pull out the page's node to have its own offset
242
            if (paginatorAxis[gestureAxis] === false) {
243
                node = pageNodes.item(index);
244
            }
245
 
246
        }
247
 
248
        // Return the modified argument list
249
        return new Y.Do.AlterArgs("new args", [x, y, duration, easing, node]);
250
    },
251
 
252
    /**
253
     * Executed after host._gestureMoveEnd
254
     * Determines if the gesture should page prev or next (if at all)
255
     *
256
     * @method _afterHostGestureMoveEnd
257
     * @param e {EventFacade} The event facade
258
     * @protected
259
     */
260
    _afterHostGestureMoveEnd: function () {
261
 
262
        // This was a flick, so we don't need to do anything here
263
        if (this._host._gesture.flick) {
264
            return;
265
        }
266
 
267
        var paginator = this,
268
            host = paginator._host,
269
            gesture = host._gesture,
270
            index = paginator._cIndex,
271
            paginatorAxis = paginator._cAxis,
272
            gestureAxis = gesture.axis,
273
            isHorizontal = (gestureAxis === DIM_X),
274
            delta = gesture[(isHorizontal ? 'deltaX' : 'deltaY')],
275
            isForward = (delta > 0),
276
            pageDims = paginator._pageDims[index],
277
            halfway = pageDims[(isHorizontal ? 'width' : 'height')] / 2,
278
            isHalfway = (Math.abs(delta) >= halfway),
279
            canScroll = paginatorAxis[gestureAxis],
280
            rtl = host.rtl;
281
 
282
        if (canScroll) {
283
            if (isHalfway) { // TODO: This condition should probably be configurable
284
                // Fire next()/prev()
285
                paginator[(rtl === isForward ? 'prev' : 'next')]();
286
            }
287
            // Scrollback
288
            else {
289
                paginator.scrollToIndex(paginator.get(INDEX));
290
            }
291
        }
292
    },
293
 
294
    /**
295
     * Executed before host._mousewheel
296
     * Prevents mousewheel events in some conditions
297
     *
298
     * @method _beforeHostMousewheel
299
     * @param e {EventFacade} The event facade
300
     * @protected
301
     */
302
    _beforeHostMousewheel: function (e) {
303
        var paginator = this,
304
            host = paginator._host,
305
            bb = host._bb,
306
            isForward = (e.wheelDelta < 0),
307
            paginatorAxis = paginator._cAxis;
308
 
309
        // Only if the mousewheel event occurred on a DOM node inside the BB
310
        if (bb.contains(e.target) && paginatorAxis[DIM_Y]) {
311
 
312
            // Fire next()/prev()
313
            paginator[(isForward ? 'next' : 'prev')]();
314
 
315
            // prevent browser default behavior on mousewheel
316
            e.preventDefault();
317
 
318
            // Block host._mousewheel from running
319
            return new Y.Do.Prevent();
320
        }
321
    },
322
 
323
    /**
324
     * Executed before host._flick
325
     * Prevents flick events in some conditions
326
     *
327
     * @method _beforeHostFlick
328
     * @param e {EventFacade} The event facade
329
     * @protected
330
     */
331
    _beforeHostFlick: function (e) {
332
 
333
        // If the widget is disabled
334
        if (this._host.get(DISABLED)) {
335
            return false;
336
        }
337
 
338
        // The drag was out of bounds, so do nothing (which will cause a snapback)
339
        if (this._host._isOutOfBounds()){
340
            return new Y.Do.Prevent();
341
        }
342
 
343
        var paginator = this,
344
            host = paginator._host,
345
            gesture = host._gesture,
346
            paginatorAxis = paginator.get(AXIS),
347
            flick = e.flick,
348
            velocity = flick.velocity,
349
            flickAxis = flick.axis || false,
350
            isForward = (velocity < 0),
351
            canScroll = paginatorAxis[flickAxis],
352
            rtl = host.rtl;
353
 
354
        // Store the flick data in the this._host._gesture object so it knows this was a flick
355
        if (gesture) {
356
            gesture.flick = flick;
357
        }
358
 
359
        // Can we scroll along this axis?
360
        if (canScroll) {
361
 
362
            // Fire next()/prev()
363
            paginator[(rtl === isForward ? 'prev' : 'next')]();
364
 
365
            // Prevent flicks on the paginated axis
366
            if (paginatorAxis[flickAxis]) {
367
                return new Y.Do.Prevent();
368
            }
369
        }
370
    },
371
 
372
    /**
373
     * Executes after host's 'scrollEnd' event
374
     * Runs cleanup operations
375
     *
376
     * @method _afterHostScrollEnded
377
     * @param e {EventFacade} The event facade
378
     * @protected
379
     */
380
    _afterHostScrollEnded: function () {
381
        var paginator = this,
382
            host = paginator._host,
383
            index = paginator._cIndex,
384
            scrollX = host.get(SCROLL_X),
385
            scrollY = host.get(SCROLL_Y),
386
            paginatorAxis = paginator._cAxis;
387
 
388
        if (paginatorAxis[DIM_Y]) {
389
            paginator._pageDims[index].scrollX = scrollX;
390
        } else {
391
            paginator._pageDims[index].scrollY = scrollY;
392
        }
393
 
394
        paginator._optimize();
395
    },
396
 
397
    /**
398
     * index attr change handler
399
     *
400
     * @method _afterIndexChange
401
     * @param e {EventFacade} The event facade
402
     * @protected
403
     */
404
    _afterIndexChange: function (e) {
405
        var paginator = this,
406
            host = paginator._host,
407
            index = e.newVal,
408
            pageDims = paginator._pageDims[index],
409
            hostAxis = host._cAxis,
410
            paginatorAxis = paginator._cAxis;
411
 
412
        // Cache the new index value
413
        paginator._cIndex = index;
414
 
415
        // For dual-axis instances, we need to hack some host properties to the
416
        // current page's max height/width and current stored offset
417
        if (hostAxis[DIM_X] && hostAxis[DIM_Y]) {
418
            if (paginatorAxis[DIM_Y]) {
419
                host._maxScrollX = pageDims.maxScrollX;
420
                host.set(SCROLL_X, pageDims.scrollX, { src: UI });
421
            }
422
            else if (paginatorAxis[DIM_X]) {
423
                host._maxScrollY = pageDims.maxScrollY;
424
                host.set(SCROLL_Y, pageDims.scrollY, { src: UI });
425
            }
426
        }
427
 
428
        if (e.src !== UI) {
429
            paginator.scrollToIndex(index);
430
        }
431
    },
432
 
433
    /**
434
     * Optimization: Hides the pages not near the viewport
435
     *
436
     * @method _optimize
437
     * @protected
438
     */
439
    _optimize: function () {
440
 
441
        if (!this._optimizeMemory) {
442
            return false;
443
        }
444
 
445
        var paginator = this,
446
            currentIndex = paginator._cIndex,
447
            pageNodes = paginator._getStage(currentIndex);
448
 
449
        // Show the pages in/near the viewport & hide the rest
450
        paginator._showNodes(pageNodes.visible);
451
        paginator._hideNodes(pageNodes.hidden);
452
    },
453
 
454
    /**
455
     * Optimization: Determines which nodes should be visible, and which should be hidden.
456
     *
457
     * @method _getStage
458
     * @param index {Number} The page index # intended to be in focus.
459
     * @return {object}
460
     * @protected
461
     */
462
    _getStage: function (index) {
463
        var paginator = this,
464
            pageBuffer = paginator._pageBuffer,
465
            pageCount = paginator.get(TOTAL),
466
            pageNodes = paginator._getPageNodes(),
467
            start = Math.max(0, index - pageBuffer),
468
            end = Math.min(pageCount, index + 1 + pageBuffer); // noninclusive
469
 
470
        return {
471
            visible: pageNodes.splice(start, end - start),
472
            hidden: pageNodes
473
        };
474
    },
475
 
476
    /**
477
     * A utility method to show node(s)
478
     *
479
     * @method _showNodes
480
     * @param nodeList {Object} The list of nodes to show
481
     * @protected
482
     */
483
    _showNodes: function (nodeList) {
484
        if (nodeList) {
485
            nodeList.removeClass(CLASS_HIDDEN).setStyle('visibility', '');
486
        }
487
    },
488
 
489
    /**
490
     * A utility method to hide node(s)
491
     *
492
     * @method _hideNodes
493
     * @param nodeList {Object} The list of nodes to hide
494
     * @protected
495
     */
496
    _hideNodes: function (nodeList) {
497
        if (nodeList) {
498
            nodeList.addClass(CLASS_HIDDEN).setStyle('visibility', 'hidden');
499
        }
500
    },
501
 
502
    /**
503
     * Gets a nodeList for the "pages"
504
     *
505
     * @method _getPageNodes
506
     * @protected
507
     * @return {nodeList}
508
     */
509
    _getPageNodes: function () {
510
        var paginator = this,
511
            host = paginator._host,
512
            cb = host._cb,
513
            pageSelector = paginator.get(SELECTOR),
514
            pageNodes = (pageSelector ? cb.all(pageSelector) : cb.get('children'));
515
 
516
        return pageNodes;
517
    },
518
 
519
    /**
520
     * Scroll to the next page, with animation
521
     *
522
     * @method next
523
     */
524
    next: function () {
525
        var paginator = this,
526
            scrollview = paginator._host,
527
            index = paginator._cIndex,
528
            target = index + 1,
529
            total = paginator.get(TOTAL);
530
 
531
        // If the widget is disabled, ignore
532
        if (scrollview.get(DISABLED)) {
533
            return;
534
        }
535
 
536
        // If the target index is greater than the page count, ignore
537
        if (target >= total) {
538
            return;
539
        }
540
 
541
        // Update the index
542
        paginator.set(INDEX, target);
543
    },
544
 
545
    /**
546
     * Scroll to the previous page, with animation
547
     *
548
     * @method prev
549
     */
550
    prev: function () {
551
        var paginator = this,
552
            scrollview = paginator._host,
553
            index = paginator._cIndex,
554
            target = index - 1;
555
 
556
        // If the widget is disabled, ignore
557
        if (scrollview.get(DISABLED)) {
558
            return;
559
        }
560
 
561
        // If the target index is before the first page, ignore
562
        if (target < 0) {
563
            return;
564
        }
565
 
566
        // Update the index
567
        paginator.set(INDEX, target);
568
    },
569
 
570
    /**
571
     * Deprecated for 3.7.0.
572
     * @method scrollTo
573
     * @deprecated
574
     */
575
    scrollTo: function () {
576
        return this.scrollToIndex.apply(this, arguments);
577
    },
578
 
579
    /**
580
     * Scroll to a given page in the scrollview
581
     *
582
     * @method scrollToIndex
583
     * @since 3.7.0
584
     * @param index {Number} The index of the page to scroll to
585
     * @param {Number} [duration] The number of ms the animation should last
586
     * @param {String} [easing] The timing function to use in the animation
587
     */
588
    scrollToIndex: function (index, duration, easing) {
589
        var paginator = this,
590
            host = paginator._host,
591
            pageNode = paginator._getPageNodes().item(index),
592
            scrollAxis = (paginator._cAxis[DIM_X] ? SCROLL_X : SCROLL_Y),
593
            scrollOffset = pageNode.get(scrollAxis === SCROLL_X ? 'offsetLeft' : 'offsetTop');
594
 
595
        duration = (duration !== undefined) ? duration : PaginatorPlugin.TRANSITION.duration;
596
        easing = (easing !== undefined) ? easing : PaginatorPlugin.TRANSITION.easing;
597
 
598
        // Set the index ATTR to the specified index value
599
        paginator.set(INDEX, index, { src: UI });
600
 
601
        // Makes sure the viewport nodes are visible
602
        paginator._showNodes(pageNode);
603
 
604
        // Scroll to the offset
605
        host.set(scrollAxis, scrollOffset, {
606
            duration: duration,
607
            easing: easing
608
        });
609
    },
610
 
611
    /**
612
     * Setter for 'axis' attribute
613
     *
614
     * @method _axisSetter
615
     * @param val {Mixed} A string ('x', 'y', 'xy') to specify which axis/axes to allow scrolling on
616
     * @param name {String} The attribute name
617
     * @return {Object} An object to specify scrollability on the x & y axes
618
     *
619
     * @protected
620
     */
621
    _axisSetter: function (val) {
622
 
623
        // Turn a string into an axis object
624
        if (Y.Lang.isString(val)) {
625
            return {
626
                x: (val.match(/x/i) ? true : false),
627
                y: (val.match(/y/i) ? true : false)
628
            };
629
        }
630
    },
631
 
632
 
633
    /**
634
     * After listener for the axis attribute
635
     *
636
     * @method _afterAxisChange
637
     * @param e {EventFacade} The event facade
638
     * @protected
639
     */
640
    _afterAxisChange: function (e) {
641
        this._cAxis = e.newVal;
642
    }
643
 
644
    // End prototype properties
645
 
646
}, {
647
 
648
    // Static properties
649
 
650
    /**
651
     * The identity of the plugin
652
     *
653
     * @property NAME
654
     * @type String
655
     * @default 'pluginScrollViewPaginator'
656
     * @readOnly
657
     * @protected
658
     * @static
659
     */
660
    NAME: 'pluginScrollViewPaginator',
661
 
662
    /**
663
     * The namespace on which the plugin will reside
664
     *
665
     * @property NS
666
     * @type String
667
     * @default 'pages'
668
     * @static
669
     */
670
    NS: 'pages',
671
 
672
    /**
673
     * The default attribute configuration for the plugin
674
     *
675
     * @property ATTRS
676
     * @type {Object}
677
     * @static
678
     */
679
    ATTRS: {
680
 
681
        /**
682
         * Specifies ability to scroll on x, y, or x and y axis/axes.
683
         *  If unspecified, it inherits from the host instance.
684
         *
685
         * @attribute axis
686
         * @type String
687
         */
688
        axis: {
689
            setter: '_axisSetter',
690
            writeOnce: 'initOnly'
691
        },
692
 
693
        /**
694
         * CSS selector for a page inside the scrollview. The scrollview
695
         * will snap to the closest page.
696
         *
697
         * @attribute selector
698
         * @type {String}
699
         * @default null
700
         */
701
        selector: {
702
            value: null
703
        },
704
 
705
        /**
706
         * The active page number for a paged scrollview
707
         *
708
         * @attribute index
709
         * @type {Number}
710
         * @default 0
711
         */
712
        index: {
713
            value: 0
714
        },
715
 
716
        /**
717
         * The total number of pages
718
         *
719
         * @attribute total
720
         * @type {Number}
721
         * @default 0
722
         */
723
        total: {
724
            value: 0
725
        }
726
    },
727
 
728
    /**
729
     * The default snap to current duration and easing values used on scroll end.
730
     *
731
     * @property SNAP_TO_CURRENT
732
     * @static
733
     */
734
    TRANSITION: {
735
        duration: 300,
736
        easing: 'ease-out'
737
    }
738
 
739
    // End static properties
740
 
741
});
742
 
743
Y.namespace('Plugin').ScrollViewPaginator = PaginatorPlugin;
744
 
745
 
746
}, '3.18.1', {"requires": ["plugin", "classnamemanager"]});