simulate.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /* General Utils
  2. ----------------------------------------------------------------------------------------------------------------------*/
  3. $.simulateByPoint = function(type, options) {
  4. var docEl = $(document);
  5. var point = options.point;
  6. var clientX, clientY;
  7. var node;
  8. if (point) {
  9. clientX = point.left - docEl.scrollLeft();
  10. clientY = point.top - docEl.scrollTop();
  11. node = document.elementFromPoint(clientX, clientY);
  12. $(node).simulate(type, options);
  13. }
  14. };
  15. /* Touch
  16. ----------------------------------------------------------------------------------------------------------------------*/
  17. var origSimulateEvent = $.simulate.prototype.simulateEvent;
  18. var touchUID = Date.now();
  19. $.simulate.prototype.simulateEvent = function(elem, type, options) {
  20. if (/^touch/.test(type)) {
  21. return this.simulateTouchEvent(elem, type, options);
  22. }
  23. else {
  24. return origSimulateEvent.apply(this, arguments);
  25. }
  26. };
  27. $.simulate.prototype.simulateTouchEvent = function(elem, type, options) {
  28. // http://stackoverflow.com/a/29019278/96342
  29. var event = document.createEvent('Event');
  30. event.initEvent(type, true, true); // cancelable, bubbleable
  31. event.touches = [{
  32. target: elem,
  33. identifier: touchUID++,
  34. pageX: options.clientX,
  35. pageY: options.clientY,
  36. screenX: options.clientX,
  37. screenY: options.clientY,
  38. clientX: options.clientX,
  39. clientY: options.clientY
  40. }];
  41. this.dispatchEvent(elem, type, event, options);
  42. };
  43. $.simulateMouseClick = function(elem) {
  44. var $elem = $(elem);
  45. var clientCoords = {
  46. clientX: $elem.offset().left + $elem.outerWidth() / 2,
  47. clientY: $elem.offset().top + $elem.outerHeight() / 2
  48. };
  49. $elem.simulate('mousemove', clientCoords);
  50. $elem.simulate('mousedown', clientCoords);
  51. $elem.simulate('mouseup', clientCoords);
  52. $elem.simulate('click', clientCoords);
  53. };
  54. $.simulateTouchClick = function(elem) {
  55. var $elem = $(elem);
  56. var clientCoords = {
  57. clientX: $elem.offset().left + $elem.outerWidth() / 2,
  58. clientY: $elem.offset().top + $elem.outerHeight() / 2
  59. };
  60. $elem.simulate('touchstart', clientCoords);
  61. $elem.simulate('touchend', clientCoords);
  62. $elem.simulate('mousemove', clientCoords);
  63. $elem.simulate('mousedown', clientCoords);
  64. $elem.simulate('mouseup', clientCoords);
  65. $elem.simulate('click', clientCoords);
  66. };
  67. /* Drag-n-drop
  68. ----------------------------------------------------------------------------------------------------------------------*/
  69. var DEBUG_DELAY = 500;
  70. var DEBUG_MIN_DURATION = 2000;
  71. var DEBUG_MIN_MOVES = 100;
  72. var DRAG_DEFAULTS = {
  73. point: null, // the start point
  74. localPoint: { left: '50%', top: '50%' },
  75. end: null, // can be a point or an el
  76. localEndPoint: { left: '50%', top: '50%' },
  77. dx: 0,
  78. dy: 0,
  79. moves: 5,
  80. duration: 100 // ms
  81. };
  82. var dragStackCnt = 0;
  83. $.simulate.prototype.simulateDrag = function() {
  84. var targetNode = this.target;
  85. var targetEl = $(targetNode);
  86. var options = $.extend({}, DRAG_DEFAULTS, this.options);
  87. var dx = options.dx;
  88. var dy = options.dy;
  89. var duration = options.duration;
  90. var moves = options.moves;
  91. var startPoint;
  92. var endEl;
  93. var endPoint;
  94. var localPoint;
  95. var offset;
  96. // compute start point
  97. if (options.point) {
  98. startPoint = options.point;
  99. }
  100. else {
  101. localPoint = normalizeElPoint(options.localPoint, targetEl);
  102. offset = targetEl.offset();
  103. startPoint = {
  104. left: offset.left + localPoint.left,
  105. top: offset.top + localPoint.top
  106. };
  107. }
  108. // compute end point
  109. if (options.end) {
  110. if (isPoint(options.end)) {
  111. endPoint = options.end;
  112. }
  113. else { // assume options.end is an element
  114. endEl = $(options.end);
  115. localPoint = normalizeElPoint(options.localEndPoint, endEl);
  116. offset = endEl.offset();
  117. endPoint = {
  118. left: offset.left + localPoint.left,
  119. top: offset.top + localPoint.top
  120. };
  121. }
  122. }
  123. if (endPoint) {
  124. dx = endPoint.left - startPoint.left;
  125. dy = endPoint.top - startPoint.top;
  126. }
  127. moves = Math.max(moves, options.debug ? DEBUG_MIN_MOVES : 1);
  128. duration = Math.max(duration, options.debug ? DEBUG_MIN_DURATION : 10);
  129. simulateDrag(
  130. this,
  131. targetNode,
  132. startPoint,
  133. dx,
  134. dy,
  135. moves,
  136. duration,
  137. options
  138. );
  139. };
  140. function simulateDrag(self, targetNode, startPoint, dx, dy, moveCnt, duration, options) {
  141. var debug = options.debug;
  142. var isTouch = options.isTouch;
  143. var docNode = targetNode.ownerDocument;
  144. var docEl = $(docNode);
  145. var waitTime = duration / moveCnt;
  146. var moveIndex = 0;
  147. var clientCoords;
  148. var intervalId;
  149. var dotEl;
  150. var dragId;
  151. if (debug) {
  152. dotEl = $('<div>')
  153. .css({
  154. position: 'absolute',
  155. zIndex: 99999,
  156. border: '5px solid red',
  157. borderRadius: '5px',
  158. margin: '-5px 0 0 -5px'
  159. })
  160. .appendTo('body');
  161. }
  162. function updateCoords() {
  163. var progress = moveIndex / moveCnt;
  164. var left = startPoint.left + dx * progress;
  165. var top = startPoint.top + dy * progress;
  166. clientCoords = {
  167. clientX: left - docEl.scrollLeft(),
  168. clientY: top - docEl.scrollTop()
  169. };
  170. if (debug) {
  171. dotEl.css({ left: left, top: top });
  172. }
  173. }
  174. function startDrag() {
  175. updateCoords();
  176. dragId = ++dragStackCnt;
  177. // simulate a drag-start only if another drag isn't already happening
  178. if (dragStackCnt === 1) {
  179. self.simulateEvent(targetNode, isTouch ? 'touchstart' : 'mousedown', clientCoords);
  180. }
  181. var delay = options.delay || 0;
  182. if (debug) {
  183. delay = Math.max(delay, DEBUG_DELAY);
  184. }
  185. if (delay) {
  186. setTimeout(function() {
  187. startMoving();
  188. }, delay);
  189. }
  190. else {
  191. startMoving();
  192. }
  193. }
  194. function startMoving() {
  195. intervalId = setInterval(tick, waitTime);
  196. }
  197. function tick() { // called one interval after start
  198. moveIndex++;
  199. updateCoords(); // update clientCoords before mousemove
  200. self.simulateEvent(docNode, isTouch ? 'touchmove' : 'mousemove', clientCoords);
  201. if (moveIndex >= moveCnt) {
  202. stopMoving();
  203. }
  204. }
  205. function stopMoving() {
  206. clearInterval(intervalId);
  207. if (debug) {
  208. setTimeout(function() {
  209. dotEl.remove(); // do this before calling stopDrag/callback. don't want dot picked up by elementFromPoint
  210. stopDrag();
  211. }, DEBUG_DELAY);
  212. }
  213. else {
  214. stopDrag();
  215. }
  216. }
  217. function stopDrag() { // progress at 1, coords already up to date at this point
  218. (options.onBeforeRelease || function() {})();
  219. // only simulate a drop if the current drag is still the active one.
  220. // otherwise, this means another drag has begun via onBeforeRelease.
  221. if (dragId === dragStackCnt) {
  222. if ($.contains(docNode, targetNode)) {
  223. self.simulateEvent(targetNode, isTouch ? 'touchend' : 'mouseup', clientCoords);
  224. self.simulateEvent(targetNode, 'click', clientCoords);
  225. }
  226. else {
  227. self.simulateEvent(docNode, isTouch ? 'touchend' : 'mouseup', clientCoords);
  228. }
  229. }
  230. dragStackCnt--;
  231. (options.onRelease || options.callback || function() {})(); // TODO: deprecate "callback" ?
  232. }
  233. startDrag();
  234. }
  235. function normalizeElPoint(point, el) {
  236. var left = point.left;
  237. var top = point.top;
  238. if (/%$/.test(left)) {
  239. left = parseInt(left) / 100 * el.outerWidth();
  240. }
  241. if (/%$/.test(top)) {
  242. top = parseInt(top) / 100 * el.outerHeight();
  243. }
  244. return { left: left, top: top };
  245. }
  246. function isPoint(input) {
  247. return typeof input === 'object' && // `in` operator only works on objects
  248. 'left' in input && 'top' in input;
  249. }