[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/gesture-simulate/ -> gesture-simulate.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('gesture-simulate', function (Y, NAME) {
   9  
  10  /**
  11   * Simulate high-level user gestures by generating a set of native DOM events.
  12   *
  13   * @module gesture-simulate
  14   * @requires event-simulate, async-queue, node-screen
  15   */
  16  
  17  var NAME = "gesture-simulate",
  18  
  19      // phantomjs check may be temporary, until we determine if it really support touch all the way through, like it claims to (http://code.google.com/p/phantomjs/issues/detail?id=375)
  20      SUPPORTS_TOUCH = ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.phantomjs) && !(Y.UA.chrome && Y.UA.chrome < 6)),
  21  
  22      gestureNames = {
  23          tap: 1,
  24          doubletap: 1,
  25          press: 1,
  26          move: 1,
  27          flick: 1,
  28          pinch: 1,
  29          rotate: 1
  30      },
  31  
  32      touchEvents = {
  33          touchstart: 1,
  34          touchmove: 1,
  35          touchend: 1,
  36          touchcancel: 1
  37      },
  38  
  39      document = Y.config.doc,
  40      emptyTouchList,
  41  
  42      EVENT_INTERVAL = 20,        // 20ms
  43      START_PAGEX,                // will be adjusted to the node element center
  44      START_PAGEY,                // will be adjusted to the node element center
  45  
  46      // defaults that user can override.
  47      DEFAULTS = {
  48          // tap gestures
  49          HOLD_TAP: 10,           // 10ms
  50          DELAY_TAP: 10,          // 10ms
  51  
  52          // press gesture
  53          HOLD_PRESS: 3000,       // 3sec
  54          MIN_HOLD_PRESS: 1000,   // 1sec
  55          MAX_HOLD_PRESS: 60000,  // 1min
  56  
  57          // move gesture
  58          DISTANCE_MOVE: 200,     // 200 pixels
  59          DURATION_MOVE: 1000,    // 1sec
  60          MAX_DURATION_MOVE: 5000,// 5sec
  61  
  62          // flick gesture
  63          MIN_VELOCITY_FLICK: 1.3,
  64          DISTANCE_FLICK: 200,     // 200 pixels
  65          DURATION_FLICK: 1000,    // 1sec
  66          MAX_DURATION_FLICK: 5000,// 5sec
  67  
  68          // pinch/rotation
  69          DURATION_PINCH: 1000     // 1sec
  70      },
  71  
  72      TOUCH_START = 'touchstart',
  73      TOUCH_MOVE = 'touchmove',
  74      TOUCH_END = 'touchend',
  75  
  76      GESTURE_START = 'gesturestart',
  77      GESTURE_CHANGE = 'gesturechange',
  78      GESTURE_END = 'gestureend',
  79  
  80      MOUSE_UP    = 'mouseup',
  81      MOUSE_MOVE  = 'mousemove',
  82      MOUSE_DOWN  = 'mousedown',
  83      MOUSE_CLICK = 'click',
  84      MOUSE_DBLCLICK = 'dblclick',
  85  
  86      X_AXIS = 'x',
  87      Y_AXIS = 'y';
  88  
  89  
  90  function Simulations(node) {
  91      if(!node) {
  92          Y.error(NAME+': invalid target node');
  93      }
  94      this.node = node;
  95      this.target = Y.Node.getDOMNode(node);
  96  
  97      var startXY = this.node.getXY(),
  98          dims = this._getDims();
  99  
 100      START_PAGEX = startXY[0] + (dims[0])/2;
 101      START_PAGEY = startXY[1] + (dims[1])/2;
 102  }
 103  
 104  Simulations.prototype = {
 105  
 106      /**
 107       * Helper method to convert a degree to a radian.
 108       *
 109       * @method _toRadian
 110       * @private
 111       * @param {Number} deg A degree to be converted to a radian.
 112       * @return {Number} The degree in radian.
 113       */
 114      _toRadian: function(deg) {
 115          return deg * (Math.PI/180);
 116      },
 117  
 118      /**
 119       * Helper method to get height/width while accounting for
 120       * rotation/scale transforms where possible by using the
 121       * bounding client rectangle height/width instead of the
 122       * offsetWidth/Height which region uses.
 123       * @method _getDims
 124       * @private
 125       * @return {Array} Array with [height, width]
 126       */
 127      _getDims : function() {
 128          var region,
 129              width,
 130              height;
 131  
 132          // Ideally, this should be in DOM somewhere.
 133          if (this.target.getBoundingClientRect) {
 134              region = this.target.getBoundingClientRect();
 135  
 136              if ("height" in region) {
 137                  height = region.height;
 138              } else {
 139                  // IE7,8 has getBCR, but no height.
 140                  height = Math.abs(region.bottom - region.top);
 141              }
 142  
 143              if ("width" in region) {
 144                  width = region.width;
 145              } else {
 146                  // IE7,8 has getBCR, but no width.
 147                  width = Math.abs(region.right - region.left);
 148              }
 149          } else {
 150              region = this.node.get("region");
 151              width = region.width;
 152              height = region.height;
 153          }
 154  
 155          return [width, height];
 156      },
 157  
 158      /**
 159       * Helper method to convert a point relative to the node element into
 160       * the point in the page coordination.
 161       *
 162       * @method _calculateDefaultPoint
 163       * @private
 164       * @param {Array} point A point relative to the node element.
 165       * @return {Array} The same point in the page coordination.
 166       */
 167      _calculateDefaultPoint: function(point) {
 168  
 169          var height;
 170  
 171          if(!Y.Lang.isArray(point) || point.length === 0) {
 172              point = [START_PAGEX, START_PAGEY];
 173          } else {
 174              if(point.length == 1) {
 175                  height = this._getDims[1];
 176                  point[1] = height/2;
 177              }
 178              // convert to page(viewport) coordination
 179              point[0] = this.node.getX() + point[0];
 180              point[1] = this.node.getY() + point[1];
 181          }
 182  
 183          return point;
 184      },
 185  
 186      /**
 187       * The "rotate" and "pinch" methods are essencially same with the exact same
 188       * arguments. Only difference is the required parameters. The rotate method
 189       * requires "rotation" parameter while the pinch method requires "startRadius"
 190       * and "endRadius" parameters.
 191       *
 192       * @method rotate
 193       * @param {Function} cb The callback to execute when the gesture simulation
 194       *      is completed.
 195       * @param {Array} center A center point where the pinch gesture of two fingers
 196       *      should happen. It is relative to the top left corner of the target
 197       *      node element.
 198       * @param {Number} startRadius A radius of start circle where 2 fingers are
 199       *      on when the gesture starts. This is optional. The default is a fourth of
 200       *      either target node width or height whichever is smaller.
 201       * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
 202       *      the pinch or spread gestures are completed. This is optional.
 203       *      The default is a fourth of either target node width or height whichever is less.
 204       * @param {Number} duration A duration of the gesture in millisecond.
 205       * @param {Number} start A start angle(0 degree at 12 o'clock) where the
 206       *      gesture should start. Default is 0.
 207       * @param {Number} rotation A rotation in degree. It is required.
 208       */
 209      rotate: function(cb, center, startRadius, endRadius, duration, start, rotation) {
 210          var radius,
 211              r1 = startRadius,   // optional
 212              r2 = endRadius;     // optional
 213  
 214          if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
 215              radius = (this.target.offsetWidth < this.target.offsetHeight)?
 216                  this.target.offsetWidth/4 : this.target.offsetHeight/4;
 217              r1 = radius;
 218              r2 = radius;
 219          }
 220  
 221          // required
 222          if(!Y.Lang.isNumber(rotation)) {
 223              Y.error(NAME+'Invalid rotation detected.');
 224          }
 225  
 226          this.pinch(cb, center, r1, r2, duration, start, rotation);
 227      },
 228  
 229      /**
 230       * The "rotate" and "pinch" methods are essencially same with the exact same
 231       * arguments. Only difference is the required parameters. The rotate method
 232       * requires "rotation" parameter while the pinch method requires "startRadius"
 233       * and "endRadius" parameters.
 234       *
 235       * The "pinch" gesture can simulate various 2 finger gestures such as pinch,
 236       * spread and/or rotation. The "startRadius" and "endRadius" are required.
 237       * If endRadius is larger than startRadius, it becomes a spread gesture
 238       * otherwise a pinch gesture.
 239       *
 240       * @method pinch
 241       * @param {Function} cb The callback to execute when the gesture simulation
 242       *      is completed.
 243       * @param {Array} center A center point where the pinch gesture of two fingers
 244       *      should happen. It is relative to the top left corner of the target
 245       *      node element.
 246       * @param {Number} startRadius A radius of start circle where 2 fingers are
 247       *      on when the gesture starts. This paramenter is required.
 248       * @param {Number} endRadius A radius of end circle where 2 fingers will be on when
 249       *      the pinch or spread gestures are completed. This parameter is required.
 250       * @param {Number} duration A duration of the gesture in millisecond.
 251       * @param {Number} start A start angle(0 degree at 12 o'clock) where the
 252       *      gesture should start. Default is 0.
 253       * @param {Number} rotation If rotation is desired during the pinch or
 254       *      spread gestures, this parameter can be used. Default is 0 degree.
 255       */
 256      pinch: function(cb, center, startRadius, endRadius, duration, start, rotation) {
 257          var eventQueue,
 258              i,
 259              interval = EVENT_INTERVAL,
 260              touches,
 261              id = 0,
 262              r1 = startRadius,   // required
 263              r2 = endRadius,     // required
 264              radiusPerStep,
 265              centerX, centerY,
 266              startScale, endScale, scalePerStep,
 267              startRot, endRot, rotPerStep,
 268              path1 = {start: [], end: []}, // paths for 1st and 2nd fingers.
 269              path2 = {start: [], end: []},
 270              steps,
 271              touchMove;
 272  
 273          center = this._calculateDefaultPoint(center);
 274  
 275          if(!Y.Lang.isNumber(r1) || !Y.Lang.isNumber(r2) || r1<0 || r2<0) {
 276              Y.error(NAME+'Invalid startRadius and endRadius detected.');
 277          }
 278  
 279          if(!Y.Lang.isNumber(duration) || duration <= 0) {
 280              duration = DEFAULTS.DURATION_PINCH;
 281          }
 282  
 283          if(!Y.Lang.isNumber(start)) {
 284              start = 0.0;
 285          } else {
 286              start = start%360;
 287              while(start < 0) {
 288                  start += 360;
 289              }
 290          }
 291  
 292          if(!Y.Lang.isNumber(rotation)) {
 293              rotation = 0.0;
 294          }
 295  
 296          Y.AsyncQueue.defaults.timeout = interval;
 297          eventQueue = new Y.AsyncQueue();
 298  
 299          // range determination
 300          centerX = center[0];
 301          centerY = center[1];
 302  
 303          startRot = start;
 304          endRot = start + rotation;
 305  
 306          // 1st finger path
 307          path1.start = [
 308              centerX + r1*Math.sin(this._toRadian(startRot)),
 309              centerY - r1*Math.cos(this._toRadian(startRot))
 310          ];
 311          path1.end   = [
 312              centerX + r2*Math.sin(this._toRadian(endRot)),
 313              centerY - r2*Math.cos(this._toRadian(endRot))
 314          ];
 315  
 316          // 2nd finger path
 317          path2.start = [
 318              centerX - r1*Math.sin(this._toRadian(startRot)),
 319              centerY + r1*Math.cos(this._toRadian(startRot))
 320          ];
 321          path2.end   = [
 322              centerX - r2*Math.sin(this._toRadian(endRot)),
 323              centerY + r2*Math.cos(this._toRadian(endRot))
 324          ];
 325  
 326          startScale = 1.0;
 327          endScale = endRadius/startRadius;
 328  
 329          // touch/gesture start
 330          eventQueue.add({
 331              fn: function() {
 332                  var coord1, coord2, coord, touches;
 333  
 334                  // coordinate for each touch object.
 335                  coord1 = {
 336                      pageX: path1.start[0],
 337                      pageY: path1.start[1],
 338                      clientX: path1.start[0],
 339                      clientY: path1.start[1]
 340                  };
 341                  coord2 = {
 342                      pageX: path2.start[0],
 343                      pageY: path2.start[1],
 344                      clientX: path2.start[0],
 345                      clientY: path2.start[1]
 346                  };
 347                  touches = this._createTouchList([Y.merge({
 348                      identifier: (id++)
 349                  }, coord1), Y.merge({
 350                      identifier: (id++)
 351                  }, coord2)]);
 352  
 353                  // coordinate for top level event
 354                  coord = {
 355                      pageX: (path1.start[0] + path2.start[0])/2,
 356                      pageY: (path1.start[0] + path2.start[1])/2,
 357                      clientX: (path1.start[0] + path2.start[0])/2,
 358                      clientY: (path1.start[0] + path2.start[1])/2
 359                  };
 360  
 361                  this._simulateEvent(this.target, TOUCH_START, Y.merge({
 362                      touches: touches,
 363                      targetTouches: touches,
 364                      changedTouches: touches,
 365                      scale: startScale,
 366                      rotation: startRot
 367                  }, coord));
 368  
 369                  if(Y.UA.ios >= 2.0) {
 370                      /* gesture starts when the 2nd finger touch starts.
 371                      * The implementation will fire 1 touch start event for both fingers,
 372                      * simulating 2 fingers touched on the screen at the same time.
 373                      */
 374                      this._simulateEvent(this.target, GESTURE_START, Y.merge({
 375                          scale: startScale,
 376                          rotation: startRot
 377                      }, coord));
 378                  }
 379              },
 380              timeout: 0,
 381              context: this
 382          });
 383  
 384          // gesture change
 385          steps = Math.floor(duration/interval);
 386          radiusPerStep = (r2 - r1)/steps;
 387          scalePerStep = (endScale - startScale)/steps;
 388          rotPerStep = (endRot - startRot)/steps;
 389  
 390          touchMove = function(step) {
 391              var radius = r1 + (radiusPerStep)*step,
 392                  px1 = centerX + radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
 393                  py1 = centerY - radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
 394                  px2 = centerX - radius*Math.sin(this._toRadian(startRot + rotPerStep*step)),
 395                  py2 = centerY + radius*Math.cos(this._toRadian(startRot + rotPerStep*step)),
 396                  px = (px1+px2)/2,
 397                  py = (py1+py2)/2,
 398                  coord1, coord2, coord, touches;
 399  
 400              // coordinate for each touch object.
 401              coord1 = {
 402                  pageX: px1,
 403                  pageY: py1,
 404                  clientX: px1,
 405                  clientY: py1
 406              };
 407              coord2 = {
 408                  pageX: px2,
 409                  pageY: py2,
 410                  clientX: px2,
 411                  clientY: py2
 412              };
 413              touches = this._createTouchList([Y.merge({
 414                  identifier: (id++)
 415              }, coord1), Y.merge({
 416                  identifier: (id++)
 417              }, coord2)]);
 418  
 419              // coordinate for top level event
 420              coord = {
 421                  pageX: px,
 422                  pageY: py,
 423                  clientX: px,
 424                  clientY: py
 425              };
 426  
 427              this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
 428                  touches: touches,
 429                  targetTouches: touches,
 430                  changedTouches: touches,
 431                  scale: startScale + scalePerStep*step,
 432                  rotation: startRot + rotPerStep*step
 433              }, coord));
 434  
 435              if(Y.UA.ios >= 2.0) {
 436                  this._simulateEvent(this.target, GESTURE_CHANGE, Y.merge({
 437                      scale: startScale + scalePerStep*step,
 438                      rotation: startRot + rotPerStep*step
 439                  }, coord));
 440              }
 441          };
 442  
 443          for (i=0; i < steps; i++) {
 444              eventQueue.add({
 445                  fn: touchMove,
 446                  args: [i],
 447                  context: this
 448              });
 449          }
 450  
 451          // gesture end
 452          eventQueue.add({
 453              fn: function() {
 454                  var emptyTouchList = this._getEmptyTouchList(),
 455                      coord1, coord2, coord, touches;
 456  
 457                  // coordinate for each touch object.
 458                  coord1 = {
 459                      pageX: path1.end[0],
 460                      pageY: path1.end[1],
 461                      clientX: path1.end[0],
 462                      clientY: path1.end[1]
 463                  };
 464                  coord2 = {
 465                      pageX: path2.end[0],
 466                      pageY: path2.end[1],
 467                      clientX: path2.end[0],
 468                      clientY: path2.end[1]
 469                  };
 470                  touches = this._createTouchList([Y.merge({
 471                      identifier: (id++)
 472                  }, coord1), Y.merge({
 473                      identifier: (id++)
 474                  }, coord2)]);
 475  
 476                  // coordinate for top level event
 477                  coord = {
 478                      pageX: (path1.end[0] + path2.end[0])/2,
 479                      pageY: (path1.end[0] + path2.end[1])/2,
 480                      clientX: (path1.end[0] + path2.end[0])/2,
 481                      clientY: (path1.end[0] + path2.end[1])/2
 482                  };
 483  
 484                  if(Y.UA.ios >= 2.0) {
 485                      this._simulateEvent(this.target, GESTURE_END, Y.merge({
 486                          scale: endScale,
 487                          rotation: endRot
 488                      }, coord));
 489                  }
 490  
 491                  this._simulateEvent(this.target, TOUCH_END, Y.merge({
 492                      touches: emptyTouchList,
 493                      targetTouches: emptyTouchList,
 494                      changedTouches: touches,
 495                      scale: endScale,
 496                      rotation: endRot
 497                  }, coord));
 498              },
 499              context: this
 500          });
 501  
 502          if(cb && Y.Lang.isFunction(cb)) {
 503              eventQueue.add({
 504                  fn: cb,
 505  
 506                  // by default, the callback runs the node context where
 507                  // simulateGesture method is called.
 508                  context: this.node
 509  
 510                  //TODO: Use args to pass error object as 1st param if there is an error.
 511                  //args:
 512              });
 513          }
 514  
 515          eventQueue.run();
 516      },
 517  
 518      /**
 519       * The "tap" gesture can be used for various single touch point gestures
 520       * such as single tap, N number of taps, long press. The default is a single
 521       * tap.
 522       *
 523       * @method tap
 524       * @param {Function} cb The callback to execute when the gesture simulation
 525       *      is completed.
 526       * @param {Array} point A point(relative to the top left corner of the
 527       *      target node element) where the tap gesture should start. The default
 528       *      is the center of the taget node.
 529       * @param {Number} times The number of taps. Default is 1.
 530       * @param {Number} hold The hold time in milliseconds between "touchstart" and
 531       *      "touchend" event generation. Default is 10ms.
 532       * @param {Number} delay The time gap in millisecond between taps if this
 533       *      gesture has more than 1 tap. Default is 10ms.
 534       */
 535      tap: function(cb, point, times, hold, delay) {
 536          var eventQueue = new Y.AsyncQueue(),
 537              emptyTouchList = this._getEmptyTouchList(),
 538              touches,
 539              coord,
 540              i,
 541              touchStart,
 542              touchEnd;
 543  
 544          point = this._calculateDefaultPoint(point);
 545  
 546          if(!Y.Lang.isNumber(times) || times < 1) {
 547              times = 1;
 548          }
 549  
 550          if(!Y.Lang.isNumber(hold)) {
 551              hold = DEFAULTS.HOLD_TAP;
 552          }
 553  
 554          if(!Y.Lang.isNumber(delay)) {
 555              delay = DEFAULTS.DELAY_TAP;
 556          }
 557  
 558          coord = {
 559              pageX: point[0],
 560              pageY: point[1],
 561              clientX: point[0],
 562              clientY: point[1]
 563          };
 564  
 565          touches = this._createTouchList([Y.merge({identifier: 0}, coord)]);
 566  
 567          touchStart = function() {
 568              this._simulateEvent(this.target, TOUCH_START, Y.merge({
 569                  touches: touches,
 570                  targetTouches: touches,
 571                  changedTouches: touches
 572              }, coord));
 573          };
 574  
 575          touchEnd = function() {
 576              this._simulateEvent(this.target, TOUCH_END, Y.merge({
 577                  touches: emptyTouchList,
 578                  targetTouches: emptyTouchList,
 579                  changedTouches: touches
 580              }, coord));
 581          };
 582  
 583          for (i=0; i < times; i++) {
 584              eventQueue.add({
 585                  fn: touchStart,
 586                  context: this,
 587                  timeout: (i === 0)? 0 : delay
 588              });
 589  
 590              eventQueue.add({
 591                  fn: touchEnd,
 592                  context: this,
 593                  timeout: hold
 594              });
 595          }
 596  
 597          if(times > 1 && !SUPPORTS_TOUCH) {
 598              eventQueue.add({
 599                  fn: function() {
 600                      this._simulateEvent(this.target, MOUSE_DBLCLICK, coord);
 601                  },
 602                  context: this
 603              });
 604          }
 605  
 606          if(cb && Y.Lang.isFunction(cb)) {
 607              eventQueue.add({
 608                  fn: cb,
 609  
 610                  // by default, the callback runs the node context where
 611                  // simulateGesture method is called.
 612                  context: this.node
 613  
 614                  //TODO: Use args to pass error object as 1st param if there is an error.
 615                  //args:
 616              });
 617          }
 618  
 619          eventQueue.run();
 620      },
 621  
 622      /**
 623       * The "flick" gesture is a specialized "move" that has some velocity
 624       * and the movement always runs either x or y axis. The velocity is calculated
 625       * with "distance" and "duration" arguments. If the calculated velocity is
 626       * below than the minimum velocity, the given duration will be ignored and
 627       * new duration will be created to make a valid flick gesture.
 628       *
 629       * @method flick
 630       * @param {Function} cb The callback to execute when the gesture simulation
 631       *      is completed.
 632       * @param {Array} point A point(relative to the top left corner of the
 633       *      target node element) where the flick gesture should start. The default
 634       *      is the center of the taget node.
 635       * @param {String} axis Either "x" or "y".
 636       * @param {Number} distance A distance in pixels to flick.
 637       * @param {Number} duration A duration of the gesture in millisecond.
 638       *
 639       */
 640      flick: function(cb, point, axis, distance, duration) {
 641          var path;
 642  
 643          point = this._calculateDefaultPoint(point);
 644  
 645          if(!Y.Lang.isString(axis)) {
 646              axis = X_AXIS;
 647          } else {
 648              axis = axis.toLowerCase();
 649              if(axis !== X_AXIS && axis !== Y_AXIS) {
 650                  Y.error(NAME+'(flick): Only x or y axis allowed');
 651              }
 652          }
 653  
 654          if(!Y.Lang.isNumber(distance)) {
 655              distance = DEFAULTS.DISTANCE_FLICK;
 656          }
 657  
 658          if(!Y.Lang.isNumber(duration)){
 659              duration = DEFAULTS.DURATION_FLICK; // ms
 660          } else {
 661              if(duration > DEFAULTS.MAX_DURATION_FLICK) {
 662                  duration = DEFAULTS.MAX_DURATION_FLICK;
 663              }
 664          }
 665  
 666          /*
 667           * Check if too slow for a flick.
 668           * Adjust duration if the calculated velocity is less than
 669           * the minimum velcocity to be claimed as a flick.
 670           */
 671          if(Math.abs(distance)/duration < DEFAULTS.MIN_VELOCITY_FLICK) {
 672              duration = Math.abs(distance)/DEFAULTS.MIN_VELOCITY_FLICK;
 673          }
 674  
 675          path = {
 676              start: Y.clone(point),
 677              end: [
 678                  (axis === X_AXIS) ? point[0]+distance : point[0],
 679                  (axis === Y_AXIS) ? point[1]+distance : point[1]
 680              ]
 681          };
 682  
 683          this._move(cb, path, duration);
 684      },
 685  
 686      /**
 687       * The "move" gesture simulate the movement of any direction between
 688       * the straight line of start and end point for the given duration.
 689       * The path argument is an object with "point", "xdist" and "ydist" properties.
 690       * The "point" property is an array with x and y coordinations(relative to the
 691       * top left corner of the target node element) while "xdist" and "ydist"
 692       * properties are used for the distance along the x and y axis. A negative
 693       * distance number can be used to drag either left or up direction.
 694       *
 695       * If no arguments are given, it will simulate the default move, which
 696       * is moving 200 pixels from the center of the element to the positive X-axis
 697       * direction for 1 sec.
 698       *
 699       * @method move
 700       * @param {Function} cb The callback to execute when the gesture simulation
 701       *      is completed.
 702       * @param {Object} path An object with "point", "xdist" and "ydist".
 703       * @param {Number} duration A duration of the gesture in millisecond.
 704       */
 705      move: function(cb, path, duration) {
 706          var convertedPath;
 707  
 708          if(!Y.Lang.isObject(path)) {
 709              path = {
 710                  point: this._calculateDefaultPoint([]),
 711                  xdist: DEFAULTS.DISTANCE_MOVE,
 712                  ydist: 0
 713              };
 714          } else {
 715              // convert to the page coordination
 716              if(!Y.Lang.isArray(path.point)) {
 717                  path.point = this._calculateDefaultPoint([]);
 718              } else {
 719                  path.point = this._calculateDefaultPoint(path.point);
 720              }
 721  
 722              if(!Y.Lang.isNumber(path.xdist)) {
 723                  path.xdist = DEFAULTS.DISTANCE_MOVE;
 724              }
 725  
 726              if(!Y.Lang.isNumber(path.ydist)) {
 727                  path.ydist = 0;
 728              }
 729          }
 730  
 731          if(!Y.Lang.isNumber(duration)){
 732              duration = DEFAULTS.DURATION_MOVE; // ms
 733          } else {
 734              if(duration > DEFAULTS.MAX_DURATION_MOVE) {
 735                  duration = DEFAULTS.MAX_DURATION_MOVE;
 736              }
 737          }
 738  
 739          convertedPath = {
 740              start: Y.clone(path.point),
 741              end: [path.point[0]+path.xdist, path.point[1]+path.ydist]
 742          };
 743  
 744          this._move(cb, convertedPath, duration);
 745      },
 746  
 747      /**
 748       * A base method on top of "move" and "flick" methods. The method takes
 749       * the path with start/end properties and duration to generate a set of
 750       * touch events for the movement gesture.
 751       *
 752       * @method _move
 753       * @private
 754       * @param {Function} cb The callback to execute when the gesture simulation
 755       *      is completed.
 756       * @param {Object} path An object with "start" and "end" properties. Each
 757       *      property should be an array with x and y coordination (e.g. start: [100, 50])
 758       * @param {Number} duration A duration of the gesture in millisecond.
 759       */
 760      _move: function(cb, path, duration) {
 761          var eventQueue,
 762              i,
 763              interval = EVENT_INTERVAL,
 764              steps, stepX, stepY,
 765              id = 0,
 766              touchMove;
 767  
 768          if(!Y.Lang.isNumber(duration)){
 769              duration = DEFAULTS.DURATION_MOVE; // ms
 770          } else {
 771              if(duration > DEFAULTS.MAX_DURATION_MOVE) {
 772                  duration = DEFAULTS.MAX_DURATION_MOVE;
 773              }
 774          }
 775  
 776          if(!Y.Lang.isObject(path)) {
 777              path = {
 778                  start: [
 779                      START_PAGEX,
 780                      START_PAGEY
 781                  ],
 782                  end: [
 783                      START_PAGEX + DEFAULTS.DISTANCE_MOVE,
 784                      START_PAGEY
 785                  ]
 786              };
 787          } else {
 788              if(!Y.Lang.isArray(path.start)) {
 789                  path.start = [
 790                      START_PAGEX,
 791                      START_PAGEY
 792                  ];
 793              }
 794              if(!Y.Lang.isArray(path.end)) {
 795                  path.end = [
 796                      START_PAGEX + DEFAULTS.DISTANCE_MOVE,
 797                      START_PAGEY
 798                  ];
 799              }
 800          }
 801  
 802          Y.AsyncQueue.defaults.timeout = interval;
 803          eventQueue = new Y.AsyncQueue();
 804  
 805          // start
 806          eventQueue.add({
 807              fn: function() {
 808                  var coord = {
 809                          pageX: path.start[0],
 810                          pageY: path.start[1],
 811                          clientX: path.start[0],
 812                          clientY: path.start[1]
 813                      },
 814                      touches = this._createTouchList([
 815                          Y.merge({identifier: (id++)}, coord)
 816                      ]);
 817  
 818                  this._simulateEvent(this.target, TOUCH_START, Y.merge({
 819                      touches: touches,
 820                      targetTouches: touches,
 821                      changedTouches: touches
 822                  }, coord));
 823              },
 824              timeout: 0,
 825              context: this
 826          });
 827  
 828          // move
 829          steps = Math.floor(duration/interval);
 830          stepX = (path.end[0] - path.start[0])/steps;
 831          stepY = (path.end[1] - path.start[1])/steps;
 832  
 833          touchMove = function(step) {
 834              var px = path.start[0]+(stepX * step),
 835                  py = path.start[1]+(stepY * step),
 836                  coord = {
 837                      pageX: px,
 838                      pageY: py,
 839                      clientX: px,
 840                      clientY: py
 841                  },
 842                  touches = this._createTouchList([
 843                      Y.merge({identifier: (id++)}, coord)
 844                  ]);
 845  
 846              this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
 847                  touches: touches,
 848                  targetTouches: touches,
 849                  changedTouches: touches
 850              }, coord));
 851          };
 852  
 853          for (i=0; i < steps; i++) {
 854              eventQueue.add({
 855                  fn: touchMove,
 856                  args: [i],
 857                  context: this
 858              });
 859          }
 860  
 861          // last move
 862          eventQueue.add({
 863              fn: function() {
 864                  var coord = {
 865                          pageX: path.end[0],
 866                          pageY: path.end[1],
 867                          clientX: path.end[0],
 868                          clientY: path.end[1]
 869                      },
 870                      touches = this._createTouchList([
 871                          Y.merge({identifier: id}, coord)
 872                      ]);
 873  
 874                  this._simulateEvent(this.target, TOUCH_MOVE, Y.merge({
 875                      touches: touches,
 876                      targetTouches: touches,
 877                      changedTouches: touches
 878                  }, coord));
 879              },
 880              timeout: 0,
 881              context: this
 882          });
 883  
 884          // end
 885          eventQueue.add({
 886              fn: function() {
 887                  var coord = {
 888                      pageX: path.end[0],
 889                      pageY: path.end[1],
 890                      clientX: path.end[0],
 891                      clientY: path.end[1]
 892                  },
 893                  emptyTouchList = this._getEmptyTouchList(),
 894                  touches = this._createTouchList([
 895                      Y.merge({identifier: id}, coord)
 896                  ]);
 897  
 898                  this._simulateEvent(this.target, TOUCH_END, Y.merge({
 899                      touches: emptyTouchList,
 900                      targetTouches: emptyTouchList,
 901                      changedTouches: touches
 902                  }, coord));
 903              },
 904              context: this
 905          });
 906  
 907          if(cb && Y.Lang.isFunction(cb)) {
 908              eventQueue.add({
 909                  fn: cb,
 910  
 911                  // by default, the callback runs the node context where
 912                  // simulateGesture method is called.
 913                  context: this.node
 914  
 915                  //TODO: Use args to pass error object as 1st param if there is an error.
 916                  //args:
 917              });
 918          }
 919  
 920          eventQueue.run();
 921      },
 922  
 923      /**
 924       * Helper method to return a singleton instance of empty touch list.
 925       *
 926       * @method _getEmptyTouchList
 927       * @private
 928       * @return {TouchList | Array} An empty touch list object.
 929       */
 930      _getEmptyTouchList: function() {
 931          if(!emptyTouchList) {
 932              emptyTouchList = this._createTouchList([]);
 933          }
 934  
 935          return emptyTouchList;
 936      },
 937  
 938      /**
 939       * Helper method to convert an array with touch points to TouchList object as
 940       * defined in http://www.w3.org/TR/touch-events/
 941       *
 942       * @method _createTouchList
 943       * @private
 944       * @param {Array} touchPoints
 945       * @return {TouchList | Array} If underlaying platform support creating touch list
 946       *      a TouchList object will be returned otherwise a fake Array object
 947       *      will be returned.
 948       */
 949      _createTouchList: function(touchPoints) {
 950          /*
 951          * Android 4.0.3 emulator:
 952          * Native touch api supported starting in version 4.0 (Ice Cream Sandwich).
 953          * However the support seems limited. In Android 4.0.3 emulator, I got
 954          * "TouchList is not defined".
 955          */
 956          var touches = [],
 957              touchList,
 958              self = this;
 959  
 960          if(!!touchPoints && Y.Lang.isArray(touchPoints)) {
 961              if(Y.UA.android && Y.UA.android >= 4.0 || Y.UA.ios && Y.UA.ios >= 2.0) {
 962                  Y.each(touchPoints, function(point) {
 963                      if(!point.identifier) {point.identifier = 0;}
 964                      if(!point.pageX) {point.pageX = 0;}
 965                      if(!point.pageY) {point.pageY = 0;}
 966                      if(!point.screenX) {point.screenX = 0;}
 967                      if(!point.screenY) {point.screenY = 0;}
 968  
 969                      touches.push(document.createTouch(Y.config.win,
 970                          self.target,
 971                          point.identifier,
 972                          point.pageX, point.pageY,
 973                          point.screenX, point.screenY));
 974                  });
 975  
 976                  touchList = document.createTouchList.apply(document, touches);
 977              } else if(Y.UA.ios && Y.UA.ios < 2.0) {
 978                  Y.error(NAME+': No touch event simulation framework present.');
 979              } else {
 980                  // this will inclide android(Y.UA.android && Y.UA.android < 4.0)
 981                  // and desktops among all others.
 982  
 983                  /*
 984                   * Touch APIs are broken in androids older than 4.0. We will use
 985                   * simulated touch apis for these versions.
 986                   */
 987                  touchList = [];
 988                  Y.each(touchPoints, function(point) {
 989                      if(!point.identifier) {point.identifier = 0;}
 990                      if(!point.clientX)  {point.clientX = 0;}
 991                      if(!point.clientY)  {point.clientY = 0;}
 992                      if(!point.pageX)    {point.pageX = 0;}
 993                      if(!point.pageY)    {point.pageY = 0;}
 994                      if(!point.screenX)  {point.screenX = 0;}
 995                      if(!point.screenY)  {point.screenY = 0;}
 996  
 997                      touchList.push({
 998                          target: self.target,
 999                          identifier: point.identifier,
1000                          clientX: point.clientX,
1001                          clientY: point.clientY,
1002                          pageX: point.pageX,
1003                          pageY: point.pageY,
1004                          screenX: point.screenX,
1005                          screenY: point.screenY
1006                      });
1007                  });
1008  
1009                  touchList.item = function(i) {
1010                      return touchList[i];
1011                  };
1012              }
1013          } else {
1014              Y.error(NAME+': Invalid touchPoints passed');
1015          }
1016  
1017          return touchList;
1018      },
1019  
1020      /**
1021       * @method _simulateEvent
1022       * @private
1023       * @param {HTMLElement} target The DOM element that's the target of the event.
1024       * @param {String} type The type of event or name of the supported gesture to simulate
1025       *      (i.e., "click", "doubletap", "flick").
1026       * @param {Object} options (Optional) Extra options to copy onto the event object.
1027       *      For gestures, options are used to refine the gesture behavior.
1028       */
1029      _simulateEvent: function(target, type, options) {
1030          var touches;
1031  
1032          if (touchEvents[type]) {
1033              if(SUPPORTS_TOUCH) {
1034                  Y.Event.simulate(target, type, options);
1035              } else {
1036                  // simulate using mouse events if touch is not applicable on this platform.
1037                  // but only single touch event can be simulated.
1038                  if(this._isSingleTouch(options.touches, options.targetTouches, options.changedTouches)) {
1039                      type = {
1040                          touchstart: MOUSE_DOWN,
1041                          touchmove: MOUSE_MOVE,
1042                          touchend: MOUSE_UP
1043                      }[type];
1044  
1045                      options.button = 0;
1046                      options.relatedTarget = null; // since we are not using mouseover event.
1047  
1048                      // touchend has none in options.touches.
1049                      touches = (type === MOUSE_UP)? options.changedTouches : options.touches;
1050  
1051                      options = Y.mix(options, {
1052                          screenX: touches.item(0).screenX,
1053                          screenY: touches.item(0).screenY,
1054                          clientX: touches.item(0).clientX,
1055                          clientY: touches.item(0).clientY
1056                      }, true);
1057  
1058                      Y.Event.simulate(target, type, options);
1059  
1060                      if(type == MOUSE_UP) {
1061                          Y.Event.simulate(target, MOUSE_CLICK, options);
1062                      }
1063                  } else {
1064                      Y.error("_simulateEvent(): Event '" + type + "' has multi touch objects that can't be simulated in your platform.");
1065                  }
1066              }
1067          } else {
1068              // pass thru for all non touch events
1069              Y.Event.simulate(target, type, options);
1070          }
1071      },
1072  
1073      /**
1074       * Helper method to check the single touch.
1075       * @method _isSingleTouch
1076       * @private
1077       * @param {TouchList} touches
1078       * @param {TouchList} targetTouches
1079       * @param {TouchList} changedTouches
1080       */
1081      _isSingleTouch: function(touches, targetTouches, changedTouches) {
1082          return (touches && (touches.length <= 1)) &&
1083              (targetTouches && (targetTouches.length <= 1)) &&
1084              (changedTouches && (changedTouches.length <= 1));
1085      }
1086  };
1087  
1088  /*
1089   * A gesture simulation class.
1090   */
1091  Y.GestureSimulation = Simulations;
1092  
1093  /*
1094   * Various simulation default behavior properties. If user override
1095   * Y.GestureSimulation.defaults, overriden values will be used and this
1096   * should be done before the gesture simulation.
1097   */
1098  Y.GestureSimulation.defaults = DEFAULTS;
1099  
1100  /*
1101   * The high level gesture names that YUI knows how to simulate.
1102   */
1103  Y.GestureSimulation.GESTURES = gestureNames;
1104  
1105  /**
1106   * Simulates the higher user level gesture of the given name on a target.
1107   * This method generates a set of low level touch events(Apple specific gesture
1108   * events as well for the iOS platforms) asynchronously. Note that gesture
1109   * simulation is relying on `Y.Event.simulate()` method to generate
1110   * the touch events under the hood. The `Y.Event.simulate()` method
1111   * itself is a synchronous method.
1112   *
1113   * Users are suggested to use `Node.simulateGesture()` method which
1114   * basically calls this method internally. Supported gestures are `tap`,
1115   * `doubletap`, `press`, `move`, `flick`, `pinch` and `rotate`.
1116   *
1117   * The `pinch` gesture is used to simulate the pinching and spreading of two
1118   * fingers. During a pinch simulation, rotation is also possible. Essentially
1119   * `pinch` and `rotate` simulations share the same base implementation to allow
1120   * both pinching and rotation at the same time. The only difference is `pinch`
1121   * requires `start` and `end` option properties while `rotate` requires `rotation`
1122   * option property.
1123   *
1124   * The `pinch` and `rotate` gestures can be described as placing 2 fingers along a
1125   * circle. Pinching and spreading can be described by start and end circles while
1126   * rotation occurs on a single circle. If the radius of the start circle is greater
1127   * than the end circle, the gesture becomes a pinch, otherwise it is a spread spread.
1128   *
1129   * @example
1130   *
1131   *     var node = Y.one("#target");
1132   *
1133   *     // double tap example
1134   *     node.simulateGesture("doubletap", function() {
1135   *         // my callback function
1136   *     });
1137   *
1138   *     // flick example from the center of the node, move 50 pixels down for 50ms)
1139   *     node.simulateGesture("flick", {
1140   *         axis: y,
1141   *         distance: -100
1142   *         duration: 50
1143   *     }, function() {
1144   *         // my callback function
1145   *     });
1146   *
1147   *     // simulate rotating a node 75 degrees counter-clockwise
1148   *     node.simulateGesture("rotate", {
1149   *         rotation: -75
1150   *     });
1151   *
1152   *     // simulate a pinch and a rotation at the same time.
1153   *     // fingers start on a circle of radius 100 px, placed at top/bottom
1154   *     // fingers end on a circle of radius 50px, placed at right/left
1155   *     node.simulateGesture("pinch", {
1156   *         r1: 100,
1157   *         r2: 50,
1158   *         start: 0
1159   *         rotation: 90
1160   *     });
1161   *
1162   * @method simulateGesture
1163   * @param {HTMLElement|Node} node The YUI node or HTML element that's the target
1164   *      of the event.
1165   * @param {String} name The name of the supported gesture to simulate. The
1166   *      supported gesture name is one of "tap", "doubletap", "press", "move",
1167   *      "flick", "pinch" and "rotate".
1168   * @param {Object} [options] Extra options used to define the gesture behavior:
1169   *
1170   *      Valid options properties for the `tap` gesture:
1171   *
1172   *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
1173   *        where the tap should be simulated. Default is the center of the node
1174   *        element.
1175   *      @param {Number} [options.hold=10] (Optional) The hold time in milliseconds.
1176   *        This is the time between `touchstart` and `touchend` event generation.
1177   *      @param {Number} [options.times=1] (Optional) Indicates the number of taps.
1178   *      @param {Number} [options.delay=10] (Optional) The number of milliseconds
1179   *        before the next tap simulation happens. This is valid only when `times`
1180   *        is more than 1.
1181   *
1182   *      Valid options properties for the `doubletap` gesture:
1183   *
1184   *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
1185   *        where the doubletap should be simulated. Default is the center of the
1186   *        node element.
1187   *
1188   *      Valid options properties for the `press` gesture:
1189   *
1190   *      @param {Array} [options.point] (Optional) Indicates the [x,y] coordinates
1191   *        where the press should be simulated. Default is the center of the node
1192   *        element.
1193   *      @param {Number} [options.hold=3000] (Optional) The hold time in milliseconds.
1194   *        This is the time between `touchstart` and `touchend` event generation.
1195   *        Default is 3000ms (3 seconds).
1196   *
1197   *      Valid options properties for the `move` gesture:
1198   *
1199   *      @param {Object} [options.path] (Optional) Indicates the path of the finger
1200   *        movement. It's an object with three optional properties: `point`,
1201   *        `xdist` and  `ydist`.
1202   *        @param {Array} [options.path.point] A starting point of the gesture.
1203   *          Default is the center of the node element.
1204   *        @param {Number} [options.path.xdist=200] A distance to move in pixels
1205   *          along the X axis. A negative distance value indicates moving left.
1206   *        @param {Number} [options.path.ydist=0] A distance to move in pixels
1207   *          along the Y axis. A negative distance value indicates moving up.
1208   *      @param {Number} [options.duration=1000] (Optional) The duration of the
1209   *        gesture in milliseconds.
1210   *
1211   *      Valid options properties for the `flick` gesture:
1212   *
1213   *      @param {Array} [options.point] (Optional) Indicates the [x, y] coordinates
1214   *        where the flick should be simulated. Default is the center of the
1215   *        node element.
1216   *      @param {String} [options.axis='x'] (Optional) Valid values are either
1217   *        "x" or "y". Indicates axis to move along. The flick can move to one of
1218   *        4 directions(left, right, up and down).
1219   *      @param {Number} [options.distance=200] (Optional) Distance to move in pixels
1220   *      @param {Number} [options.duration=1000] (Optional) The duration of the
1221   *        gesture in milliseconds. User given value could be automatically
1222   *        adjusted by the framework if it is below the minimum velocity to be
1223   *        a flick gesture.
1224   *
1225   *      Valid options properties for the `pinch` gesture:
1226   *
1227   *      @param {Array} [options.center] (Optional) The center of the circle where
1228   *        two fingers are placed. Default is the center of the node element.
1229   *      @param {Number} [options.r1] (Required) Pixel radius of the start circle
1230   *        where 2 fingers will be on when the gesture starts. The circles are
1231   *        centered at the center of the element.
1232   *      @param {Number} [options.r2] (Required) Pixel radius of the end circle
1233   *        when this gesture ends.
1234   *      @param {Number} [options.duration=1000] (Optional) The duration of the
1235   *        gesture in milliseconds.
1236   *      @param {Number} [options.start=0] (Optional) Starting degree of the first
1237   *        finger. The value is relative to the path of the north. Default is 0
1238   *        (i.e., 12:00 on a clock).
1239   *      @param {Number} [options.rotation=0] (Optional) Degrees to rotate from
1240   *        the starting degree. A negative value means rotation to the
1241   *        counter-clockwise direction.
1242   *
1243   *      Valid options properties for the `rotate` gesture:
1244   *
1245   *      @param {Array} [options.center] (Optional) The center of the circle where
1246   *        two fingers are placed. Default is the center of the node element.
1247   *      @param {Number} [options.r1] (Optional) Pixel radius of the start circle
1248   *        where 2 fingers will be on when the gesture starts. The circles are
1249   *        centered at the center of the element. Default is a fourth of the node
1250   *        element width or height, whichever is smaller.
1251   *      @param {Number} [options.r2] (Optional) Pixel radius of the end circle
1252   *        when this gesture ends. Default is a fourth of the node element width or
1253   *        height, whichever is smaller.
1254   *      @param {Number} [options.duration=1000] (Optional) The duration of the
1255   *        gesture in milliseconds.
1256   *      @param {Number} [options.start=0] (Optional) Starting degree of the first
1257   *        finger. The value is relative to the path of the north. Default is 0
1258   *        (i.e., 12:00 on a clock).
1259   *      @param {Number} [options.rotation] (Required) Degrees to rotate from
1260   *        the starting degree. A negative value means rotation to the
1261   *        counter-clockwise direction.
1262   *
1263   * @param {Function} [cb] The callback to execute when the asynchronouse gesture
1264   *      simulation is completed.
1265   *      @param {Error} cb.err An error object if the simulation is failed.
1266   * @for Event
1267   * @static
1268   */
1269  Y.Event.simulateGesture = function(node, name, options, cb) {
1270  
1271      node = Y.one(node);
1272  
1273      var sim = new Y.GestureSimulation(node);
1274      name = name.toLowerCase();
1275  
1276      if(!cb && Y.Lang.isFunction(options)) {
1277          cb = options;
1278          options = {};
1279      }
1280  
1281      options = options || {};
1282  
1283      if (gestureNames[name]) {
1284          switch(name) {
1285              // single-touch: point gestures
1286              case 'tap':
1287                  sim.tap(cb, options.point, options.times, options.hold, options.delay);
1288                  break;
1289              case 'doubletap':
1290                  sim.tap(cb, options.point, 2);
1291                  break;
1292              case 'press':
1293                  if(!Y.Lang.isNumber(options.hold)) {
1294                      options.hold = DEFAULTS.HOLD_PRESS;
1295                  } else if(options.hold < DEFAULTS.MIN_HOLD_PRESS) {
1296                      options.hold = DEFAULTS.MIN_HOLD_PRESS;
1297                  } else if(options.hold > DEFAULTS.MAX_HOLD_PRESS) {
1298                      options.hold = DEFAULTS.MAX_HOLD_PRESS;
1299                  }
1300                  sim.tap(cb, options.point, 1, options.hold);
1301                  break;
1302  
1303              // single-touch: move gestures
1304              case 'move':
1305                  sim.move(cb, options.path, options.duration);
1306                  break;
1307              case 'flick':
1308                  sim.flick(cb, options.point, options.axis, options.distance,
1309                      options.duration);
1310                  break;
1311  
1312              // multi-touch: pinch/rotation gestures
1313              case 'pinch':
1314                  sim.pinch(cb, options.center, options.r1, options.r2,
1315                      options.duration, options.start, options.rotation);
1316                  break;
1317              case 'rotate':
1318                  sim.rotate(cb, options.center, options.r1, options.r2,
1319                      options.duration, options.start, options.rotation);
1320                  break;
1321          }
1322      } else {
1323          Y.error(NAME+': Not a supported gesture simulation: '+name);
1324      }
1325  };
1326  
1327  
1328  }, '3.17.2', {"requires": ["async-queue", "event-simulate", "node-screen"]});


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