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