~launchpad-pqm/lazr-js/toolchain

« back to all changes in this revision

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

  • Committer: Sidnei da Silva
  • Date: 2009-11-16 00:51:29 UTC
  • mto: This revision was merged to the branch mainline in revision 154.
  • Revision ID: sidnei.da.silva@canonical.com-20091116005129-8ibwjlboa38glaw5
- Improved generation of skin modules and revamped combo service to make it more twisty.

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']});