~ted/lazr-js/annoying-debug-message

« back to all changes in this revision

Viewing changes to src-js/lazrjs/yui/scrollview/scrollview-base.js

  • Committer: Launchpad Patch Queue Manager
  • Date: 2010-09-09 14:20:30 UTC
  • mfrom: (182.1.3 yui-3.2)
  • Revision ID: launchpad@pqm.canonical.com-20100909142030-13w6vo0ixfysxc15
[r=beuno] Update lazr-js to yui-3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
 
3
Code licensed under the BSD License:
 
4
http://developer.yahoo.com/yui/license.html
 
5
version: 3.2.0
 
6
build: 2676
 
7
*/
 
8
YUI.add('scrollview-base', function(Y) {
 
9
 
 
10
/**
 
11
 * The scrollview-base module provides a basic ScrollView Widget, without scrollbar indicators
 
12
 *
 
13
 * @module scrollview-base
 
14
 */
 
15
 
 
16
var getClassName = Y.ClassNameManager.getClassName,
 
17
    SCROLLVIEW = 'scrollview',
 
18
    CLASS_NAMES = {
 
19
        vertical: getClassName(SCROLLVIEW, 'vert'),
 
20
        horizontal: getClassName(SCROLLVIEW, 'horiz')
 
21
    },
 
22
    EV_SCROLL_END = 'scrollEnd',
 
23
    EV_SCROLL_FLICK = 'flick',
 
24
 
 
25
    FLICK = EV_SCROLL_FLICK,
 
26
 
 
27
    UI = 'ui',
 
28
    
 
29
    LEFT = "left",
 
30
    TOP = "top",
 
31
    
 
32
    PX = "px",
 
33
 
 
34
    SCROLL_Y = "scrollY",
 
35
    SCROLL_X = "scrollX",
 
36
    BOUNCE = "bounce",
 
37
    
 
38
    DIM_X = "x",
 
39
    DIM_Y = "y",
 
40
 
 
41
    BOUNDING_BOX = "boundingBox",
 
42
    CONTENT_BOX = "contentBox",
 
43
    
 
44
    EMPTY = "",
 
45
    ZERO = "0s",
 
46
    
 
47
    OWNER_DOC = "ownerDocument",
 
48
    MOUSE_UP = "mouseup",
 
49
 
 
50
    IE = Y.UA.ie,
 
51
 
 
52
    NATIVE_TRANSITIONS = Y.Transition.useNative;
 
53
 
 
54
Y.Node.DOM_EVENTS.DOMSubtreeModified = true;
 
55
 
 
56
/**
 
57
 * ScrollView provides a scrollable widget, supporting flick gestures, across both touch and mouse based devices. 
 
58
 *
 
59
 * @class ScrollView
 
60
 * @namespace 
 
61
 * @param config {Object} Object literal with initial attribute values
 
62
 * @extends Widget
 
63
 * @constructor
 
64
 */
 
65
function ScrollView() {
 
66
    ScrollView.superclass.constructor.apply(this, arguments);
 
67
}
 
68
 
 
69
Y.ScrollView = Y.extend(ScrollView, Y.Widget, {
 
70
    
 
71
    // Y.ScrollView prototype
 
72
    
 
73
    /**
 
74
     * Designated initializer
 
75
     *
 
76
     * @method initializer
 
77
     */
 
78
    initializer: function() {
 
79
        this._createEvents();
 
80
 
 
81
        // Cache - they're write once, and not going to change
 
82
        this._cb = this.get(CONTENT_BOX);
 
83
        this._bb = this.get(BOUNDING_BOX);
 
84
    },
 
85
 
 
86
    /** 
 
87
     * Publishes events which occur during the scroll lifecycle
 
88
     *
 
89
     * @method _createEvents
 
90
     * @private
 
91
     */    
 
92
    _createEvents: function() {
 
93
        /**
 
94
         * Notification event fired at the end of a scroll transition
 
95
         * 
 
96
         * @event scrollEnd
 
97
         * @param e {EventFacade} The default event facade.
 
98
         */
 
99
        this.publish(EV_SCROLL_END);
 
100
 
 
101
        /**
 
102
         * Notification event fired at the end of a flick gesture (the flick animation may still be in progress)
 
103
         * 
 
104
         * @event flick
 
105
         * @param e {EventFacade} The default event facade.
 
106
         */
 
107
        this.publish(EV_SCROLL_FLICK);
 
108
    },
 
109
 
 
110
    /** 
 
111
     * Override the contentBox sizing method, since the contentBox height
 
112
     * should not be that of the boundingBox.
 
113
     *
 
114
     * @method _uiSizeCB
 
115
     * @protected
 
116
     */
 
117
    _uiSizeCB: function() {},
 
118
 
 
119
    /**
 
120
     * Content box transition callback
 
121
     *
 
122
     * @method _transitionEnded
 
123
     * @param {Event.Facade} e The event facade
 
124
     * @private
 
125
     */
 
126
    _transitionEnded: function(e) {
 
127
        this.fire(EV_SCROLL_END);
 
128
    },
 
129
 
 
130
    /**
 
131
     * bindUI implementation
 
132
     *
 
133
     * Hooks up events for the widget
 
134
     * @method bindUI
 
135
     */
 
136
    bindUI: function() {
 
137
 
 
138
        var cb = this._cb,
 
139
            bb = this._bb,
 
140
            flick = this.get(FLICK); 
 
141
 
 
142
        bb.on('gesturemovestart', Y.bind(this._onGestureMoveStart, this));
 
143
 
 
144
        // IE SELECT HACK. See if we can do this non-natively and in the gesture for a future release.
 
145
        if (IE) {
 
146
            this._nativeBody = Y.Node.getDOMNode(Y.one("body", cb.get("ownerDocument")));
 
147
            this._cbDoc = cb.get(OWNER_DOC);
 
148
 
 
149
            cb.on("mousedown", function() {
 
150
                this._selectstart = this._nativeBody.onselectstart;
 
151
                this._nativeBody.onselectstart = this._iePreventSelect;
 
152
                this._cbDoc.once(MOUSE_UP, this._ieRestoreSelect, this);
 
153
            }, this);
 
154
        }
 
155
 
 
156
        // TODO: Fires way to often when using non-native transitions, due to property change
 
157
        if (NATIVE_TRANSITIONS) {
 
158
            cb.on('DOMSubtreeModified', Y.bind(this._uiDimensionsChange, this));
 
159
        }
 
160
 
 
161
        if (flick) {
 
162
            cb.on("flick", Y.bind(this._flick, this), flick);
 
163
        }
 
164
 
 
165
        this.after({
 
166
            'scrollYChange' : this._afterScrollYChange,
 
167
            'scrollXChange' : this._afterScrollXChange,
 
168
            'heightChange'  : this._afterHeightChange,
 
169
            'widthChange'   : this._afterWidthChange,
 
170
            'renderedChange': function() { Y.later(0, this, '_uiDimensionsChange'); } 
 
171
        });
 
172
    },
 
173
    
 
174
    /**
 
175
     * syncUI implementation
 
176
     *
 
177
     * Update the scroll position, based on the current value of scrollY
 
178
     * @method bindUI
 
179
     */
 
180
    syncUI: function() {
 
181
        this.scrollTo(this.get(SCROLL_X), this.get(SCROLL_Y));
 
182
    },
 
183
 
 
184
    /**
 
185
     * Scroll the element to a given y coordinate
 
186
     *
 
187
     * @method scrollTo
 
188
     * @param x {Number} The x-position to scroll to
 
189
     * @param y {Number} The y-position to scroll to
 
190
     * @param duration {Number} Duration, in ms, of the scroll animation (default is 0)
 
191
     * @param easing {String} An easing equation if duration is set
 
192
     */
 
193
    scrollTo: function(x, y, duration, easing) {
 
194
 
 
195
        var cb = this._cb,
 
196
            xSet = (x !== null),
 
197
            ySet = (y !== null),
 
198
            xMove = (xSet) ? x * -1 : 0,
 
199
            yMove = (ySet) ? y * -1 : 0,
 
200
            transition,
 
201
            callback = this._transEndCallback;
 
202
 
 
203
        duration = duration || 0;
 
204
        easing = easing || ScrollView.EASING;
 
205
 
 
206
        if (xSet) {
 
207
            this.set(SCROLL_X, x, { src: UI });
 
208
        }
 
209
 
 
210
        if (ySet) {
 
211
            this.set(SCROLL_Y, y, { src: UI });
 
212
        }
 
213
 
 
214
        if (NATIVE_TRANSITIONS) {
 
215
            // ANDROID WORKAROUND - try and stop existing transition, before kicking off new one.
 
216
            cb.setStyle(ScrollView._TRANSITION_DURATION, ZERO);
 
217
            cb.setStyle(ScrollView._TRANSITION_PROPERTY, EMPTY);
 
218
 
 
219
            // Causes bounce back from 0,0 instead of current translation for bottom/right edge animation
 
220
            // cb.setStyle("WebkitTransform", cb.getComputedStyle("WebkitTransform"));
 
221
        }
 
222
 
 
223
        if (duration !== 0) {
 
224
 
 
225
            transition = {
 
226
                easing : easing,
 
227
                duration : duration/1000
 
228
            };
 
229
 
 
230
            if (NATIVE_TRANSITIONS) {
 
231
                transition.transform = 'translate3D('+ xMove +'px,'+ yMove +'px, 0px)';
 
232
            } else {
 
233
                if (xSet) { transition.left = xMove + PX; }
 
234
                if (ySet) { transition.top = yMove + PX; }
 
235
            }
 
236
 
 
237
 
 
238
            if (!callback) {
 
239
                callback = this._transEndCallback = Y.bind(this._transitionEnded, this);
 
240
            }
 
241
 
 
242
            cb.transition(transition, callback);
 
243
 
 
244
        } else {
 
245
            if (NATIVE_TRANSITIONS) {
 
246
                cb.setStyle('transform', 'translate3D('+ xMove +'px,'+ yMove +'px, 0px)');
 
247
            } else {
 
248
                if (xSet) { cb.setStyle(LEFT, xMove + PX); }
 
249
                if (ySet) { cb.setStyle(TOP, yMove + PX); }
 
250
            }
 
251
        }
 
252
    },
 
253
 
 
254
    /**
 
255
     * Native onselectstart handle to prevent selection in IE
 
256
     *
 
257
     * @method _iePreventSelect
 
258
     * @private
 
259
     */
 
260
    _iePreventSelect : function() {
 
261
        return false;
 
262
    },
 
263
 
 
264
    /**
 
265
     * Restores native onselectstart handle, backed up to prevent selection in IE
 
266
     *
 
267
     * @method _ieRestoreSelect
 
268
     * @private
 
269
     */
 
270
    _ieRestoreSelect : function() {
 
271
        this._nativeBody.onselectstart = this._selectstart;
 
272
    },
 
273
 
 
274
    _preventStart : false,
 
275
 
 
276
    _preventMove : true,
 
277
    
 
278
    _preventEnd : true,
 
279
 
 
280
    /**
 
281
     * gesturemovestart event handler
 
282
     *
 
283
     * @method _onGestureMoveStart
 
284
     * @param e {Event.Facade} The gesturemovestart event facade
 
285
     * @private
 
286
     */
 
287
    _onGestureMoveStart: function(e) {
 
288
 
 
289
        var bb = this._bb;
 
290
 
 
291
        if (this._preventStart) {
 
292
            e.preventDefault();
 
293
        }
 
294
 
 
295
        this._killTimer();
 
296
 
 
297
        this._moveEvt = bb.on('gesturemove', Y.bind(this._onGestureMove, this));
 
298
        this._moveEndEvt = bb.on('gesturemoveend', Y.bind(this._onGestureMoveEnd, this));
 
299
 
 
300
        this._moveStartY = e.clientY + this.get(SCROLL_Y);
 
301
        this._moveStartX = e.clientX + this.get(SCROLL_X);
 
302
 
 
303
        this._moveStartTime = (new Date()).getTime();
 
304
        this._moveStartClientY = this._moveEndClientY = e.clientY;
 
305
        this._moveStartClientX = this._moveEndClientX = e.clientX;
 
306
 
 
307
        /**
 
308
         * Internal state, defines whether or not the scrollview is currently being dragged
 
309
         * 
 
310
         * @property _isDragging
 
311
         * @type boolean
 
312
         * @protected
 
313
         */
 
314
        this._isDragging = false;
 
315
        
 
316
        /**
 
317
         * Internal state, defines whether or not the scrollview is currently animating a flick
 
318
         * 
 
319
         * @property _flicking
 
320
         * @type boolean
 
321
         * @protected
 
322
         */
 
323
        this._flicking = false;
 
324
        
 
325
        /**
 
326
         * Internal state, defines whether or not the scrollview needs to snap to a boundary edge
 
327
         * 
 
328
         * @property _snapToEdge
 
329
         * @type boolean
 
330
         * @protected
 
331
         */
 
332
        this._snapToEdge = false;
 
333
    },    
 
334
    
 
335
    /**
 
336
     * gesturemove event handler
 
337
     *
 
338
     * @method _onGestureMove
 
339
     * @param e {Event.Facade} The gesturemove event facade
 
340
     * @private
 
341
     */
 
342
    _onGestureMove: function(e) {
 
343
 
 
344
        if (this._preventMove) {
 
345
            e.preventDefault();
 
346
        }
 
347
 
 
348
        this._isDragging = true;
 
349
        this._moveEndClientY = e.clientY;
 
350
        this._moveEndClientX = e.clientX;
 
351
        this._lastMoved = (new Date()).getTime();
 
352
 
 
353
        if(this._scrollsVertical) {
 
354
            this.set(SCROLL_Y, -(e.clientY - this._moveStartY));
 
355
        }
 
356
 
 
357
        if(this._scrollsHorizontal) {
 
358
            this.set(SCROLL_X, -(e.clientX - this._moveStartX));
 
359
        }
 
360
    },
 
361
 
 
362
    /**
 
363
     * gestureend event handler
 
364
     *
 
365
     * @method _onGestureMoveEnd
 
366
     * @param e {Event.Facade} The gesturemoveend event facade
 
367
     * @private
 
368
     */
 
369
    _onGestureMoveEnd: function(e) {
 
370
 
 
371
        if (this._preventEnd) {
 
372
            e.preventDefault();
 
373
        }
 
374
 
 
375
        var minY = this._minScrollY,
 
376
            maxY = this._maxScrollY,
 
377
            minX = this._minScrollX,
 
378
            maxX = this._maxScrollX,
 
379
            startPoint = this._scrollsVertical ? this._moveStartClientY : this._moveStartClientX,
 
380
            endPoint = this._scrollsVertical ? this._moveEndClientY : this._moveEndClientX,
 
381
            distance = startPoint - endPoint;
 
382
 
 
383
        this._moveEvt.detach();
 
384
        this._moveEndEvt.detach();
 
385
        
 
386
        /**
 
387
         * Internal state, defines whether or not the scrollview has been scrolled half it's width/height
 
388
         * 
 
389
         * @property _scrolledHalfway
 
390
         * @type boolean
 
391
         * @protected
 
392
         */
 
393
        this._scrolledHalfway = false;
 
394
        this._snapToEdge = false;
 
395
        this._isDragging = false;
 
396
 
 
397
        /**
 
398
         * Contains the distance (postive or negative) in pixels by which the scrollview was last scrolled. This is useful when
 
399
         * setting up click listeners on the scrollview content, which on mouse based devices are always fired, even after a
 
400
         * drag/flick. 
 
401
         * 
 
402
         * <p>Touch based devices don't currently fire a click event, if the finger has been moved (beyond a threshold) so this check isn't required,
 
403
         * if working in a purely touch based environment</p>
 
404
         * 
 
405
         * @property lastScrolledAmt
 
406
         * @type Number
 
407
         * @public
 
408
         */
 
409
        this.lastScrolledAmt = distance;
 
410
 
 
411
        if(this._scrollsHorizontal && Math.abs(distance) > (this.get('width')/2)) {
 
412
            this._scrolledHalfway = true;
 
413
            
 
414
            /**
 
415
             * Internal state, defines whether or not the scrollview has been scrolled in the forward (distance > 0), or backward (distance < 0) direction
 
416
             * 
 
417
             * @property _scrolledForward
 
418
             * @type boolean
 
419
             * @protected
 
420
             */
 
421
            this._scrolledForward = distance > 0;
 
422
        }
 
423
 
 
424
        if(this._scrollsVertical && Math.abs(distance) > (this.get('height')/2)) {
 
425
            this._scrolledHalfway = true;
 
426
            this._scrolledForward = distance > 0;
 
427
        }
 
428
 
 
429
        // Check for minY
 
430
        if(this._scrollsVertical && this.get(SCROLL_Y) < minY) {
 
431
            this._snapToEdge = true;
 
432
            this.set(SCROLL_Y, minY);
 
433
        }
 
434
        
 
435
        // Check for minX
 
436
        if(this._scrollsHorizontal && this.get(SCROLL_X) < minX) {
 
437
            this._snapToEdge = true;
 
438
            this.set(SCROLL_X, minX);
 
439
        }
 
440
        
 
441
        // Check for maxY
 
442
        if(this.get(SCROLL_Y) > maxY) {
 
443
            this._snapToEdge = true;
 
444
            this.set(SCROLL_Y, maxY);
 
445
        }
 
446
        
 
447
        // Check for maxX
 
448
        if(this.get(SCROLL_X) > maxX) {
 
449
            this._snapToEdge = true;
 
450
            this.set(SCROLL_X, maxX);
 
451
        }
 
452
 
 
453
 
 
454
        if(this._snapToEdge) {
 
455
            return;
 
456
        }
 
457
 
 
458
        this.fire(EV_SCROLL_END, {
 
459
            onGestureMoveEnd: true
 
460
        });
 
461
 
 
462
        return;
 
463
    },
 
464
 
 
465
    /**
 
466
     * After listener for changes to the scrollY attribute
 
467
     *
 
468
     * @method _afterScrollYChange
 
469
     * @param e {Event.Facade} The event facade
 
470
     * @protected
 
471
     */
 
472
    _afterScrollYChange : function(e) {
 
473
        if(e.src !== UI) {
 
474
            this._uiScrollY(e.newVal, e.duration, e.easing);
 
475
        }
 
476
    },
 
477
 
 
478
    /**
 
479
     * Update the UI when the scrollY attribute changes
 
480
     *
 
481
     * @method _uiScrollY
 
482
     * @param val {Number} The scrollY value
 
483
     * @param duration {Number} The length (in ms) of the scroll animation
 
484
     * @param easing {String} An easing equation, if duration is defined
 
485
     * @protected
 
486
     */
 
487
    _uiScrollY : function(val, duration, easing) {
 
488
        duration = duration || this._snapToEdge ? 400 : 0;
 
489
        easing = easing || this._snapToEdge ? ScrollView.SNAP_EASING : null;
 
490
 
 
491
        this.scrollTo(null, val, duration, easing);
 
492
    },
 
493
 
 
494
    /**
 
495
     * After listener for changes to the scrollX attribute
 
496
     *
 
497
     * @method _afterScrollXChange
 
498
     * @param e {Event.Facade} The event facade
 
499
     * @protected
 
500
     */
 
501
    _afterScrollXChange : function(e) {
 
502
        if(e.src !== UI) {
 
503
            this._uiScrollX(e.newVal, e.duration, e.easing);
 
504
        }
 
505
    },
 
506
 
 
507
    /**
 
508
     * Update the UI when the scrollX attribute changes
 
509
     *
 
510
     * @method _uiScrollX
 
511
     * @param val {Number} The scrollX value
 
512
     * @param duration {Number} The length (in ms) of the scroll animation
 
513
     * @param easing {String} An easing equation, if duration is defined
 
514
     * @protected
 
515
     */
 
516
    _uiScrollX : function(val, duration, easing) {
 
517
        duration = duration || this._snapToEdge ? 400 : 0;
 
518
        easing = easing || this._snapToEdge ? ScrollView.SNAP_EASING : null;
 
519
 
 
520
        this.scrollTo(val, null, duration, easing);
 
521
    },
 
522
    
 
523
    /**
 
524
     * After listener for the height attribute
 
525
     *
 
526
     * @method _afterHeightChange
 
527
     * @param e {Event.Facade} The event facade
 
528
     * @protected
 
529
     */
 
530
    _afterHeightChange: function() {
 
531
        this._uiDimensionsChange();
 
532
    },
 
533
    
 
534
    /**
 
535
     * After listener for the width attribute
 
536
     *
 
537
     * @method _afterWidthChange
 
538
     * @param e {Event.Facade} The event facade
 
539
     * @protected
 
540
     */
 
541
    _afterWidthChange: function() {
 
542
        this._uiDimensionsChange();
 
543
    },
 
544
    
 
545
    /**
 
546
     * This method gets invoked whenever the height or width attributes change,
 
547
     * allowing us to determine which scrolling axes need to be enabled.
 
548
     *
 
549
     * @method _uiDimensionsChange
 
550
     * @protected
 
551
     */
 
552
    _uiDimensionsChange: function() {
 
553
        var bb = this._bb,
 
554
 
 
555
            height = this.get('height'),
 
556
            width = this.get('width'),
 
557
 
 
558
            // Use bb instead of cb. cb doesn't gives us the right results
 
559
            // in FF (due to overflow:hidden)
 
560
            scrollHeight = bb.get('scrollHeight'),
 
561
            scrollWidth = bb.get('scrollWidth');
 
562
 
 
563
        if(height && scrollHeight > height) {            
 
564
            this._scrollsVertical = true;
 
565
            this._maxScrollY = scrollHeight - height;
 
566
            this._minScrollY = 0;
 
567
            this._scrollHeight = scrollHeight;
 
568
            bb.addClass(ScrollView.CLASS_NAMES.vertical);
 
569
        }
 
570
 
 
571
        if(width && scrollWidth > width) {
 
572
            this._scrollsHorizontal = true;
 
573
            this._maxScrollX = scrollWidth - width;
 
574
            this._minScrollX = 0;
 
575
            this._scrollWidth = scrollWidth;
 
576
            bb.addClass(ScrollView.CLASS_NAMES.horizontal);
 
577
        }
 
578
        
 
579
        /**
 
580
         * Internal state, defines whether or not the scrollview can scroll vertically 
 
581
         * 
 
582
         * @property _scrollsVertical
 
583
         * @type boolean
 
584
         * @protected
 
585
         */
 
586
        
 
587
        /**
 
588
         * Internal state, defines the maximum amount that the scrollview can be scrolled along the Y axis 
 
589
         * 
 
590
         * @property _maxScrollY
 
591
         * @type number
 
592
         * @protected
 
593
         */
 
594
 
 
595
        /**
 
596
         * Internal state, defines the minimum amount that the scrollview can be scrolled along the Y axis 
 
597
         * 
 
598
         * @property _minScrollY
 
599
         * @type number
 
600
         * @protected
 
601
         */
 
602
 
 
603
        /**
 
604
         * Internal state, cached scrollHeight, for performance 
 
605
         * 
 
606
         * @property _scrollHeight
 
607
         * @type number
 
608
         * @protected
 
609
         */
 
610
 
 
611
        /**
 
612
         * Internal state, defines whether or not the scrollview can scroll horizontally 
 
613
         * 
 
614
         * @property _scrollsHorizontal
 
615
         * @type boolean
 
616
         * @protected
 
617
         */
 
618
        
 
619
        /**
 
620
         * Internal state, defines the maximum amount that the scrollview can be scrolled along the X axis 
 
621
         * 
 
622
         * @property _maxScrollX
 
623
         * @type number
 
624
         * @protected
 
625
         */
 
626
 
 
627
        /**
 
628
         * Internal state, defines the minimum amount that the scrollview can be scrolled along the X axis 
 
629
         * 
 
630
         * @property _minScrollX
 
631
         * @type number
 
632
         * @protected
 
633
         */
 
634
 
 
635
        /**
 
636
         * Internal state, cached scrollWidth, for performance 
 
637
         * 
 
638
         * @property _scrollWidth
 
639
         * @type number
 
640
         * @protected
 
641
         */
 
642
    },
 
643
 
 
644
    /**
 
645
     * Execute a flick at the end of a scroll action
 
646
     *
 
647
     * @method _flick
 
648
     * @param distance {Number} The distance (in px) the user scrolled before the flick
 
649
     * @param time {Number} The number of ms the scroll event lasted before the flick
 
650
     * @protected
 
651
     */
 
652
    _flick: function(e) {
 
653
        var flick = e.flick;
 
654
 
 
655
        /**
 
656
         * Internal state, currently calculated velocity from the flick 
 
657
         * 
 
658
         * @property _currentVelocity
 
659
         * @type number
 
660
         * @protected
 
661
         */
 
662
        this._currentVelocity = flick.velocity;
 
663
        this._flicking = true;
 
664
 
 
665
        this._decelCached = this.get('deceleration');
 
666
        this._bounceCached = this.get('bounce');
 
667
 
 
668
        this._pastYEdge = false;
 
669
        this._pastXEdge = false;
 
670
 
 
671
        this._flickFrame();
 
672
 
 
673
        this.fire(EV_SCROLL_FLICK);
 
674
    },
 
675
 
 
676
    /**
 
677
     * Execute a single frame in the flick animation
 
678
     *
 
679
     * @method _flickFrame
 
680
     * @protected
 
681
     */
 
682
    _flickFrame: function() {
 
683
        var newY,
 
684
            maxY,
 
685
            minY,
 
686
            newX,
 
687
            maxX,
 
688
            minX,
 
689
            scrollsVertical  = this._scrollsVertical,
 
690
            scrollsHorizontal = this._scrollsHorizontal,
 
691
            deceleration = this._decelCached,
 
692
            bounce = this._bounceCached,
 
693
            step = ScrollView.FRAME_STEP;
 
694
 
 
695
        if(scrollsVertical) {
 
696
            maxY = this._maxScrollY;
 
697
            minY = this._minScrollY;
 
698
            newY = this.get(SCROLL_Y) - (this._currentVelocity * step);
 
699
        }
 
700
 
 
701
        if(scrollsHorizontal) {
 
702
            maxX = this._maxScrollX;
 
703
            minX = this._minScrollX;
 
704
            newX = this.get(SCROLL_X) - (this._currentVelocity * step);
 
705
        }
 
706
        
 
707
        this._currentVelocity = (this._currentVelocity * deceleration);
 
708
 
 
709
        if(Math.abs(this._currentVelocity).toFixed(4) <= 0.015) {
 
710
            this._flicking = false;
 
711
            this._killTimer(!(this._pastYEdge || this._pastXEdge));
 
712
 
 
713
            if(scrollsVertical) {
 
714
                if(newY < minY) {
 
715
                    this._snapToEdge = true;
 
716
                    this.set(SCROLL_Y, minY);
 
717
                } else if(newY > maxY) {
 
718
                    this._snapToEdge = true;
 
719
                    this.set(SCROLL_Y, maxY);
 
720
                }
 
721
            }
 
722
            
 
723
            if(scrollsHorizontal) {
 
724
                if(newX < minX) {
 
725
                    this._snapToEdge = true;
 
726
                    this.set(SCROLL_X, minX);
 
727
                } else if(newX > maxX) {
 
728
                    this._snapToEdge = true;
 
729
                    this.set(SCROLL_X, maxX);
 
730
                }
 
731
            }
 
732
 
 
733
            return;
 
734
        }
 
735
 
 
736
        if (scrollsVertical) {
 
737
            if (newY < minY || newY > maxY) {
 
738
                this._pastYEdge = true;
 
739
                this._currentVelocity *= bounce;
 
740
            }
 
741
 
 
742
            this.set(SCROLL_Y, newY);
 
743
        }
 
744
 
 
745
        if (scrollsHorizontal) {
 
746
            if (newX < minX || newX > maxX) {
 
747
                this._pastXEdge = true;
 
748
                this._currentVelocity *= bounce;
 
749
            }
 
750
 
 
751
            this.set(SCROLL_X, newX);
 
752
        }
 
753
 
 
754
        if (!this._flickTimer) {
 
755
            this._flickTimer = Y.later(step, this, '_flickFrame', null, true);
 
756
        }
 
757
    },
 
758
 
 
759
    /**
 
760
     * Stop the animation timer
 
761
     *
 
762
     * @method _killTimer
 
763
     * @param fireEvent {Boolean} If true, fire the scrollEnd event
 
764
     * @protected
 
765
     */
 
766
    _killTimer: function(fireEvent) {
 
767
        if(this._flickTimer) {
 
768
            this._flickTimer.cancel();
 
769
            this._flickTimer = null;
 
770
        }
 
771
 
 
772
        if(fireEvent) {
 
773
            this.fire(EV_SCROLL_END);
 
774
        }
 
775
    },
 
776
 
 
777
    /**
 
778
     * The scrollX, scrollY setter implementation
 
779
     * 
 
780
     * @method _setScroll
 
781
     * @private
 
782
     * @param {Number} val
 
783
     * @param {String} dim
 
784
     * 
 
785
     * @return {Number} The constrained value, if it exceeds min/max range
 
786
     */
 
787
    _setScroll : function(val, dim) {
 
788
        var bouncing = this._cachedBounce || this.get(BOUNCE),
 
789
            range = ScrollView.BOUNCE_RANGE,
 
790
 
 
791
            maxScroll = (dim == DIM_X) ? this._maxScrollX : this._maxScrollY,
 
792
 
 
793
            min = bouncing ? -range : 0,
 
794
            max = bouncing ? maxScroll + range : maxScroll;
 
795
 
 
796
        if(!bouncing || !this._isDragging) {
 
797
            if(val < min) {
 
798
                val = min;
 
799
            } else if(val > max) {
 
800
                val = max;
 
801
            }            
 
802
        }
 
803
 
 
804
        return val;
 
805
    },
 
806
 
 
807
    /**
 
808
     * Setter for the scrollX attribute
 
809
     *
 
810
     * @method _setScrollX
 
811
     * @param val {Number} The new scrollX value
 
812
     * @return {Number} The normalized value
 
813
     * @protected
 
814
     */    
 
815
    _setScrollX: function(val) {
 
816
        return this._setScroll(val, DIM_X);
 
817
    },
 
818
 
 
819
    /**
 
820
     * Setter for the scrollY ATTR
 
821
     *
 
822
     * @method _setScrollY
 
823
     * @param val {Number} The new scrollY value
 
824
     * @return {Number} The normalized value 
 
825
     * @protected
 
826
     */
 
827
    _setScrollY: function(val) {
 
828
        return this._setScroll(val, DIM_Y);
 
829
    }
 
830
    
 
831
}, {
 
832
   
 
833
   // Y.ScrollView static properties
 
834
 
 
835
   /**
 
836
    * The identity of the widget.
 
837
    *
 
838
    * @property ScrollView.NAME
 
839
    * @type String
 
840
    * @default 'scrollview'
 
841
    * @readOnly
 
842
    * @protected
 
843
    * @static
 
844
    */
 
845
   NAME: 'scrollview',
 
846
 
 
847
   /**
 
848
    * Static property used to define the default attribute configuration of
 
849
    * the Widget.
 
850
    *
 
851
    * @property ScrollView.ATTRS
 
852
    * @type {Object}
 
853
    * @protected
 
854
    * @static
 
855
    */
 
856
    ATTRS: {
 
857
 
 
858
        /**
 
859
         * The scroll position in the y-axis
 
860
         *
 
861
         * @attribute scrollY
 
862
         * @type Number
 
863
         * @default 0
 
864
         */
 
865
        scrollY: {
 
866
            value: 0,
 
867
            setter: '_setScrollY'
 
868
        },
 
869
 
 
870
        /**
 
871
         * The scroll position in the x-axis
 
872
         *
 
873
         * @attribute scrollX
 
874
         * @type Number
 
875
         * @default 0
 
876
         */
 
877
        scrollX: {
 
878
            value: 0,
 
879
            setter: '_setScrollX'
 
880
        },
 
881
 
 
882
        /**
 
883
         * Drag coefficent for inertial scrolling. The closer to 1 this
 
884
         * value is, the less friction during scrolling.
 
885
         *
 
886
         * @attribute deceleration
 
887
         * @default 0.93
 
888
         */
 
889
        deceleration: {
 
890
            value: 0.93
 
891
        },
 
892
 
 
893
        /**
 
894
         * Drag coefficient for intertial scrolling at the upper
 
895
         * and lower boundaries of the scrollview. Set to 0 to 
 
896
         * disable "rubber-banding".
 
897
         *
 
898
         * @attribute bounce
 
899
         * @type Number
 
900
         * @default 0.1
 
901
         */
 
902
        bounce: {
 
903
            value: 0.1
 
904
        },
 
905
 
 
906
        /**
 
907
         * The minimum distance and/or velocity which define a flick
 
908
         *
 
909
         * @attribute flick
 
910
         * @type Object
 
911
         * @default Object with properties minDistance = 10, minVelocity = 0.3.
 
912
         */
 
913
        flick: {
 
914
            value: {
 
915
                minDistance: 10,
 
916
                minVelocity: 0.3
 
917
            }
 
918
        }
 
919
    },
 
920
 
 
921
    /**
 
922
     * List of class names used in the scrollview's DOM
 
923
     *
 
924
     * @property ScrollView.CLASS_NAMES
 
925
     * @type Object
 
926
     * @static
 
927
     */
 
928
    CLASS_NAMES: CLASS_NAMES,
 
929
 
 
930
    /**
 
931
     * Flag used to source property changes initiated from the DOM
 
932
     *
 
933
     * @property ScrollView.UI_SRC
 
934
     * @type String
 
935
     * @static
 
936
     * @default "ui"
 
937
     */
 
938
    UI_SRC: UI,
 
939
 
 
940
    /**
 
941
     * The default bounce distance in pixels
 
942
     *
 
943
     * @property ScrollView.BOUNCE_RANGE
 
944
     * @type Number
 
945
     * @static
 
946
     * @default 150
 
947
     */
 
948
    BOUNCE_RANGE : 150,
 
949
 
 
950
    /**
 
951
     * The interval used when animating the flick
 
952
     *
 
953
     * @property ScrollView.FRAME_STEP
 
954
     * @type Number
 
955
     * @static
 
956
     * @default 30
 
957
     */
 
958
    FRAME_STEP : 30,
 
959
 
 
960
    /**
 
961
     * The default easing used when animating the flick
 
962
     *
 
963
     * @property ScrollView.EASING
 
964
     * @type String
 
965
     * @static
 
966
     * @default 'cubic-bezier(0, 0.1, 0, 1.0)'
 
967
     */
 
968
    EASING : 'cubic-bezier(0, 0.1, 0, 1.0)',
 
969
 
 
970
    /**
 
971
     * The default easing to use when animatiing the bounce snap back.
 
972
     *
 
973
     * @property ScrollView.SNAP_EASING
 
974
     * @type String
 
975
     * @static
 
976
     * @default 'ease-out'
 
977
     */
 
978
    SNAP_EASING : 'ease-out',
 
979
 
 
980
    /**
 
981
     * Style property name to use to set transition duration. Currently Webkit specific (WebkitTransitionDuration)
 
982
     * 
 
983
     * @property ScrollView._TRANSITION_DURATION
 
984
     * @private
 
985
     */
 
986
    _TRANSITION_DURATION : "WebkitTransitionDuration",
 
987
 
 
988
    /**
 
989
     * Style property name to use to set transition property. Currently, Webkit specific (WebkitTransitionProperty)
 
990
     *
 
991
     * @property ScrollView._TRANSITION_PROPERTY
 
992
     * @private
 
993
     */
 
994
    _TRANSITION_PROPERTY : "WebkitTransitionProperty"
 
995
});
 
996
 
 
997
 
 
998
}, '3.2.0' ,{skinnable:true, requires:['widget', 'event-gestures', 'transition']});