[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/scrollview-base/ -> scrollview-base-debug.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1