Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('scrollview-base', function (Y, NAME) {
2
 
3
/**
4
 * The scrollview-base module provides a basic ScrollView Widget, without scrollbar indicators
5
 *
6
 * @module scrollview
7
 * @submodule scrollview-base
8
 */
9
 
10
 // Local vars
11
var getClassName = Y.ClassNameManager.getClassName,
12
    DOCUMENT = Y.config.doc,
13
    IE = Y.UA.ie,
14
    NATIVE_TRANSITIONS = Y.Transition.useNative,
15
    vendorPrefix = Y.Transition._VENDOR_PREFIX, // Todo: This is a private property, and alternative approaches should be investigated
16
    SCROLLVIEW = 'scrollview',
17
    CLASS_NAMES = {
18
        vertical: getClassName(SCROLLVIEW, 'vert'),
19
        horizontal: getClassName(SCROLLVIEW, 'horiz')
20
    },
21
    EV_SCROLL_END = 'scrollEnd',
22
    FLICK = 'flick',
23
    DRAG = 'drag',
24
    MOUSEWHEEL = 'mousewheel',
25
    UI = 'ui',
26
    TOP = 'top',
27
    LEFT = 'left',
28
    PX = 'px',
29
    AXIS = 'axis',
30
    SCROLL_Y = 'scrollY',
31
    SCROLL_X = 'scrollX',
32
    BOUNCE = 'bounce',
33
    DISABLED = 'disabled',
34
    DECELERATION = 'deceleration',
35
    DIM_X = 'x',
36
    DIM_Y = 'y',
37
    BOUNDING_BOX = 'boundingBox',
38
    CONTENT_BOX = 'contentBox',
39
    GESTURE_MOVE = 'gesturemove',
40
    START = 'start',
41
    END = 'end',
42
    EMPTY = '',
43
    ZERO = '0s',
44
    SNAP_DURATION = 'snapDuration',
45
    SNAP_EASING = 'snapEasing',
46
    EASING = 'easing',
47
    FRAME_DURATION = 'frameDuration',
48
    BOUNCE_RANGE = 'bounceRange',
49
    _constrain = function (val, min, max) {
50
        return Math.min(Math.max(val, min), max);
51
    };
52
 
53
/**
54
 * ScrollView provides a scrollable widget, supporting flick gestures,
55
 * across both touch and mouse based devices.
56
 *
57
 * @class ScrollView
58
 * @param config {Object} Object literal with initial attribute values
59
 * @extends Widget
60
 * @constructor
61
 */
62
function ScrollView() {
63
    ScrollView.superclass.constructor.apply(this, arguments);
64
}
65
 
66
Y.ScrollView = Y.extend(ScrollView, Y.Widget, {
67
 
68
    // *** Y.ScrollView prototype
69
 
70
    /**
71
     * Flag driving whether or not we should try and force H/W acceleration when transforming. Currently enabled by default for Webkit.
72
     * Used by the _transform method.
73
     *
74
     * @property _forceHWTransforms
75
     * @type boolean
76
     * @protected
77
     */
78
    _forceHWTransforms: Y.UA.webkit ? true : false,
79
 
80
    /**
81
     * <p>Used to control whether or not ScrollView's internal
82
     * gesturemovestart, gesturemove and gesturemoveend
83
     * event listeners should preventDefault. The value is an
84
     * object, with "start", "move" and "end" properties used to
85
     * specify which events should preventDefault and which shouldn't:</p>
86
     *
87
     * <pre>
88
     * {
89
     *    start: false,
90
     *    move: true,
91
     *    end: false
92
     * }
93
     * </pre>
94
     *
95
     * <p>The default values are set up in order to prevent panning,
96
     * on touch devices, while allowing click listeners on elements inside
97
     * the ScrollView to be notified as expected.</p>
98
     *
99
     * @property _prevent
100
     * @type Object
101
     * @protected
102
     */
103
    _prevent: {
104
        start: false,
105
        move: true,
106
        end: false
107
    },
108
 
109
    /**
110
     * Contains the distance (postive or negative) in pixels by which
111
     *  the scrollview was last scrolled. This is useful when setting up
112
     *  click listeners on the scrollview content, which on mouse based
113
     *  devices are always fired, even after a drag/flick.
114
     *
115
     * <p>Touch based devices don't currently fire a click event,
116
     *  if the finger has been moved (beyond a threshold) so this
117
     *  check isn't required, if working in a purely touch based environment</p>
118
     *
119
     * @property lastScrolledAmt
120
     * @type Number
121
     * @public
122
     * @default 0
123
     */
124
    lastScrolledAmt: 0,
125
 
126
    /**
127
     * Internal state, defines the minimum amount that the scrollview can be scrolled along the X axis
128
     *
129
     * @property _minScrollX
130
     * @type number
131
     * @protected
132
     */
133
    _minScrollX: null,
134
 
135
    /**
136
     * Internal state, defines the maximum amount that the scrollview can be scrolled along the X axis
137
     *
138
     * @property _maxScrollX
139
     * @type number
140
     * @protected
141
     */
142
    _maxScrollX: null,
143
 
144
    /**
145
     * Internal state, defines the minimum amount that the scrollview can be scrolled along the Y axis
146
     *
147
     * @property _minScrollY
148
     * @type number
149
     * @protected
150
     */
151
    _minScrollY: null,
152
 
153
    /**
154
     * Internal state, defines the maximum amount that the scrollview can be scrolled along the Y axis
155
     *
156
     * @property _maxScrollY
157
     * @type number
158
     * @protected
159
     */
160
    _maxScrollY: null,
161
 
162
    /**
163
     * Designated initializer
164
     *
165
     * @method initializer
166
     * @param {Object} Configuration object for the plugin
167
     */
168
    initializer: function () {
169
        var sv = this;
170
 
171
        // Cache these values, since they aren't going to change.
172
        sv._bb = sv.get(BOUNDING_BOX);
173
        sv._cb = sv.get(CONTENT_BOX);
174
 
175
        // Cache some attributes
176
        sv._cAxis = sv.get(AXIS);
177
        sv._cBounce = sv.get(BOUNCE);
178
        sv._cBounceRange = sv.get(BOUNCE_RANGE);
179
        sv._cDeceleration = sv.get(DECELERATION);
180
        sv._cFrameDuration = sv.get(FRAME_DURATION);
181
    },
182
 
183
    /**
184
     * bindUI implementation
185
     *
186
     * Hooks up events for the widget
187
     * @method bindUI
188
     */
189
    bindUI: function () {
190
        var sv = this;
191
 
192
        // Bind interaction listers
193
        sv._bindFlick(sv.get(FLICK));
194
        sv._bindDrag(sv.get(DRAG));
195
        sv._bindMousewheel(true);
196
 
197
        // Bind change events
198
        sv._bindAttrs();
199
 
200
        // IE SELECT HACK. See if we can do this non-natively and in the gesture for a future release.
201
        if (IE) {
202
            sv._fixIESelect(sv._bb, sv._cb);
203
        }
204
 
205
        // Set any deprecated static properties
206
        if (ScrollView.SNAP_DURATION) {
207
            sv.set(SNAP_DURATION, ScrollView.SNAP_DURATION);
208
        }
209
 
210
        if (ScrollView.SNAP_EASING) {
211
            sv.set(SNAP_EASING, ScrollView.SNAP_EASING);
212
        }
213
 
214
        if (ScrollView.EASING) {
215
            sv.set(EASING, ScrollView.EASING);
216
        }
217
 
218
        if (ScrollView.FRAME_STEP) {
219
            sv.set(FRAME_DURATION, ScrollView.FRAME_STEP);
220
        }
221
 
222
        if (ScrollView.BOUNCE_RANGE) {
223
            sv.set(BOUNCE_RANGE, ScrollView.BOUNCE_RANGE);
224
        }
225
 
226
        // Recalculate dimension properties
227
        // TODO: This should be throttled.
228
        // Y.one(WINDOW).after('resize', sv._afterDimChange, sv);
229
    },
230
 
231
    /**
232
     * Bind event listeners
233
     *
234
     * @method _bindAttrs
235
     * @private
236
     */
237
    _bindAttrs: function () {
238
        var sv = this,
239
            scrollChangeHandler = sv._afterScrollChange,
240
            dimChangeHandler = sv._afterDimChange;
241
 
242
        // Bind any change event listeners
243
        sv.after({
244
            'scrollEnd': sv._afterScrollEnd,
245
            'disabledChange': sv._afterDisabledChange,
246
            'flickChange': sv._afterFlickChange,
247
            'dragChange': sv._afterDragChange,
248
            'axisChange': sv._afterAxisChange,
249
            'scrollYChange': scrollChangeHandler,
250
            'scrollXChange': scrollChangeHandler,
251
            'heightChange': dimChangeHandler,
252
            'widthChange': dimChangeHandler
253
        });
254
    },
255
 
256
    /**
257
     * Bind (or unbind) gesture move listeners required for drag support
258
     *
259
     * @method _bindDrag
260
     * @param drag {boolean} If true, the method binds listener to enable
261
     *  drag (gesturemovestart). If false, the method unbinds gesturemove
262
     *  listeners for drag support.
263
     * @private
264
     */
265
    _bindDrag: function (drag) {
266
        var sv = this,
267
            bb = sv._bb;
268
 
269
        // Unbind any previous 'drag' listeners
270
        bb.detach(DRAG + '|*');
271
 
272
        if (drag) {
273
            bb.on(DRAG + '|' + GESTURE_MOVE + START, Y.bind(sv._onGestureMoveStart, sv));
274
        }
275
    },
276
 
277
    /**
278
     * Bind (or unbind) flick listeners.
279
     *
280
     * @method _bindFlick
281
     * @param flick {Object|boolean} If truthy, the method binds listeners for
282
     *  flick support. If false, the method unbinds flick listeners.
283
     * @private
284
     */
285
    _bindFlick: function (flick) {
286
        var sv = this,
287
            bb = sv._bb;
288
 
289
        // Unbind any previous 'flick' listeners
290
        bb.detach(FLICK + '|*');
291
 
292
        if (flick) {
293
            bb.on(FLICK + '|' + FLICK, Y.bind(sv._flick, sv), flick);
294
 
295
            // Rebind Drag, becuase _onGestureMoveEnd always has to fire -after- _flick
296
            sv._bindDrag(sv.get(DRAG));
297
        }
298
    },
299
 
300
    /**
301
     * Bind (or unbind) mousewheel listeners.
302
     *
303
     * @method _bindMousewheel
304
     * @param mousewheel {Object|boolean} If truthy, the method binds listeners for
305
     *  mousewheel support. If false, the method unbinds mousewheel listeners.
306
     * @private
307
     */
308
    _bindMousewheel: function (mousewheel) {
309
        var sv = this,
310
            bb = sv._bb;
311
 
312
        // Unbind any previous 'mousewheel' listeners
313
        // TODO: This doesn't actually appear to work properly. Fix. #2532743
314
        bb.detach(MOUSEWHEEL + '|*');
315
 
316
        // Only enable for vertical scrollviews
317
        if (mousewheel) {
318
            // Bound to document, because that's where mousewheel events fire off of.
319
            Y.one(DOCUMENT).on(MOUSEWHEEL, Y.bind(sv._mousewheel, sv));
320
        }
321
    },
322
 
323
    /**
324
     * syncUI implementation.
325
     *
326
     * Update the scroll position, based on the current value of scrollX/scrollY.
327
     *
328
     * @method syncUI
329
     */
330
    syncUI: function () {
331
        var sv = this,
332
            scrollDims = sv._getScrollDims(),
333
            width = scrollDims.offsetWidth,
334
            height = scrollDims.offsetHeight,
335
            scrollWidth = scrollDims.scrollWidth,
336
            scrollHeight = scrollDims.scrollHeight;
337
 
338
        // If the axis is undefined, auto-calculate it
339
        if (sv._cAxis === undefined) {
340
            // This should only ever be run once (for now).
341
            // In the future SV might post-load axis changes
342
            sv._cAxis = {
343
                x: (scrollWidth > width),
344
                y: (scrollHeight > height)
345
            };
346
 
347
            sv._set(AXIS, sv._cAxis);
348
        }
349
 
350
        // get text direction on or inherited by scrollview node
351
        sv.rtl = (sv._cb.getComputedStyle('direction') === 'rtl');
352
 
353
        // Cache the disabled value
354
        sv._cDisabled = sv.get(DISABLED);
355
 
356
        // Run this to set initial values
357
        sv._uiDimensionsChange();
358
 
359
        // If we're out-of-bounds, snap back.
360
        if (sv._isOutOfBounds()) {
361
            sv._snapBack();
362
        }
363
    },
364
 
365
    /**
366
     * Utility method to obtain widget dimensions
367
     *
368
     * @method _getScrollDims
369
     * @return {Object} The offsetWidth, offsetHeight, scrollWidth and
370
     *  scrollHeight as an array: [offsetWidth, offsetHeight, scrollWidth,
371
     *  scrollHeight]
372
     * @private
373
     */
374
    _getScrollDims: function () {
375
        var sv = this,
376
            cb = sv._cb,
377
            bb = sv._bb,
378
            TRANS = ScrollView._TRANSITION,
379
            // Ideally using CSSMatrix - don't think we have it normalized yet though.
380
            // origX = (new WebKitCSSMatrix(cb.getComputedStyle("transform"))).e,
381
            // origY = (new WebKitCSSMatrix(cb.getComputedStyle("transform"))).f,
382
            origX = sv.get(SCROLL_X),
383
            origY = sv.get(SCROLL_Y),
384
            origHWTransform,
385
            dims;
386
 
387
        // TODO: Is this OK? Just in case it's called 'during' a transition.
388
        if (NATIVE_TRANSITIONS) {
389
            cb.setStyle(TRANS.DURATION, ZERO);
390
            cb.setStyle(TRANS.PROPERTY, EMPTY);
391
        }
392
 
393
        origHWTransform = sv._forceHWTransforms;
394
        sv._forceHWTransforms = false; // the z translation was causing issues with picking up accurate scrollWidths in Chrome/Mac.
395
 
396
        sv._moveTo(cb, 0, 0);
397
        dims = {
398
            'offsetWidth': bb.get('offsetWidth'),
399
            'offsetHeight': bb.get('offsetHeight'),
400
            'scrollWidth': bb.get('scrollWidth'),
401
            'scrollHeight': bb.get('scrollHeight')
402
        };
403
        sv._moveTo(cb, -(origX), -(origY));
404
 
405
        sv._forceHWTransforms = origHWTransform;
406
 
407
        return dims;
408
    },
409
 
410
    /**
411
     * This method gets invoked whenever the height or width attributes change,
412
     * allowing us to determine which scrolling axes need to be enabled.
413
     *
414
     * @method _uiDimensionsChange
415
     * @protected
416
     */
417
    _uiDimensionsChange: function () {
418
        var sv = this,
419
            bb = sv._bb,
420
            scrollDims = sv._getScrollDims(),
421
            width = scrollDims.offsetWidth,
422
            height = scrollDims.offsetHeight,
423
            scrollWidth = scrollDims.scrollWidth,
424
            scrollHeight = scrollDims.scrollHeight,
425
            rtl = sv.rtl,
426
            svAxis = sv._cAxis,
427
            minScrollX = (rtl ? Math.min(0, -(scrollWidth - width)) : 0),
428
            maxScrollX = (rtl ? 0 : Math.max(0, scrollWidth - width)),
429
            minScrollY = 0,
430
            maxScrollY = Math.max(0, scrollHeight - height);
431
 
432
        if (svAxis && svAxis.x) {
433
            bb.addClass(CLASS_NAMES.horizontal);
434
        }
435
 
436
        if (svAxis && svAxis.y) {
437
            bb.addClass(CLASS_NAMES.vertical);
438
        }
439
 
440
        sv._setBounds({
441
            minScrollX: minScrollX,
442
            maxScrollX: maxScrollX,
443
            minScrollY: minScrollY,
444
            maxScrollY: maxScrollY
445
        });
446
    },
447
 
448
    /**
449
     * Set the bounding dimensions of the ScrollView
450
     *
451
     * @method _setBounds
452
     * @protected
453
     * @param bounds {Object} [duration] ms of the scroll animation. (default is 0)
454
     *   @param {Number} [bounds.minScrollX] The minimum scroll X value
455
     *   @param {Number} [bounds.maxScrollX] The maximum scroll X value
456
     *   @param {Number} [bounds.minScrollY] The minimum scroll Y value
457
     *   @param {Number} [bounds.maxScrollY] The maximum scroll Y value
458
     */
459
    _setBounds: function (bounds) {
460
        var sv = this;
461
 
462
        // TODO: Do a check to log if the bounds are invalid
463
 
464
        sv._minScrollX = bounds.minScrollX;
465
        sv._maxScrollX = bounds.maxScrollX;
466
        sv._minScrollY = bounds.minScrollY;
467
        sv._maxScrollY = bounds.maxScrollY;
468
    },
469
 
470
    /**
471
     * Get the bounding dimensions of the ScrollView
472
     *
473
     * @method _getBounds
474
     * @protected
475
     */
476
    _getBounds: function () {
477
        var sv = this;
478
 
479
        return {
480
            minScrollX: sv._minScrollX,
481
            maxScrollX: sv._maxScrollX,
482
            minScrollY: sv._minScrollY,
483
            maxScrollY: sv._maxScrollY
484
        };
485
 
486
    },
487
 
488
    /**
489
     * Scroll the element to a given xy coordinate
490
     *
491
     * @method scrollTo
492
     * @param x {Number} The x-position to scroll to. (null for no movement)
493
     * @param y {Number} The y-position to scroll to. (null for no movement)
494
     * @param {Number} [duration] ms of the scroll animation. (default is 0)
495
     * @param {String} [easing] An easing equation if duration is set. (default is `easing` attribute)
496
     * @param {String} [node] The node to transform.  Setting this can be useful in
497
     *  dual-axis paginated instances. (default is the instance's contentBox)
498
     */
499
    scrollTo: function (x, y, duration, easing, node) {
500
        // Check to see if widget is disabled
501
        if (this._cDisabled) {
502
            return;
503
        }
504
 
505
        var sv = this,
506
            cb = sv._cb,
507
            TRANS = ScrollView._TRANSITION,
508
            callback = Y.bind(sv._onTransEnd, sv), // @Todo : cache this
509
            newX = 0,
510
            newY = 0,
511
            transition = {},
512
            transform;
513
 
514
        // default the optional arguments
515
        duration = duration || 0;
516
        easing = easing || sv.get(EASING); // @TODO: Cache this
517
        node = node || cb;
518
 
519
        if (x !== null) {
520
            sv.set(SCROLL_X, x, {src:UI});
521
            newX = -(x);
522
        }
523
 
524
        if (y !== null) {
525
            sv.set(SCROLL_Y, y, {src:UI});
526
            newY = -(y);
527
        }
528
 
529
        transform = sv._transform(newX, newY);
530
 
531
        if (NATIVE_TRANSITIONS) {
532
            // ANDROID WORKAROUND - try and stop existing transition, before kicking off new one.
533
            node.setStyle(TRANS.DURATION, ZERO).setStyle(TRANS.PROPERTY, EMPTY);
534
        }
535
 
536
        // Move
537
        if (duration === 0) {
538
            if (NATIVE_TRANSITIONS) {
539
                node.setStyle('transform', transform);
540
            }
541
            else {
542
                // TODO: If both set, batch them in the same update
543
                // Update: Nope, setStyles() just loops through each property and applies it.
544
                if (x !== null) {
545
                    node.setStyle(LEFT, newX + PX);
546
                }
547
                if (y !== null) {
548
                    node.setStyle(TOP, newY + PX);
549
                }
550
            }
551
        }
552
 
553
        // Animate
554
        else {
555
            transition.easing = easing;
556
            transition.duration = duration / 1000;
557
 
558
            if (NATIVE_TRANSITIONS) {
559
                transition.transform = transform;
560
            }
561
            else {
562
                transition.left = newX + PX;
563
                transition.top = newY + PX;
564
            }
565
 
566
            node.transition(transition, callback);
567
        }
568
    },
569
 
570
    /**
571
     * Utility method, to create the translate transform string with the
572
     * x, y translation amounts provided.
573
     *
574
     * @method _transform
575
     * @param {Number} x Number of pixels to translate along the x axis
576
     * @param {Number} y Number of pixels to translate along the y axis
577
     * @private
578
     */
579
    _transform: function (x, y) {
580
        // TODO: Would we be better off using a Matrix for this?
581
        var prop = 'translate(' + x + 'px, ' + y + 'px)';
582
 
583
        if (this._forceHWTransforms) {
584
            prop += ' translateZ(0)';
585
        }
586
 
587
        return prop;
588
    },
589
 
590
    /**
591
    * Utility method, to move the given element to the given xy position
592
    *
593
    * @method _moveTo
594
    * @param node {Node} The node to move
595
    * @param x {Number} The x-position to move to
596
    * @param y {Number} The y-position to move to
597
    * @private
598
    */
599
    _moveTo : function(node, x, y) {
600
        if (NATIVE_TRANSITIONS) {
601
            node.setStyle('transform', this._transform(x, y));
602
        } else {
603
            node.setStyle(LEFT, x + PX);
604
            node.setStyle(TOP, y + PX);
605
        }
606
    },
607
 
608
 
609
    /**
610
     * Content box transition callback
611
     *
612
     * @method _onTransEnd
613
     * @param {EventFacade} e The event facade
614
     * @private
615
     */
616
    _onTransEnd: function () {
617
        var sv = this;
618
 
619
        // If for some reason we're OOB, snapback
620
        if (sv._isOutOfBounds()) {
621
            sv._snapBack();
622
        }
623
        else {
624
            /**
625
             * Notification event fired at the end of a scroll transition
626
             *
627
             * @event scrollEnd
628
             * @param e {EventFacade} The default event facade.
629
             */
630
            sv.fire(EV_SCROLL_END);
631
        }
632
    },
633
 
634
    /**
635
     * gesturemovestart event handler
636
     *
637
     * @method _onGestureMoveStart
638
     * @param e {EventFacade} The gesturemovestart event facade
639
     * @private
640
     */
641
    _onGestureMoveStart: function (e) {
642
 
643
        if (this._cDisabled) {
644
            return false;
645
        }
646
 
647
        var sv = this,
648
            bb = sv._bb,
649
            currentX = sv.get(SCROLL_X),
650
            currentY = sv.get(SCROLL_Y),
651
            clientX = e.clientX,
652
            clientY = e.clientY;
653
 
654
        if (sv._prevent.start) {
655
            e.preventDefault();
656
        }
657
 
658
        // if a flick animation is in progress, cancel it
659
        if (sv._flickAnim) {
660
            sv._cancelFlick();
661
            sv._onTransEnd();
662
        }
663
 
664
        // Reset lastScrolledAmt
665
        sv.lastScrolledAmt = 0;
666
 
667
        // Stores data for this gesture cycle.  Cleaned up later
668
        sv._gesture = {
669
 
670
            // Will hold the axis value
671
            axis: null,
672
 
673
            // The current attribute values
674
            startX: currentX,
675
            startY: currentY,
676
 
677
            // The X/Y coordinates where the event began
678
            startClientX: clientX,
679
            startClientY: clientY,
680
 
681
            // The X/Y coordinates where the event will end
682
            endClientX: null,
683
            endClientY: null,
684
 
685
            // The current delta of the event
686
            deltaX: null,
687
            deltaY: null,
688
 
689
            // Will be populated for flicks
690
            flick: null,
691
 
692
            // Create some listeners for the rest of the gesture cycle
693
            onGestureMove: bb.on(DRAG + '|' + GESTURE_MOVE, Y.bind(sv._onGestureMove, sv)),
694
 
695
            // @TODO: Don't bind gestureMoveEnd if it's a Flick?
696
            onGestureMoveEnd: bb.on(DRAG + '|' + GESTURE_MOVE + END, Y.bind(sv._onGestureMoveEnd, sv))
697
        };
698
    },
699
 
700
    /**
701
     * gesturemove event handler
702
     *
703
     * @method _onGestureMove
704
     * @param e {EventFacade} The gesturemove event facade
705
     * @private
706
     */
707
    _onGestureMove: function (e) {
708
        var sv = this,
709
            gesture = sv._gesture,
710
            svAxis = sv._cAxis,
711
            svAxisX = svAxis.x,
712
            svAxisY = svAxis.y,
713
            startX = gesture.startX,
714
            startY = gesture.startY,
715
            startClientX = gesture.startClientX,
716
            startClientY = gesture.startClientY,
717
            clientX = e.clientX,
718
            clientY = e.clientY;
719
 
720
        if (sv._prevent.move) {
721
            e.preventDefault();
722
        }
723
 
724
        gesture.deltaX = startClientX - clientX;
725
        gesture.deltaY = startClientY - clientY;
726
 
727
        // Determine if this is a vertical or horizontal movement
728
        // @TODO: This is crude, but it works.  Investigate more intelligent ways to detect intent
729
        if (gesture.axis === null) {
730
            gesture.axis = (Math.abs(gesture.deltaX) > Math.abs(gesture.deltaY)) ? DIM_X : DIM_Y;
731
        }
732
 
733
        // Move X or Y.  @TODO: Move both if dualaxis.
734
        if (gesture.axis === DIM_X && svAxisX) {
735
            sv.set(SCROLL_X, startX + gesture.deltaX);
736
        }
737
        else if (gesture.axis === DIM_Y && svAxisY) {
738
            sv.set(SCROLL_Y, startY + gesture.deltaY);
739
        }
740
    },
741
 
742
    /**
743
     * gesturemoveend event handler
744
     *
745
     * @method _onGestureMoveEnd
746
     * @param e {EventFacade} The gesturemoveend event facade
747
     * @private
748
     */
749
    _onGestureMoveEnd: function (e) {
750
        var sv = this,
751
            gesture = sv._gesture,
752
            flick = gesture.flick,
753
            clientX = e.clientX,
754
            clientY = e.clientY,
755
            isOOB;
756
 
757
        if (sv._prevent.end) {
758
            e.preventDefault();
759
        }
760
 
761
        // Store the end X/Y coordinates
762
        gesture.endClientX = clientX;
763
        gesture.endClientY = clientY;
764
 
765
        // Cleanup the event handlers
766
        gesture.onGestureMove.detach();
767
        gesture.onGestureMoveEnd.detach();
768
 
769
        // If this wasn't a flick, wrap up the gesture cycle
770
        if (!flick) {
771
            // @TODO: Be more intelligent about this. Look at the Flick attribute to see
772
            // if it is safe to assume _flick did or didn't fire.
773
            // Then, the order _flick and _onGestureMoveEnd fire doesn't matter?
774
 
775
            // If there was movement (_onGestureMove fired)
776
            if (gesture.deltaX !== null && gesture.deltaY !== null) {
777
 
778
                isOOB = sv._isOutOfBounds();
779
 
780
                // If we're out-out-bounds, then snapback
781
                if (isOOB) {
782
                    sv._snapBack();
783
                }
784
 
785
                // Inbounds
786
                else {
787
                    // Fire scrollEnd unless this is a paginated instance and the gesture axis is the same as paginator's
788
                    // Not totally confident this is ideal to access a plugin's properties from a host, @TODO revisit
789
                    if (!sv.pages || (sv.pages && !sv.pages.get(AXIS)[gesture.axis])) {
790
                        sv._onTransEnd();
791
                    }
792
                }
793
            }
794
        }
795
    },
796
 
797
    /**
798
     * Execute a flick at the end of a scroll action
799
     *
800
     * @method _flick
801
     * @param e {EventFacade} The Flick event facade
802
     * @private
803
     */
804
    _flick: function (e) {
805
        if (this._cDisabled) {
806
            return false;
807
        }
808
 
809
        var sv = this,
810
            svAxis = sv._cAxis,
811
            flick = e.flick,
812
            flickAxis = flick.axis,
813
            flickVelocity = flick.velocity,
814
            axisAttr = flickAxis === DIM_X ? SCROLL_X : SCROLL_Y,
815
            startPosition = sv.get(axisAttr);
816
 
817
        // Sometimes flick is enabled, but drag is disabled
818
        if (sv._gesture) {
819
            sv._gesture.flick = flick;
820
        }
821
 
822
        // Prevent unneccesary firing of _flickFrame if we can't scroll on the flick axis
823
        if (svAxis[flickAxis]) {
824
            sv._flickFrame(flickVelocity, flickAxis, startPosition);
825
        }
826
    },
827
 
828
    /**
829
     * Execute a single frame in the flick animation
830
     *
831
     * @method _flickFrame
832
     * @param velocity {Number} The velocity of this animated frame
833
     * @param flickAxis {String} The axis on which to animate
834
     * @param startPosition {Number} The starting X/Y point to flick from
835
     * @protected
836
     */
837
    _flickFrame: function (velocity, flickAxis, startPosition) {
838
 
839
        var sv = this,
840
            axisAttr = flickAxis === DIM_X ? SCROLL_X : SCROLL_Y,
841
            bounds = sv._getBounds(),
842
 
843
            // Localize cached values
844
            bounce = sv._cBounce,
845
            bounceRange = sv._cBounceRange,
846
            deceleration = sv._cDeceleration,
847
            frameDuration = sv._cFrameDuration,
848
 
849
            // Calculate
850
            newVelocity = velocity * deceleration,
851
            newPosition = startPosition - (frameDuration * newVelocity),
852
 
853
            // Some convinience conditions
854
            min = flickAxis === DIM_X ? bounds.minScrollX : bounds.minScrollY,
855
            max = flickAxis === DIM_X ? bounds.maxScrollX : bounds.maxScrollY,
856
            belowMin       = (newPosition < min),
857
            belowMax       = (newPosition < max),
858
            aboveMin       = (newPosition > min),
859
            aboveMax       = (newPosition > max),
860
            belowMinRange  = (newPosition < (min - bounceRange)),
861
            withinMinRange = (belowMin && (newPosition > (min - bounceRange))),
862
            withinMaxRange = (aboveMax && (newPosition < (max + bounceRange))),
863
            aboveMaxRange  = (newPosition > (max + bounceRange)),
864
            tooSlow;
865
 
866
        // If we're within the range but outside min/max, dampen the velocity
867
        if (withinMinRange || withinMaxRange) {
868
            newVelocity *= bounce;
869
        }
870
 
871
        // Is the velocity too slow to bother?
872
        tooSlow = (Math.abs(newVelocity).toFixed(4) < 0.015);
873
 
874
        // If the velocity is too slow or we're outside the range
875
        if (tooSlow || belowMinRange || aboveMaxRange) {
876
            // Cancel and delete sv._flickAnim
877
            if (sv._flickAnim) {
878
                sv._cancelFlick();
879
            }
880
 
881
            // If we're inside the scroll area, just end
882
            if (aboveMin && belowMax) {
883
                sv._onTransEnd();
884
            }
885
 
886
            // We're outside the scroll area, so we need to snap back
887
            else {
888
                sv._snapBack();
889
            }
890
        }
891
 
892
        // Otherwise, animate to the next frame
893
        else {
894
            // @TODO: maybe use requestAnimationFrame instead
895
            sv._flickAnim = Y.later(frameDuration, sv, '_flickFrame', [newVelocity, flickAxis, newPosition]);
896
            sv.set(axisAttr, newPosition);
897
        }
898
    },
899
 
900
    _cancelFlick: function () {
901
        var sv = this;
902
 
903
        if (sv._flickAnim) {
904
            // Cancel the flick (if it exists)
905
            sv._flickAnim.cancel();
906
 
907
            // Also delete it, otherwise _onGestureMoveStart will think we're still flicking
908
            delete sv._flickAnim;
909
        }
910
 
911
    },
912
 
913
    /**
914
     * Handle mousewheel events on the widget
915
     *
916
     * @method _mousewheel
917
     * @param e {EventFacade} The mousewheel event facade
918
     * @private
919
     */
920
    _mousewheel: function (e) {
921
        var sv = this,
922
            scrollY = sv.get(SCROLL_Y),
923
            bounds = sv._getBounds(),
924
            bb = sv._bb,
925
            scrollOffset = 10, // 10px
926
            isForward = (e.wheelDelta > 0),
927
            scrollToY = scrollY - ((isForward ? 1 : -1) * scrollOffset);
928
 
929
        scrollToY = _constrain(scrollToY, bounds.minScrollY, bounds.maxScrollY);
930
 
931
        // Because Mousewheel events fire off 'document', every ScrollView widget will react
932
        // to any mousewheel anywhere on the page. This check will ensure that the mouse is currently
933
        // over this specific ScrollView.  Also, only allow mousewheel scrolling on Y-axis,
934
        // becuase otherwise the 'prevent' will block page scrolling.
935
        if (bb.contains(e.target) && sv._cAxis[DIM_Y]) {
936
 
937
            // Reset lastScrolledAmt
938
            sv.lastScrolledAmt = 0;
939
 
940
            // Jump to the new offset
941
            sv.set(SCROLL_Y, scrollToY);
942
 
943
            // if we have scrollbars plugin, update & set the flash timer on the scrollbar
944
            // @TODO: This probably shouldn't be in this module
945
            if (sv.scrollbars) {
946
                // @TODO: The scrollbars should handle this themselves
947
                sv.scrollbars._update();
948
                sv.scrollbars.flash();
949
                // or just this
950
                // sv.scrollbars._hostDimensionsChange();
951
            }
952
 
953
            // Fire the 'scrollEnd' event
954
            sv._onTransEnd();
955
 
956
            // prevent browser default behavior on mouse scroll
957
            e.preventDefault();
958
        }
959
    },
960
 
961
    /**
962
     * Checks to see the current scrollX/scrollY position beyond the min/max boundary
963
     *
964
     * @method _isOutOfBounds
965
     * @param x {Number} [optional] The X position to check
966
     * @param y {Number} [optional] The Y position to check
967
     * @return {Boolean} Whether the current X/Y position is out of bounds (true) or not (false)
968
     * @private
969
     */
970
    _isOutOfBounds: function (x, y) {
971
        var sv = this,
972
            svAxis = sv._cAxis,
973
            svAxisX = svAxis.x,
974
            svAxisY = svAxis.y,
975
            currentX = x || sv.get(SCROLL_X),
976
            currentY = y || sv.get(SCROLL_Y),
977
            bounds = sv._getBounds(),
978
            minX = bounds.minScrollX,
979
            minY = bounds.minScrollY,
980
            maxX = bounds.maxScrollX,
981
            maxY = bounds.maxScrollY;
982
 
983
        return (svAxisX && (currentX < minX || currentX > maxX)) || (svAxisY && (currentY < minY || currentY > maxY));
984
    },
985
 
986
    /**
987
     * Bounces back
988
     * @TODO: Should be more generalized and support both X and Y detection
989
     *
990
     * @method _snapBack
991
     * @private
992
     */
993
    _snapBack: function () {
994
        var sv = this,
995
            currentX = sv.get(SCROLL_X),
996
            currentY = sv.get(SCROLL_Y),
997
            bounds = sv._getBounds(),
998
            minX = bounds.minScrollX,
999
            minY = bounds.minScrollY,
1000
            maxX = bounds.maxScrollX,
1001
            maxY = bounds.maxScrollY,
1002
            newY = _constrain(currentY, minY, maxY),
1003
            newX = _constrain(currentX, minX, maxX),
1004
            duration = sv.get(SNAP_DURATION),
1005
            easing = sv.get(SNAP_EASING);
1006
 
1007
        if (newX !== currentX) {
1008
            sv.set(SCROLL_X, newX, {duration:duration, easing:easing});
1009
        }
1010
        else if (newY !== currentY) {
1011
            sv.set(SCROLL_Y, newY, {duration:duration, easing:easing});
1012
        }
1013
        else {
1014
            sv._onTransEnd();
1015
        }
1016
    },
1017
 
1018
    /**
1019
     * After listener for changes to the scrollX or scrollY attribute
1020
     *
1021
     * @method _afterScrollChange
1022
     * @param e {EventFacade} The event facade
1023
     * @protected
1024
     */
1025
    _afterScrollChange: function (e) {
1026
        if (e.src === ScrollView.UI_SRC) {
1027
            return false;
1028
        }
1029
 
1030
        var sv = this,
1031
            duration = e.duration,
1032
            easing = e.easing,
1033
            val = e.newVal,
1034
            scrollToArgs = [];
1035
 
1036
        // Set the scrolled value
1037
        sv.lastScrolledAmt = sv.lastScrolledAmt + (e.newVal - e.prevVal);
1038
 
1039
        // Generate the array of args to pass to scrollTo()
1040
        if (e.attrName === SCROLL_X) {
1041
            scrollToArgs.push(val);
1042
            scrollToArgs.push(sv.get(SCROLL_Y));
1043
        }
1044
        else {
1045
            scrollToArgs.push(sv.get(SCROLL_X));
1046
            scrollToArgs.push(val);
1047
        }
1048
 
1049
        scrollToArgs.push(duration);
1050
        scrollToArgs.push(easing);
1051
 
1052
        sv.scrollTo.apply(sv, scrollToArgs);
1053
    },
1054
 
1055
    /**
1056
     * After listener for changes to the flick attribute
1057
     *
1058
     * @method _afterFlickChange
1059
     * @param e {EventFacade} The event facade
1060
     * @protected
1061
     */
1062
    _afterFlickChange: function (e) {
1063
        this._bindFlick(e.newVal);
1064
    },
1065
 
1066
    /**
1067
     * After listener for changes to the disabled attribute
1068
     *
1069
     * @method _afterDisabledChange
1070
     * @param e {EventFacade} The event facade
1071
     * @protected
1072
     */
1073
    _afterDisabledChange: function (e) {
1074
        // Cache for performance - we check during move
1075
        this._cDisabled = e.newVal;
1076
    },
1077
 
1078
    /**
1079
     * After listener for the axis attribute
1080
     *
1081
     * @method _afterAxisChange
1082
     * @param e {EventFacade} The event facade
1083
     * @protected
1084
     */
1085
    _afterAxisChange: function (e) {
1086
        this._cAxis = e.newVal;
1087
    },
1088
 
1089
    /**
1090
     * After listener for changes to the drag attribute
1091
     *
1092
     * @method _afterDragChange
1093
     * @param e {EventFacade} The event facade
1094
     * @protected
1095
     */
1096
    _afterDragChange: function (e) {
1097
        this._bindDrag(e.newVal);
1098
    },
1099
 
1100
    /**
1101
     * After listener for the height or width attribute
1102
     *
1103
     * @method _afterDimChange
1104
     * @param e {EventFacade} The event facade
1105
     * @protected
1106
     */
1107
    _afterDimChange: function () {
1108
        this._uiDimensionsChange();
1109
    },
1110
 
1111
    /**
1112
     * After listener for scrollEnd, for cleanup
1113
     *
1114
     * @method _afterScrollEnd
1115
     * @param e {EventFacade} The event facade
1116
     * @protected
1117
     */
1118
    _afterScrollEnd: function () {
1119
        var sv = this;
1120
 
1121
        if (sv._flickAnim) {
1122
            sv._cancelFlick();
1123
        }
1124
 
1125
        // Ideally this should be removed, but doing so causing some JS errors with fast swiping
1126
        // because _gesture is being deleted after the previous one has been overwritten
1127
        // delete sv._gesture; // TODO: Move to sv.prevGesture?
1128
    },
1129
 
1130
    /**
1131
     * Setter for 'axis' attribute
1132
     *
1133
     * @method _axisSetter
1134
     * @param val {Mixed} A string ('x', 'y', 'xy') to specify which axis/axes to allow scrolling on
1135
     * @param name {String} The attribute name
1136
     * @return {Object} An object to specify scrollability on the x & y axes
1137
     *
1138
     * @protected
1139
     */
1140
    _axisSetter: function (val) {
1141
 
1142
        // Turn a string into an axis object
1143
        if (Y.Lang.isString(val)) {
1144
            return {
1145
                x: val.match(/x/i) ? true : false,
1146
                y: val.match(/y/i) ? true : false
1147
            };
1148
        }
1149
    },
1150
 
1151
    /**
1152
    * The scrollX, scrollY setter implementation
1153
    *
1154
    * @method _setScroll
1155
    * @private
1156
    * @param {Number} val
1157
    * @param {String} dim
1158
    *
1159
    * @return {Number} The value
1160
    */
1161
    _setScroll : function(val) {
1162
 
1163
        // Just ensure the widget is not disabled
1164
        if (this._cDisabled) {
1165
            val = Y.Attribute.INVALID_VALUE;
1166
        }
1167
 
1168
        return val;
1169
    },
1170
 
1171
    /**
1172
    * Setter for the scrollX attribute
1173
    *
1174
    * @method _setScrollX
1175
    * @param val {Number} The new scrollX value
1176
    * @return {Number} The normalized value
1177
    * @protected
1178
    */
1179
    _setScrollX: function(val) {
1180
        return this._setScroll(val, DIM_X);
1181
    },
1182
 
1183
    /**
1184
    * Setter for the scrollY ATTR
1185
    *
1186
    * @method _setScrollY
1187
    * @param val {Number} The new scrollY value
1188
    * @return {Number} The normalized value
1189
    * @protected
1190
    */
1191
    _setScrollY: function(val) {
1192
        return this._setScroll(val, DIM_Y);
1193
    }
1194
 
1195
    // End prototype properties
1196
 
1197
}, {
1198
 
1199
    // Static properties
1200
 
1201
    /**
1202
     * The identity of the widget.
1203
     *
1204
     * @property NAME
1205
     * @type String
1206
     * @default 'scrollview'
1207
     * @readOnly
1208
     * @protected
1209
     * @static
1210
     */
1211
    NAME: 'scrollview',
1212
 
1213
    /**
1214
     * Static property used to define the default attribute configuration of
1215
     * the Widget.
1216
     *
1217
     * @property ATTRS
1218
     * @type {Object}
1219
     * @protected
1220
     * @static
1221
     */
1222
    ATTRS: {
1223
 
1224
        /**
1225
         * Specifies ability to scroll on x, y, or x and y axis/axes.
1226
         *
1227
         * @attribute axis
1228
         * @type String
1229
         */
1230
        axis: {
1231
            setter: '_axisSetter',
1232
            writeOnce: 'initOnly'
1233
        },
1234
 
1235
        /**
1236
         * The current scroll position in the x-axis
1237
         *
1238
         * @attribute scrollX
1239
         * @type Number
1240
         * @default 0
1241
         */
1242
        scrollX: {
1243
            value: 0,
1244
            setter: '_setScrollX'
1245
        },
1246
 
1247
        /**
1248
         * The current scroll position in the y-axis
1249
         *
1250
         * @attribute scrollY
1251
         * @type Number
1252
         * @default 0
1253
         */
1254
        scrollY: {
1255
            value: 0,
1256
            setter: '_setScrollY'
1257
        },
1258
 
1259
        /**
1260
         * Drag coefficent for inertial scrolling. The closer to 1 this
1261
         * value is, the less friction during scrolling.
1262
         *
1263
         * @attribute deceleration
1264
         * @default 0.93
1265
         */
1266
        deceleration: {
1267
            value: 0.93
1268
        },
1269
 
1270
        /**
1271
         * Drag coefficient for intertial scrolling at the upper
1272
         * and lower boundaries of the scrollview. Set to 0 to
1273
         * disable "rubber-banding".
1274
         *
1275
         * @attribute bounce
1276
         * @type Number
1277
         * @default 0.1
1278
         */
1279
        bounce: {
1280
            value: 0.1
1281
        },
1282
 
1283
        /**
1284
         * The minimum distance and/or velocity which define a flick. Can be set to false,
1285
         * to disable flick support (note: drag support is enabled/disabled separately)
1286
         *
1287
         * @attribute flick
1288
         * @type Object
1289
         * @default Object with properties minDistance = 10, minVelocity = 0.3.
1290
         */
1291
        flick: {
1292
            value: {
1293
                minDistance: 10,
1294
                minVelocity: 0.3
1295
            }
1296
        },
1297
 
1298
        /**
1299
         * Enable/Disable dragging the ScrollView content (note: flick support is enabled/disabled separately)
1300
         * @attribute drag
1301
         * @type boolean
1302
         * @default true
1303
         */
1304
        drag: {
1305
            value: true
1306
        },
1307
 
1308
        /**
1309
         * The default duration to use when animating the bounce snap back.
1310
         *
1311
         * @attribute snapDuration
1312
         * @type Number
1313
         * @default 400
1314
         */
1315
        snapDuration: {
1316
            value: 400
1317
        },
1318
 
1319
        /**
1320
         * The default easing to use when animating the bounce snap back.
1321
         *
1322
         * @attribute snapEasing
1323
         * @type String
1324
         * @default 'ease-out'
1325
         */
1326
        snapEasing: {
1327
            value: 'ease-out'
1328
        },
1329
 
1330
        /**
1331
         * The default easing used when animating the flick
1332
         *
1333
         * @attribute easing
1334
         * @type String
1335
         * @default 'cubic-bezier(0, 0.1, 0, 1.0)'
1336
         */
1337
        easing: {
1338
            value: 'cubic-bezier(0, 0.1, 0, 1.0)'
1339
        },
1340
 
1341
        /**
1342
         * The interval (ms) used when animating the flick for JS-timer animations
1343
         *
1344
         * @attribute frameDuration
1345
         * @type Number
1346
         * @default 15
1347
         */
1348
        frameDuration: {
1349
            value: 15
1350
        },
1351
 
1352
        /**
1353
         * The default bounce distance in pixels
1354
         *
1355
         * @attribute bounceRange
1356
         * @type Number
1357
         * @default 150
1358
         */
1359
        bounceRange: {
1360
            value: 150
1361
        }
1362
    },
1363
 
1364
    /**
1365
     * List of class names used in the scrollview's DOM
1366
     *
1367
     * @property CLASS_NAMES
1368
     * @type Object
1369
     * @static
1370
     */
1371
    CLASS_NAMES: CLASS_NAMES,
1372
 
1373
    /**
1374
     * Flag used to source property changes initiated from the DOM
1375
     *
1376
     * @property UI_SRC
1377
     * @type String
1378
     * @static
1379
     * @default 'ui'
1380
     */
1381
    UI_SRC: UI,
1382
 
1383
    /**
1384
     * Object map of style property names used to set transition properties.
1385
     * Defaults to the vendor prefix established by the Transition module.
1386
     * The configured property names are `_TRANSITION.DURATION` (e.g. "WebkitTransitionDuration") and
1387
     * `_TRANSITION.PROPERTY (e.g. "WebkitTransitionProperty").
1388
     *
1389
     * @property _TRANSITION
1390
     * @private
1391
     */
1392
    _TRANSITION: {
1393
        DURATION: (vendorPrefix ? vendorPrefix + 'TransitionDuration' : 'transitionDuration'),
1394
        PROPERTY: (vendorPrefix ? vendorPrefix + 'TransitionProperty' : 'transitionProperty')
1395
    },
1396
 
1397
    /**
1398
     * The default bounce distance in pixels
1399
     *
1400
     * @property BOUNCE_RANGE
1401
     * @type Number
1402
     * @static
1403
     * @default false
1404
     * @deprecated (in 3.7.0)
1405
     */
1406
    BOUNCE_RANGE: false,
1407
 
1408
    /**
1409
     * The interval (ms) used when animating the flick
1410
     *
1411
     * @property FRAME_STEP
1412
     * @type Number
1413
     * @static
1414
     * @default false
1415
     * @deprecated (in 3.7.0)
1416
     */
1417
    FRAME_STEP: false,
1418
 
1419
    /**
1420
     * The default easing used when animating the flick
1421
     *
1422
     * @property EASING
1423
     * @type String
1424
     * @static
1425
     * @default false
1426
     * @deprecated (in 3.7.0)
1427
     */
1428
    EASING: false,
1429
 
1430
    /**
1431
     * The default easing to use when animating the bounce snap back.
1432
     *
1433
     * @property SNAP_EASING
1434
     * @type String
1435
     * @static
1436
     * @default false
1437
     * @deprecated (in 3.7.0)
1438
     */
1439
    SNAP_EASING: false,
1440
 
1441
    /**
1442
     * The default duration to use when animating the bounce snap back.
1443
     *
1444
     * @property SNAP_DURATION
1445
     * @type Number
1446
     * @static
1447
     * @default false
1448
     * @deprecated (in 3.7.0)
1449
     */
1450
    SNAP_DURATION: false
1451
 
1452
    // End static properties
1453
 
1454
});
1455
 
1456
 
1457
}, '3.18.1', {"requires": ["widget", "event-gestures", "event-mousewheel", "transition"], "skinnable": true});