[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('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"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |