Recorder.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /**
  2. * @extends Ext.ux.event.Driver
  3. * Event recorder.
  4. */
  5. Ext.define('Ext.ux.event.Recorder', function () {
  6. function samePt (pt1, pt2) {
  7. return pt1.x == pt2.x && pt1.y == pt2.y;
  8. }
  9. return {
  10. extend: 'Ext.ux.event.Driver',
  11. eventsToRecord: function () {
  12. var key = { kind: 'keyboard', modKeys: true, key: true, bubbles: true },
  13. mouse = { kind: 'mouse', button: true, modKeys: true, xy: true, bubbles: true };
  14. return {
  15. keydown: key,
  16. keypress: key,
  17. keyup: key,
  18. mousemove: mouse,
  19. mouseover: mouse,
  20. mouseout: mouse,
  21. click: mouse,
  22. //mousewheel: Ext.apply({ wheel: true }, mouse),
  23. mousedown: mouse,
  24. mouseup: mouse,
  25. scroll: { kind: 'misc', bubbles: false }
  26. };
  27. }(),
  28. ignoreIdRegEx: /ext-gen(?:\d+)/,
  29. constructor: function (config) {
  30. var me = this,
  31. events = config && config.eventsToRecord;
  32. if (events) {
  33. me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
  34. events); // and merge
  35. delete config.eventsToRecord; // don't smash
  36. }
  37. me.callParent(arguments);
  38. me.addEvents(
  39. /**
  40. * @event add
  41. * Fires when an event is added to the recording.
  42. * @param {Ext.ux.event.Recorder} this
  43. * @param {Object} eventDescriptor The event descriptor.
  44. */
  45. 'add',
  46. /**
  47. * @event coalesce
  48. * Fires when an event is coalesced. This edits the tail of the recorded
  49. * event list.
  50. * @param {Ext.ux.event.Recorder} this
  51. * @param {Object} eventDescriptor The event descriptor that was coalesced.
  52. */
  53. 'coalesce'
  54. );
  55. me.clear();
  56. me.modKeys = [];
  57. me.attachTo = me.attachTo || window;
  58. },
  59. clear: function () {
  60. this.eventsRecorded = [];
  61. },
  62. coalesce: function (rec) {
  63. var me = this,
  64. events = me.eventsRecorded,
  65. length = events.length,
  66. tail = length && events[length-1],
  67. tailPrev;
  68. if (!tail) {
  69. return false;
  70. }
  71. if (rec.type == 'mousemove') {
  72. if (tail.type == 'mousemove' && rec.ts - tail.ts < 200) {
  73. rec.ts = tail.ts;
  74. events[length-1] = rec;
  75. return true;
  76. }
  77. } else if (rec.type == 'click') {
  78. if (length >= 2 && tail.type == 'mouseup' &&
  79. (tailPrev = events[length-2]).type == 'mousedown') {
  80. if (rec.button == tail.button && rec.button == tailPrev.button &&
  81. rec.target == tail.target && rec.target == tailPrev.target &&
  82. samePt(rec, tail) && samePt(rec, tailPrev) ) {
  83. events.pop(); // remove mouseup
  84. tailPrev.type = 'mduclick';
  85. return true;
  86. }
  87. }
  88. }
  89. return false;
  90. },
  91. getElementXPath: function (el) {
  92. var me = this,
  93. good = false,
  94. xpath = [],
  95. count,
  96. sibling,
  97. t,
  98. tag;
  99. for (t = el; t; t = t.parentNode) {
  100. if (t == me.attachTo.document.body) {
  101. xpath.unshift('~');
  102. good = true;
  103. break;
  104. }
  105. if (t.id && !me.ignoreIdRegEx.test(t.id)) {
  106. xpath.unshift('#' + t.id);
  107. good = true;
  108. break;
  109. }
  110. for (count = 1, sibling = t; !!(sibling = sibling.previousSibling); ) {
  111. if (sibling.tagName == t.tagName) {
  112. ++count;
  113. }
  114. }
  115. tag = t.tagName.toLowerCase();
  116. if (count < 2) {
  117. xpath.unshift(tag);
  118. } else {
  119. xpath.unshift(tag + '[' + count + ']');
  120. }
  121. }
  122. return good ? xpath.join('/') : null;
  123. },
  124. getRecordedEvents: function () {
  125. return this.eventsRecorded;
  126. },
  127. // DOMNodeInserted
  128. onDomInsert: function (event, target) {
  129. this.watchTree(target);
  130. },
  131. //DOMNodeRemoved
  132. onDomRemove: function (event, target) {
  133. this.unwatchTree(target);
  134. },
  135. onEvent: function (e) {
  136. var me = this,
  137. info = me.eventsToRecord[e.type],
  138. root,
  139. modKeys, elXY,
  140. rec = {
  141. type: e.type,
  142. ts: me.getTimestamp(),
  143. target: me.getElementXPath(e.target)
  144. },
  145. xy;
  146. if (!rec.target) {
  147. return;
  148. }
  149. root = e.target.ownerDocument;
  150. root = root.defaultView || root.parentWindow; // Standards || IE
  151. if (root !== me.attachTo) {
  152. return;
  153. }
  154. if (info.xy) {
  155. xy = e.getXY();
  156. if (rec.target) {
  157. elXY = Ext.fly(e.getTarget()).getXY();
  158. xy[0] -= elXY[0];
  159. xy[1] -= elXY[1];
  160. }
  161. rec.x = xy[0];
  162. rec.y = xy[1];
  163. }
  164. if (info.button) {
  165. rec.button = e.button;
  166. }
  167. if (info.wheel) {
  168. rec.wheel = e.getWheelDelta();
  169. }
  170. if (info.modKeys) {
  171. me.modKeys[0] = e.altKey ? 'A' : '';
  172. me.modKeys[1] = e.ctrlKey ? 'C' : '';
  173. me.modKeys[2] = e.metaKey ? 'M' : '';
  174. me.modKeys[3] = e.shiftKey ? 'S' : '';
  175. modKeys = me.modKeys.join('');
  176. if (modKeys) {
  177. rec.modKeys = modKeys;
  178. }
  179. }
  180. if (info.key) {
  181. rec.charCode = e.getCharCode();
  182. rec.keyCode = e.getKey();
  183. }
  184. if (me.coalesce(rec)) {
  185. me.fireEvent('coalesce', me, rec);
  186. } else {
  187. me.eventsRecorded.push(rec);
  188. me.fireEvent('add', me, rec);
  189. }
  190. },
  191. onStart: function () {
  192. var me = this,
  193. on = {
  194. DOMNodeInserted: me.onDomInsert,
  195. DOMNodeRemoved: me.onDomRemove,
  196. scope: me
  197. },
  198. nonBubbleEvents = (me.nonBubbleEvents = {}),
  199. ddm = me.attachTo.Ext.dd.DragDropManager,
  200. evproto = me.attachTo.Ext.EventObjectImpl.prototype;
  201. me.watchingNodes = {};
  202. Ext.Object.each(me.eventsToRecord, function (name, value) {
  203. if (value) {
  204. if (value.bubbles) {
  205. on[name] = me.onEvent;
  206. } else {
  207. nonBubbleEvents[name] = value;
  208. }
  209. }
  210. });
  211. me.ddmStopEvent = ddm.stopEvent;
  212. ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function (e) {
  213. me.onEvent(e);
  214. });
  215. me.evStopEvent = evproto.stopEvent;
  216. evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function () {
  217. me.onEvent(this);
  218. });
  219. var body = me.attachTo.Ext.getBody();
  220. body.on(on);
  221. me.watchTree(body.dom);
  222. },
  223. onStop: function () {
  224. var me = this,
  225. body = me.attachTo.Ext.getBody();
  226. Ext.Object.each(me.eventsToRecord, function (name, value) {
  227. if (value) {
  228. body.un(name, me.onEvent, me);
  229. }
  230. });
  231. me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
  232. me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
  233. me.unwatchTree(body.dom);
  234. },
  235. watchTree: function (root) {
  236. if (root.nodeType != 1) {
  237. return; // only ELEMENT_NODE's please...
  238. }
  239. var me = this,
  240. id = (root.tagName == 'BODY') ? '$' : root.id,
  241. watchingNodes = me.watchingNodes;
  242. if (id && !watchingNodes[id]) {
  243. var on = {
  244. scope: me
  245. };
  246. Ext.Object.each(me.nonBubbleEvents, function (name, value) {
  247. if (value) {
  248. on[name] = me.onEvent;
  249. }
  250. });
  251. me.attachTo.Ext.fly(root).on(on);
  252. watchingNodes[id] = true;
  253. console.log('watch '+root.tagName+'#'+id);
  254. }
  255. Ext.each(root.childNodes, me.watchTree, me);
  256. },
  257. unwatchTree: function (root) {
  258. if (root.nodeType != 1) {
  259. return; // only ELEMENT_NODE's please...
  260. }
  261. var me = this,
  262. id = (root.tagName == 'BODY') ? '$' : root.id,
  263. watchingNodes = me.watchingNodes;
  264. if (id && !watchingNodes[id]) {
  265. Ext.Object.each(me.nonBubbleEvents, function (name, value) {
  266. me.attachTo.Ext.fly(root).un(name, me.onEvent, me);
  267. });
  268. delete watchingNodes[id];
  269. console.log('unwatch '+root.tagName+'#'+id);
  270. }
  271. Ext.each(root.childNodes, me.unwatchTree, me);
  272. }
  273. };
  274. }());