]> git.proxmox.com Git - extjs.git/blame - extjs/build/packages/ux/modern/ux-debug.js
add extjs 6.0.1 sources
[extjs.git] / extjs / build / packages / ux / modern / ux-debug.js
CommitLineData
6527f429
DM
1/**
2 * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
3 * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
4 */\r
5Ext.define('Ext.ux.ajax.Simlet', function() {\r
6 var urlRegex = /([^?#]*)(#.*)?$/,\r
7 dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,\r
8 intRegex = /^[+-]?\d+$/,\r
9 floatRegex = /^[+-]?\d+\.\d+$/;\r
10 function parseParamValue(value) {\r
11 var m;\r
12 if (Ext.isDefined(value)) {\r
13 value = decodeURIComponent(value);\r
14 if (intRegex.test(value)) {\r
15 value = parseInt(value, 10);\r
16 } else if (floatRegex.test(value)) {\r
17 value = parseFloat(value);\r
18 } else if (!!(m = dateRegex.test(value))) {\r
19 value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));\r
20 }\r
21 }\r
22 return value;\r
23 }\r
24 return {\r
25 alias: 'simlet.basic',\r
26 isSimlet: true,\r
27 responseProps: [\r
28 'responseText',\r
29 'responseXML',\r
30 'status',\r
31 'statusText'\r
32 ],\r
33 /**
34 * @cfg {Number} responseText
35 */\r
36 /**
37 * @cfg {Number} responseXML
38 */\r
39 /**
40 * @cfg {Object} responseHeaders
41 */\r
42 /**
43 * @cfg {Number} status
44 */\r
45 status: 200,\r
46 /**
47 * @cfg {String} statusText
48 */\r
49 statusText: 'OK',\r
50 constructor: function(config) {\r
51 Ext.apply(this, config);\r
52 },\r
53 doGet: function(ctx) {\r
54 var me = this,\r
55 ret = {};\r
56 Ext.Array.forEach(me.responseProps, function(prop) {\r
57 if (prop in me) {\r
58 ret[prop] = me[prop];\r
59 }\r
60 });\r
61 return ret;\r
62 },\r
63 doPost: function(ctx) {\r
64 var me = this,\r
65 ret = {};\r
66 Ext.Array.forEach(me.responseProps, function(prop) {\r
67 if (prop in me) {\r
68 ret[prop] = me[prop];\r
69 }\r
70 });\r
71 return ret;\r
72 },\r
73 doRedirect: function(ctx) {\r
74 return false;\r
75 },\r
76 doDelete: function(ctx) {\r
77 var me = this,\r
78 xhr = ctx.xhr,\r
79 records = xhr.options.records;\r
80 me.removeFromData(ctx, records);\r
81 },\r
82 /**
83 * Performs the action requested by the given XHR and returns an object to be applied
84 * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
85 * this is delegated to `doMethod` methods on this class, such as `doGet`.
86 *
87 * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
88 * @return {Object} The response properties to add to the XMLHttpRequest.
89 */\r
90 exec: function(xhr) {\r
91 var me = this,\r
92 ret = {},\r
93 method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),\r
94 // doGet\r
95 fn = me[method];\r
96 if (fn) {\r
97 ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));\r
98 } else {\r
99 ret = {\r
100 status: 405,\r
101 statusText: 'Method Not Allowed'\r
102 };\r
103 }\r
104 return ret;\r
105 },\r
106 getCtx: function(method, url, xhr) {\r
107 return {\r
108 method: method,\r
109 params: this.parseQueryString(url),\r
110 url: url,\r
111 xhr: xhr\r
112 };\r
113 },\r
114 openRequest: function(method, url, options, async) {\r
115 var ctx = this.getCtx(method, url),\r
116 redirect = this.doRedirect(ctx),\r
117 xhr;\r
118 if (options.action === 'destroy') {\r
119 method = 'delete';\r
120 }\r
121 if (redirect) {\r
122 xhr = redirect;\r
123 } else {\r
124 xhr = new Ext.ux.ajax.SimXhr({\r
125 mgr: this.manager,\r
126 simlet: this,\r
127 options: options\r
128 });\r
129 xhr.open(method, url, async);\r
130 }\r
131 return xhr;\r
132 },\r
133 parseQueryString: function(str) {\r
134 var m = urlRegex.exec(str),\r
135 ret = {},\r
136 key, value, i, n;\r
137 if (m && m[1]) {\r
138 var pair,\r
139 parts = m[1].split('&');\r
140 for (i = 0 , n = parts.length; i < n; ++i) {\r
141 if ((pair = parts[i].split('='))[0]) {\r
142 key = decodeURIComponent(pair.shift());\r
143 value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);\r
144 if (!(key in ret)) {\r
145 ret[key] = value;\r
146 } else if (Ext.isArray(ret[key])) {\r
147 ret[key].push(value);\r
148 } else {\r
149 ret[key] = [\r
150 ret[key],\r
151 value\r
152 ];\r
153 }\r
154 }\r
155 }\r
156 }\r
157 return ret;\r
158 },\r
159 redirect: function(method, url, params) {\r
160 switch (arguments.length) {\r
161 case 2:\r
162 if (typeof url == 'string') {\r
163 break;\r
164 };\r
165 params = url;\r
166 // fall...\r
167 case 1:\r
168 url = method;\r
169 method = 'GET';\r
170 break;\r
171 }\r
172 if (params) {\r
173 url = Ext.urlAppend(url, Ext.Object.toQueryString(params));\r
174 }\r
175 return this.manager.openRequest(method, url);\r
176 },\r
177 removeFromData: function(ctx, records) {\r
178 var me = this,\r
179 data = me.getData(ctx),\r
180 model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},\r
181 idProperty = model.idProperty || 'id';\r
182 Ext.each(records, function(record) {\r
183 var id = record.get(idProperty);\r
184 for (var i = data.length; i-- > 0; ) {\r
185 if (data[i][idProperty] === id) {\r
186 me.deleteRecord(i);\r
187 break;\r
188 }\r
189 }\r
190 });\r
191 }\r
192 };\r
193}());\r
194\r
195/**
196 * This base class is used to handle data preparation (e.g., sorting, filtering and
197 * group summary).
198 */\r
199Ext.define('Ext.ux.ajax.DataSimlet', function() {\r
200 function makeSortFn(def, cmp) {\r
201 var order = def.direction,\r
202 sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;\r
203 return function(leftRec, rightRec) {\r
204 var lhs = leftRec[def.property],\r
205 rhs = rightRec[def.property],\r
206 c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);\r
207 if (c || !cmp) {\r
208 return c * sign;\r
209 }\r
210 return cmp(leftRec, rightRec);\r
211 };\r
212 }\r
213 function makeSortFns(defs, cmp) {\r
214 for (var sortFn = cmp,\r
215 i = defs && defs.length; i; ) {\r
216 sortFn = makeSortFn(defs[--i], sortFn);\r
217 }\r
218 return sortFn;\r
219 }\r
220 return {\r
221 extend: 'Ext.ux.ajax.Simlet',\r
222 buildNodes: function(node, path) {\r
223 var me = this,\r
224 nodeData = {\r
225 data: []\r
226 },\r
227 len = node.length,\r
228 children, i, child, name;\r
229 me.nodes[path] = nodeData;\r
230 for (i = 0; i < len; ++i) {\r
231 nodeData.data.push(child = node[i]);\r
232 name = child.text || child.title;\r
233 child.id = path ? path + '/' + name : name;\r
234 children = child.children;\r
235 if (!(child.leaf = !children)) {\r
236 delete child.children;\r
237 me.buildNodes(children, child.id);\r
238 }\r
239 }\r
240 },\r
241 deleteRecord: function(pos) {\r
242 if (this.data && typeof this.data !== 'function') {\r
243 Ext.Array.removeAt(this.data, pos);\r
244 }\r
245 },\r
246 fixTree: function(ctx, tree) {\r
247 var me = this,\r
248 node = ctx.params.node,\r
249 nodes;\r
250 if (!(nodes = me.nodes)) {\r
251 me.nodes = nodes = {};\r
252 me.buildNodes(tree, '');\r
253 }\r
254 node = nodes[node];\r
255 if (node) {\r
256 if (me.node) {\r
257 me.node.sortedData = me.sortedData;\r
258 me.node.currentOrder = me.currentOrder;\r
259 }\r
260 me.node = node;\r
261 me.data = node.data;\r
262 me.sortedData = node.sortedData;\r
263 me.currentOrder = node.currentOrder;\r
264 } else {\r
265 me.data = null;\r
266 }\r
267 },\r
268 getData: function(ctx) {\r
269 var me = this,\r
270 params = ctx.params,\r
271 order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),\r
272 tree = me.tree,\r
273 dynamicData, data, fields, sortFn;\r
274 if (tree) {\r
275 me.fixTree(ctx, tree);\r
276 }\r
277 data = me.data;\r
278 if (typeof data === 'function') {\r
279 dynamicData = true;\r
280 data = data.call(this, ctx);\r
281 }\r
282 // If order is '--' then it means we had no order passed, due to the string concat above\r
283 if (!data || order === '--') {\r
284 return data || [];\r
285 }\r
286 if (!dynamicData && order == me.currentOrder) {\r
287 return me.sortedData;\r
288 }\r
289 ctx.filterSpec = params.filter && Ext.decode(params.filter);\r
290 ctx.groupSpec = params.group && Ext.decode(params.group);\r
291 fields = params.sort;\r
292 if (params.dir) {\r
293 fields = [\r
294 {\r
295 direction: params.dir,\r
296 property: fields\r
297 }\r
298 ];\r
299 } else {\r
300 fields = Ext.decode(params.sort);\r
301 }\r
302 if (ctx.filterSpec) {\r
303 var filters = new Ext.util.FilterCollection();\r
304 filters.add(this.processFilters(ctx.filterSpec));\r
305 data = Ext.Array.filter(data, filters.getFilterFn());\r
306 }\r
307 sortFn = makeSortFns((ctx.sortSpec = fields));\r
308 if (ctx.groupSpec) {\r
309 sortFn = makeSortFns([\r
310 ctx.groupSpec\r
311 ], sortFn);\r
312 }\r
313 // If a straight Ajax request, data may not be an array.\r
314 // If an Array, preserve 'physical' order of raw data...\r
315 data = Ext.isArray(data) ? data.slice(0) : data;\r
316 if (sortFn) {\r
317 Ext.Array.sort(data, sortFn);\r
318 }\r
319 me.sortedData = data;\r
320 me.currentOrder = order;\r
321 return data;\r
322 },\r
323 processFilters: Ext.identityFn,\r
324 getPage: function(ctx, data) {\r
325 var ret = data,\r
326 length = data.length,\r
327 start = ctx.params.start || 0,\r
328 end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;\r
329 if (start || end < length) {\r
330 ret = ret.slice(start, end);\r
331 }\r
332 return ret;\r
333 },\r
334 getGroupSummary: function(groupField, rows, ctx) {\r
335 return rows[0];\r
336 },\r
337 getSummary: function(ctx, data, page) {\r
338 var me = this,\r
339 groupField = ctx.groupSpec.property,\r
340 accum,\r
341 todo = {},\r
342 summary = [],\r
343 fieldValue, lastFieldValue;\r
344 Ext.each(page, function(rec) {\r
345 fieldValue = rec[groupField];\r
346 todo[fieldValue] = true;\r
347 });\r
348 function flush() {\r
349 if (accum) {\r
350 summary.push(me.getGroupSummary(groupField, accum, ctx));\r
351 accum = null;\r
352 }\r
353 }\r
354 // data is ordered primarily by the groupField, so one pass can pick up all\r
355 // the summaries one at a time.\r
356 Ext.each(data, function(rec) {\r
357 fieldValue = rec[groupField];\r
358 if (lastFieldValue !== fieldValue) {\r
359 flush();\r
360 lastFieldValue = fieldValue;\r
361 }\r
362 if (!todo[fieldValue]) {\r
363 // if we have even 1 summary, we have summarized all that we need\r
364 // (again because data and page are ordered by groupField)\r
365 return !summary.length;\r
366 }\r
367 if (accum) {\r
368 accum.push(rec);\r
369 } else {\r
370 accum = [\r
371 rec\r
372 ];\r
373 }\r
374 return true;\r
375 });\r
376 flush();\r
377 // make sure that last pesky summary goes...\r
378 return summary;\r
379 }\r
380 };\r
381}());\r
382\r
383/**
384 * JSON Simlet.
385 */\r
386Ext.define('Ext.ux.ajax.JsonSimlet', {\r
387 extend: 'Ext.ux.ajax.DataSimlet',\r
388 alias: 'simlet.json',\r
389 doGet: function(ctx) {\r
390 var me = this,\r
391 data = me.getData(ctx),\r
392 page = me.getPage(ctx, data),\r
393 reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),\r
394 root = reader && reader.getRootProperty(),\r
395 ret = me.callParent(arguments),\r
396 // pick up status/statusText\r
397 response = {};\r
398 if (root && Ext.isArray(page)) {\r
399 response[root] = page;\r
400 response[reader.getTotalProperty()] = data.length;\r
401 } else {\r
402 response = page;\r
403 }\r
404 if (ctx.groupSpec) {\r
405 response.summaryData = me.getSummary(ctx, data, page);\r
406 }\r
407 ret.responseText = Ext.encode(response);\r
408 return ret;\r
409 },\r
410 doPost: function(ctx) {\r
411 return this.doGet(ctx);\r
412 }\r
413});\r
414\r
415/**
416 * Simulates an XMLHttpRequest object's methods and properties but is backed by a
417 * {@link Ext.ux.ajax.Simlet} instance that provides the data.
418 */\r
419Ext.define('Ext.ux.ajax.SimXhr', {\r
420 readyState: 0,\r
421 mgr: null,\r
422 simlet: null,\r
423 constructor: function(config) {\r
424 var me = this;\r
425 Ext.apply(me, config);\r
426 me.requestHeaders = {};\r
427 },\r
428 abort: function() {\r
429 var me = this;\r
430 if (me.timer) {\r
431 clearTimeout(me.timer);\r
432 me.timer = null;\r
433 }\r
434 me.aborted = true;\r
435 },\r
436 getAllResponseHeaders: function() {\r
437 var headers = [];\r
438 if (Ext.isObject(this.responseHeaders)) {\r
439 Ext.Object.each(this.responseHeaders, function(name, value) {\r
440 headers.push(name + ': ' + value);\r
441 });\r
442 }\r
443 return headers.join('\r\n');\r
444 },\r
445 getResponseHeader: function(header) {\r
446 var headers = this.responseHeaders;\r
447 return (headers && headers[header]) || null;\r
448 },\r
449 open: function(method, url, async, user, password) {\r
450 var me = this;\r
451 me.method = method;\r
452 me.url = url;\r
453 me.async = async !== false;\r
454 me.user = user;\r
455 me.password = password;\r
456 me.setReadyState(1);\r
457 },\r
458 overrideMimeType: function(mimeType) {\r
459 this.mimeType = mimeType;\r
460 },\r
461 schedule: function() {\r
462 var me = this,\r
463 delay = me.mgr.delay;\r
464 if (delay) {\r
465 me.timer = setTimeout(function() {\r
466 me.onTick();\r
467 }, delay);\r
468 } else {\r
469 me.onTick();\r
470 }\r
471 },\r
472 send: function(body) {\r
473 var me = this;\r
474 me.body = body;\r
475 if (me.async) {\r
476 me.schedule();\r
477 } else {\r
478 me.onComplete();\r
479 }\r
480 },\r
481 setReadyState: function(state) {\r
482 var me = this;\r
483 if (me.readyState != state) {\r
484 me.readyState = state;\r
485 me.onreadystatechange();\r
486 }\r
487 },\r
488 setRequestHeader: function(header, value) {\r
489 this.requestHeaders[header] = value;\r
490 },\r
491 // handlers\r
492 onreadystatechange: Ext.emptyFn,\r
493 onComplete: function() {\r
494 var me = this,\r
495 callback;\r
496 me.readyState = 4;\r
497 Ext.apply(me, me.simlet.exec(me));\r
498 callback = me.jsonpCallback;\r
499 if (callback) {\r
500 var text = callback + '(' + me.responseText + ')';\r
501 eval(text);\r
502 }\r
503 },\r
504 onTick: function() {\r
505 var me = this;\r
506 me.timer = null;\r
507 me.onComplete();\r
508 me.onreadystatechange && me.onreadystatechange();\r
509 }\r
510});\r
511\r
512/**
513 * This singleton manages simulated Ajax responses. This allows application logic to be
514 * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
515 * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
516 * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
517 *
518 * The requires hooks are inserted when either the {@link #init} method is called or the
519 * first {@link Ext.ux.ajax.Simlet} is registered. For example:
520 *
521 * Ext.onReady(function () {
522 * initAjaxSim();
523 *
524 * // normal stuff
525 * });
526 *
527 * function initAjaxSim () {
528 * Ext.ux.ajax.SimManager.init({
529 * delay: 300
530 * }).register({
531 * '/app/data/url': {
532 * type: 'json', // use JsonSimlet (type is like xtype for components)
533 * data: [
534 * { foo: 42, bar: 'abc' },
535 * ...
536 * ]
537 * }
538 * });
539 * }
540 *
541 * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}. To make
542 * non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
543 * to the Ajax options:
544 *
545 * Ext.Ajax.request({
546 * url: 'page.php',
547 * nosim: true, // ignored by normal Ajax request
548 * params: {
549 * id: 1
550 * },
551 * success: function(response){
552 * var text = response.responseText;
553 * // process server response here
554 * }
555 * });
556 */\r
557Ext.define('Ext.ux.ajax.SimManager', {\r
558 singleton: true,\r
559 requires: [\r
560 'Ext.data.Connection',\r
561 'Ext.ux.ajax.SimXhr',\r
562 'Ext.ux.ajax.Simlet',\r
563 'Ext.ux.ajax.JsonSimlet'\r
564 ],\r
565 /**
566 * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
567 * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
568 * return 404. Set this to null to use real Ajax calls for non-matching URL's.
569 */\r
570 /**
571 * @cfg {String} defaultType
572 * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
573 * default is 'basic'.
574 */\r
575 defaultType: 'basic',\r
576 /**
577 * @cfg {Number} delay
578 * The number of milliseconds to delay before delivering a response to an async request.
579 */\r
580 delay: 150,\r
581 /**
582 * @property {Boolean} ready
583 * True once this singleton has initialized and applied its Ajax hooks.
584 * @private
585 */\r
586 ready: false,\r
587 constructor: function() {\r
588 this.simlets = [];\r
589 },\r
590 getSimlet: function(url) {\r
591 // Strip down to base URL (no query parameters or hash):\r
592 var me = this,\r
593 index = url.indexOf('?'),\r
594 simlets = me.simlets,\r
595 len = simlets.length,\r
596 i, simlet, simUrl, match;\r
597 if (index < 0) {\r
598 index = url.indexOf('#');\r
599 }\r
600 if (index > 0) {\r
601 url = url.substring(0, index);\r
602 }\r
603 for (i = 0; i < len; ++i) {\r
604 simlet = simlets[i];\r
605 simUrl = simlet.url;\r
606 if (simUrl instanceof RegExp) {\r
607 match = simUrl.test(url);\r
608 } else {\r
609 match = simUrl === url;\r
610 }\r
611 if (match) {\r
612 return simlet;\r
613 }\r
614 }\r
615 return me.defaultSimlet;\r
616 },\r
617 getXhr: function(method, url, options, async) {\r
618 var simlet = this.getSimlet(url);\r
619 if (simlet) {\r
620 return simlet.openRequest(method, url, options, async);\r
621 }\r
622 return null;\r
623 },\r
624 /**
625 * Initializes this singleton and applies configuration options.
626 * @param {Object} config An optional object with configuration properties to apply.
627 * @return {Ext.ux.ajax.SimManager} this
628 */\r
629 init: function(config) {\r
630 var me = this;\r
631 Ext.apply(me, config);\r
632 if (!me.ready) {\r
633 me.ready = true;\r
634 if (!('defaultSimlet' in me)) {\r
635 me.defaultSimlet = new Ext.ux.ajax.Simlet({\r
636 status: 404,\r
637 statusText: 'Not Found'\r
638 });\r
639 }\r
640 me._openRequest = Ext.data.Connection.prototype.openRequest;\r
641 Ext.data.request.Ajax.override({\r
642 openRequest: function(options, requestOptions, async) {\r
643 var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);\r
644 if (!xhr) {\r
645 xhr = this.callParent(arguments);\r
646 }\r
647 return xhr;\r
648 }\r
649 });\r
650 if (Ext.data.JsonP) {\r
651 Ext.data.JsonP.self.override({\r
652 createScript: function(url, params, options) {\r
653 var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),\r
654 script = !options.nosim && me.getXhr('GET', fullUrl, options, true);\r
655 if (!script) {\r
656 script = this.callParent(arguments);\r
657 }\r
658 return script;\r
659 },\r
660 loadScript: function(request) {\r
661 var script = request.script;\r
662 if (script.simlet) {\r
663 script.jsonpCallback = request.params[request.callbackKey];\r
664 script.send(null);\r
665 // Ext.data.JsonP will attempt dom removal of a script tag, so emulate its presence\r
666 request.script = document.createElement('script');\r
667 } else {\r
668 this.callParent(arguments);\r
669 }\r
670 }\r
671 });\r
672 }\r
673 }\r
674 return me;\r
675 },\r
676 openRequest: function(method, url, async) {\r
677 var opt = {\r
678 method: method,\r
679 url: url\r
680 };\r
681 return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);\r
682 },\r
683 /**
684 * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
685 * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
686 * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
687 * instances or configs.
688 */\r
689 register: function(simlet) {\r
690 var me = this;\r
691 me.init();\r
692 function reg(one) {\r
693 var simlet = one;\r
694 if (!simlet.isSimlet) {\r
695 simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);\r
696 }\r
697 me.simlets.push(simlet);\r
698 simlet.manager = me;\r
699 }\r
700 if (Ext.isArray(simlet)) {\r
701 Ext.each(simlet, reg);\r
702 } else if (simlet.isSimlet || simlet.url) {\r
703 reg(simlet);\r
704 } else {\r
705 Ext.Object.each(simlet, function(url, s) {\r
706 s.url = url;\r
707 reg(s);\r
708 });\r
709 }\r
710 return me;\r
711 }\r
712});\r
713\r
714/**
715 * This class simulates XML-based requests.
716 */\r
717Ext.define('Ext.ux.ajax.XmlSimlet', {\r
718 extend: 'Ext.ux.ajax.DataSimlet',\r
719 alias: 'simlet.xml',\r
720 /**
721 * This template is used to populate the XML response. The configuration of the Reader
722 * is available so that its `root` and `record` properties can be used as well as the
723 * `fields` of the associated `model`. But beyond that, the way these pieces are put
724 * together in the document requires the flexibility of a template.
725 */\r
726 xmlTpl: [\r
727 '<{root}>\n',\r
728 '<tpl for="data">',\r
729 ' <{parent.record}>\n',\r
730 '<tpl for="parent.fields">',\r
731 ' <{name}>{[parent[values.name]]}</{name}>\n',\r
732 '</tpl>',\r
733 ' </{parent.record}>\n',\r
734 '</tpl>',\r
735 '</{root}>'\r
736 ],\r
737 doGet: function(ctx) {\r
738 var me = this,\r
739 data = me.getData(ctx),\r
740 page = me.getPage(ctx, data),\r
741 proxy = ctx.xhr.options.operation.getProxy(),\r
742 reader = proxy && proxy.getReader(),\r
743 model = reader && reader.getModel(),\r
744 ret = me.callParent(arguments),\r
745 // pick up status/statusText\r
746 response = {\r
747 data: page,\r
748 reader: reader,\r
749 fields: model && model.fields,\r
750 root: reader && reader.getRootProperty(),\r
751 record: reader && reader.record\r
752 },\r
753 tpl, xml, doc;\r
754 if (ctx.groupSpec) {\r
755 response.summaryData = me.getSummary(ctx, data, page);\r
756 }\r
757 // If a straight Ajax request there won't be an xmlTpl.\r
758 if (me.xmlTpl) {\r
759 tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');\r
760 xml = tpl.apply(response);\r
761 } else {\r
762 xml = data;\r
763 }\r
764 if (typeof DOMParser != 'undefined') {\r
765 doc = (new DOMParser()).parseFromString(xml, "text/xml");\r
766 } else {\r
767 // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML\r
768 doc = new ActiveXObject("Microsoft.XMLDOM");\r
769 doc.async = false;\r
770 doc.loadXML(xml);\r
771 }\r
772 ret.responseText = xml;\r
773 ret.responseXML = doc;\r
774 return ret;\r
775 },\r
776 fixTree: function() {\r
777 this.callParent(arguments);\r
778 var buffer = [];\r
779 this.buildTreeXml(this.data, buffer);\r
780 this.data = buffer.join('');\r
781 },\r
782 buildTreeXml: function(nodes, buffer) {\r
783 var rootProperty = this.rootProperty,\r
784 recordProperty = this.recordProperty;\r
785 buffer.push('<', rootProperty, '>');\r
786 Ext.Array.forEach(nodes, function(node) {\r
787 buffer.push('<', recordProperty, '>');\r
788 for (var key in node) {\r
789 if (key == 'children') {\r
790 this.buildTreeXml(node.children, buffer);\r
791 } else {\r
792 buffer.push('<', key, '>', node[key], '</', key, '>');\r
793 }\r
794 }\r
795 buffer.push('</', recordProperty, '>');\r
796 });\r
797 buffer.push('</', rootProperty, '>');\r
798 }\r
799});\r
800\r
801/**
802 * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
803 */\r
804Ext.define('Ext.ux.event.Driver', {\r
805 extend: 'Ext.util.Observable',\r
806 active: null,\r
807 specialKeysByName: {\r
808 PGUP: 33,\r
809 PGDN: 34,\r
810 END: 35,\r
811 HOME: 36,\r
812 LEFT: 37,\r
813 UP: 38,\r
814 RIGHT: 39,\r
815 DOWN: 40\r
816 },\r
817 specialKeysByCode: {},\r
818 /**
819 * @event start
820 * Fires when this object is started.
821 * @param {Ext.ux.event.Driver} this
822 */\r
823 /**
824 * @event stop
825 * Fires when this object is stopped.
826 * @param {Ext.ux.event.Driver} this
827 */\r
828 getTextSelection: function(el) {\r
829 // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js\r
830 var doc = el.ownerDocument,\r
831 range, range2, start, end;\r
832 if (typeof el.selectionStart === "number") {\r
833 start = el.selectionStart;\r
834 end = el.selectionEnd;\r
835 } else if (doc.selection) {\r
836 range = doc.selection.createRange();\r
837 range2 = el.createTextRange();\r
838 range2.setEndPoint('EndToStart', range);\r
839 start = range2.text.length;\r
840 end = start + range.text.length;\r
841 }\r
842 return [\r
843 start,\r
844 end\r
845 ];\r
846 },\r
847 getTime: function() {\r
848 return new Date().getTime();\r
849 },\r
850 /**
851 * Returns the number of milliseconds since start was called.
852 */\r
853 getTimestamp: function() {\r
854 var d = this.getTime();\r
855 return d - this.startTime;\r
856 },\r
857 onStart: function() {},\r
858 onStop: function() {},\r
859 /**
860 * Starts this object. If this object is already started, nothing happens.
861 */\r
862 start: function() {\r
863 var me = this;\r
864 if (!me.active) {\r
865 me.active = new Date();\r
866 me.startTime = me.getTime();\r
867 me.onStart();\r
868 me.fireEvent('start', me);\r
869 }\r
870 },\r
871 /**
872 * Stops this object. If this object is not started, nothing happens.
873 */\r
874 stop: function() {\r
875 var me = this;\r
876 if (me.active) {\r
877 me.active = null;\r
878 me.onStop();\r
879 me.fireEvent('stop', me);\r
880 }\r
881 }\r
882}, function() {\r
883 var proto = this.prototype;\r
884 Ext.Object.each(proto.specialKeysByName, function(name, value) {\r
885 proto.specialKeysByCode[value] = name;\r
886 });\r
887});\r
888\r
889/**
890 * Event maker.
891 */\r
892Ext.define('Ext.ux.event.Maker', {\r
893 eventQueue: [],\r
894 startAfter: 500,\r
895 timerIncrement: 500,\r
896 currentTiming: 0,\r
897 constructor: function(config) {\r
898 var me = this;\r
899 me.currentTiming = me.startAfter;\r
900 if (!Ext.isArray(config)) {\r
901 config = [\r
902 config\r
903 ];\r
904 }\r
905 Ext.Array.each(config, function(item) {\r
906 item.el = item.el || 'el';\r
907 Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {\r
908 var event = {},\r
909 x, y, el;\r
910 if (!item.domQuery) {\r
911 el = cmp[item.el];\r
912 } else {\r
913 el = cmp.el.down(item.domQuery);\r
914 }\r
915 event.target = '#' + el.dom.id;\r
916 event.type = item.type;\r
917 event.button = config.button || 0;\r
918 x = el.getX() + (el.getWidth() / 2);\r
919 y = el.getY() + (el.getHeight() / 2);\r
920 event.xy = [\r
921 x,\r
922 y\r
923 ];\r
924 event.ts = me.currentTiming;\r
925 me.currentTiming += me.timerIncrement;\r
926 me.eventQueue.push(event);\r
927 });\r
928 if (item.screenshot) {\r
929 me.eventQueue[me.eventQueue.length - 1].screenshot = true;\r
930 }\r
931 });\r
932 return me.eventQueue;\r
933 }\r
934});\r
935\r
936/**
937 * @extends Ext.ux.event.Driver
938 * This class manages the playback of an array of "event descriptors". For details on the
939 * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
940 * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
941 *
942 * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
943 * {@link #method-start}. Like so:
944 *
945 * var player = Ext.create('Ext.ux.event.Player', {
946 * eventQueue: [ ... ],
947 * speed: 2, // play at 2x speed
948 * listeners: {
949 * stop: function () {
950 * player = null; // all done
951 * }
952 * }
953 * });
954 *
955 * player.start();
956 *
957 * A more complex use would be to incorporate keyframe generation after playing certain
958 * events.
959 *
960 * var player = Ext.create('Ext.ux.event.Player', {
961 * eventQueue: [ ... ],
962 * keyFrameEvents: {
963 * click: true
964 * },
965 * listeners: {
966 * stop: function () {
967 * // play has completed... probably time for another keyframe...
968 * player = null;
969 * },
970 * keyframe: onKeyFrame
971 * }
972 * });
973 *
974 * player.start();
975 *
976 * If a keyframe can be handled immediately (synchronously), the listener would be:
977 *
978 * function onKeyFrame () {
979 * handleKeyFrame();
980 * }
981 *
982 * If the keyframe event is always handled asynchronously, then the event listener is only
983 * a bit more:
984 *
985 * function onKeyFrame (p, eventDescriptor) {
986 * eventDescriptor.defer(); // pause event playback...
987 *
988 * handleKeyFrame(function () {
989 * eventDescriptor.finish(); // ...resume event playback
990 * });
991 * }
992 *
993 * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
994 * differently by browser), a slightly more complex listener is required.
995 *
996 * function onKeyFrame (p, eventDescriptor) {
997 * var async;
998 *
999 * handleKeyFrame(function () {
1000 * // either this callback is being called immediately by handleKeyFrame (in
1001 * // which case async is undefined) or it is being called later (in which case
1002 * // async will be true).
1003 *
1004 * if (async) {
1005 * eventDescriptor.finish();
1006 * } else {
1007 * async = false;
1008 * }
1009 * });
1010 *
1011 * // either the callback was called (and async is now false) or it was not
1012 * // called (and async remains undefined).
1013 *
1014 * if (async !== false) {
1015 * eventDescriptor.defer();
1016 * async = true; // let the callback know that we have gone async
1017 * }
1018 * }
1019 */\r
1020Ext.define('Ext.ux.event.Player', function(Player) {\r
1021 var defaults = {},\r
1022 mouseEvents = {},\r
1023 keyEvents = {},\r
1024 doc,\r
1025 //HTML events supported\r
1026 uiEvents = {},\r
1027 //events that bubble by default\r
1028 bubbleEvents = {\r
1029 //scroll: 1,\r
1030 resize: 1,\r
1031 reset: 1,\r
1032 submit: 1,\r
1033 change: 1,\r
1034 select: 1,\r
1035 error: 1,\r
1036 abort: 1\r
1037 };\r
1038 Ext.each([\r
1039 'click',\r
1040 'dblclick',\r
1041 'mouseover',\r
1042 'mouseout',\r
1043 'mousedown',\r
1044 'mouseup',\r
1045 'mousemove'\r
1046 ], function(type) {\r
1047 bubbleEvents[type] = defaults[type] = mouseEvents[type] = {\r
1048 bubbles: true,\r
1049 cancelable: (type != "mousemove"),\r
1050 // mousemove cannot be cancelled\r
1051 detail: 1,\r
1052 screenX: 0,\r
1053 screenY: 0,\r
1054 clientX: 0,\r
1055 clientY: 0,\r
1056 ctrlKey: false,\r
1057 altKey: false,\r
1058 shiftKey: false,\r
1059 metaKey: false,\r
1060 button: 0\r
1061 };\r
1062 });\r
1063 Ext.each([\r
1064 'keydown',\r
1065 'keyup',\r
1066 'keypress'\r
1067 ], function(type) {\r
1068 bubbleEvents[type] = defaults[type] = keyEvents[type] = {\r
1069 bubbles: true,\r
1070 cancelable: true,\r
1071 ctrlKey: false,\r
1072 altKey: false,\r
1073 shiftKey: false,\r
1074 metaKey: false,\r
1075 keyCode: 0,\r
1076 charCode: 0\r
1077 };\r
1078 });\r
1079 Ext.each([\r
1080 'blur',\r
1081 'change',\r
1082 'focus',\r
1083 'resize',\r
1084 'scroll',\r
1085 'select'\r
1086 ], function(type) {\r
1087 defaults[type] = uiEvents[type] = {\r
1088 bubbles: (type in bubbleEvents),\r
1089 cancelable: false,\r
1090 detail: 1\r
1091 };\r
1092 });\r
1093 var inputSpecialKeys = {\r
1094 8: function(target, start, end) {\r
1095 // backspace: 8,\r
1096 if (start < end) {\r
1097 target.value = target.value.substring(0, start) + target.value.substring(end);\r
1098 } else if (start > 0) {\r
1099 target.value = target.value.substring(0, --start) + target.value.substring(end);\r
1100 }\r
1101 this.setTextSelection(target, start, start);\r
1102 },\r
1103 46: function(target, start, end) {\r
1104 // delete: 46\r
1105 if (start < end) {\r
1106 target.value = target.value.substring(0, start) + target.value.substring(end);\r
1107 } else if (start < target.value.length - 1) {\r
1108 target.value = target.value.substring(0, start) + target.value.substring(start + 1);\r
1109 }\r
1110 this.setTextSelection(target, start, start);\r
1111 }\r
1112 };\r
1113 return {\r
1114 extend: 'Ext.ux.event.Driver',\r
1115 /**
1116 * @cfg {Array} eventQueue The event queue to playback. This must be provided before
1117 * the {@link #method-start} method is called.
1118 */\r
1119 /**
1120 * @cfg {Object} keyFrameEvents An object that describes the events that should generate
1121 * keyframe events. For example, `{ click: true }` would generate keyframe events after
1122 * each `click` event.
1123 */\r
1124 keyFrameEvents: {\r
1125 click: true\r
1126 },\r
1127 /**
1128 * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
1129 * to ignore animations. Default is true.
1130 */\r
1131 pauseForAnimations: true,\r
1132 /**
1133 * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
1134 * recorded speed). A value of 2 would playback at 2x speed.
1135 */\r
1136 speed: 1,\r
1137 stallTime: 0,\r
1138 _inputSpecialKeys: {\r
1139 INPUT: inputSpecialKeys,\r
1140 TEXTAREA: Ext.apply({}, //13: function (target, start, end) { // enter: 8,\r
1141 //TODO ?\r
1142 //}\r
1143 inputSpecialKeys)\r
1144 },\r
1145 tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,\r
1146 /**
1147 * @event beforeplay
1148 * Fires before an event is played.
1149 * @param {Ext.ux.event.Player} this
1150 * @param {Object} eventDescriptor The event descriptor about to be played.
1151 */\r
1152 /**
1153 * @event keyframe
1154 * Fires when this player reaches a keyframe. Typically, this is after events
1155 * like `click` are injected and any resulting animations have been completed.
1156 * @param {Ext.ux.event.Player} this
1157 * @param {Object} eventDescriptor The keyframe event descriptor.
1158 */\r
1159 constructor: function(config) {\r
1160 var me = this;\r
1161 me.callParent(arguments);\r
1162 me.timerFn = function() {\r
1163 me.onTick();\r
1164 };\r
1165 me.attachTo = me.attachTo || window;\r
1166 doc = me.attachTo.document;\r
1167 },\r
1168 /**
1169 * Returns the element given is XPath-like description.
1170 * @param {String} xpath The XPath-like description of the element.
1171 * @return {HTMLElement}
1172 */\r
1173 getElementFromXPath: function(xpath) {\r
1174 var me = this,\r
1175 parts = xpath.split('/'),\r
1176 regex = me.tagPathRegEx,\r
1177 i, n, m, count, tag, child,\r
1178 el = me.attachTo.document;\r
1179 el = (parts[0] == '~') ? el.body : el.getElementById(parts[0].substring(1));\r
1180 // remove '#'\r
1181 for (i = 1 , n = parts.length; el && i < n; ++i) {\r
1182 m = regex.exec(parts[i]);\r
1183 count = m[2] ? parseInt(m[2], 10) : 1;\r
1184 tag = m[1].toUpperCase();\r
1185 for (child = el.firstChild; child; child = child.nextSibling) {\r
1186 if (child.tagName == tag) {\r
1187 if (count == 1) {\r
1188 break;\r
1189 }\r
1190 --count;\r
1191 }\r
1192 }\r
1193 el = child;\r
1194 }\r
1195 return el;\r
1196 },\r
1197 // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in\r
1198 // the textarea value is two characters. This function corrects for that by converting a text offset into a\r
1199 // range character offset by subtracting one character for every line break in the textarea prior to the\r
1200 // offset\r
1201 offsetToRangeCharacterMove: function(el, offset) {\r
1202 return offset - (el.value.slice(0, offset).split("\r\n").length - 1);\r
1203 },\r
1204 setTextSelection: function(el, startOffset, endOffset) {\r
1205 // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js\r
1206 if (startOffset < 0) {\r
1207 startOffset += el.value.length;\r
1208 }\r
1209 if (endOffset == null) {\r
1210 endOffset = startOffset;\r
1211 }\r
1212 if (endOffset < 0) {\r
1213 endOffset += el.value.length;\r
1214 }\r
1215 if (typeof el.selectionStart === "number") {\r
1216 el.selectionStart = startOffset;\r
1217 el.selectionEnd = endOffset;\r
1218 } else {\r
1219 var range = el.createTextRange();\r
1220 var startCharMove = this.offsetToRangeCharacterMove(el, startOffset);\r
1221 range.collapse(true);\r
1222 if (startOffset == endOffset) {\r
1223 range.move("character", startCharMove);\r
1224 } else {\r
1225 range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));\r
1226 range.moveStart("character", startCharMove);\r
1227 }\r
1228 range.select();\r
1229 }\r
1230 },\r
1231 getTimeIndex: function() {\r
1232 var t = this.getTimestamp() - this.stallTime;\r
1233 return t * this.speed;\r
1234 },\r
1235 makeToken: function(eventDescriptor, signal) {\r
1236 var me = this,\r
1237 t0;\r
1238 eventDescriptor[signal] = true;\r
1239 eventDescriptor.defer = function() {\r
1240 eventDescriptor[signal] = false;\r
1241 t0 = me.getTime();\r
1242 };\r
1243 eventDescriptor.finish = function() {\r
1244 eventDescriptor[signal] = true;\r
1245 me.stallTime += me.getTime() - t0;\r
1246 me.schedule();\r
1247 };\r
1248 },\r
1249 /**
1250 * This method is called after an event has been played to prepare for the next event.
1251 * @param {Object} eventDescriptor The descriptor of the event just played.
1252 */\r
1253 nextEvent: function(eventDescriptor) {\r
1254 var me = this,\r
1255 index = ++me.queueIndex;\r
1256 // keyframe events are inserted after a keyFrameEvent is played.\r
1257 if (me.keyFrameEvents[eventDescriptor.type]) {\r
1258 Ext.Array.insert(me.eventQueue, index, [\r
1259 {\r
1260 keyframe: true,\r
1261 ts: eventDescriptor.ts\r
1262 }\r
1263 ]);\r
1264 }\r
1265 },\r
1266 /**
1267 * This method returns the event descriptor at the front of the queue. This does not
1268 * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
1269 * is called).
1270 */\r
1271 peekEvent: function() {\r
1272 return this.eventQueue[this.queueIndex] || null;\r
1273 },\r
1274 /**
1275 * Replaces an event in the queue with an array of events. This is often used to roll
1276 * up a multi-step pseudo-event and expand it just-in-time to be played. The process
1277 * for doing this in a derived class would be this:
1278 *
1279 * Ext.define('My.Player', {
1280 * extend: 'Ext.ux.event.Player',
1281 *
1282 * peekEvent: function () {
1283 * var event = this.callParent();
1284 *
1285 * if (event.multiStepSpecial) {
1286 * this.replaceEvent(null, [
1287 * ... expand to actual events
1288 * ]);
1289 *
1290 * event = this.callParent(); // get the new next event
1291 * }
1292 *
1293 * return event;
1294 * }
1295 * });
1296 *
1297 * This method ensures that the `beforeplay` hook (if any) from the replaced event is
1298 * placed on the first new event and the `afterplay` hook (if any) is placed on the
1299 * last new event.
1300 *
1301 * @param {Number} index The queue index to replace. Pass `null` to replace the event
1302 * at the current `queueIndex`.
1303 * @param {Event[]} events The array of events with which to replace the specified
1304 * event.
1305 */\r
1306 replaceEvent: function(index, events) {\r
1307 for (var t,\r
1308 i = 0,\r
1309 n = events.length; i < n; ++i) {\r
1310 if (i) {\r
1311 t = events[i - 1];\r
1312 delete t.afterplay;\r
1313 delete t.screenshot;\r
1314 delete events[i].beforeplay;\r
1315 }\r
1316 }\r
1317 Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);\r
1318 },\r
1319 /**
1320 * This method dequeues and injects events until it has arrived at the time index. If
1321 * no events are ready (based on the time index), this method does nothing.
1322 * @return {Boolean} True if there is more to do; false if not (at least for now).
1323 */\r
1324 processEvents: function() {\r
1325 var me = this,\r
1326 animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,\r
1327 eventDescriptor;\r
1328 while ((eventDescriptor = me.peekEvent()) !== null) {\r
1329 if (animations && animations.getCount()) {\r
1330 return true;\r
1331 }\r
1332 if (eventDescriptor.keyframe) {\r
1333 if (!me.processKeyFrame(eventDescriptor)) {\r
1334 return false;\r
1335 }\r
1336 me.nextEvent(eventDescriptor);\r
1337 } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {\r
1338 me.nextEvent(eventDescriptor);\r
1339 } else {\r
1340 return true;\r
1341 }\r
1342 }\r
1343 me.stop();\r
1344 return false;\r
1345 },\r
1346 /**
1347 * This method is called when a keyframe is reached. This will fire the keyframe event.
1348 * If the keyframe has been handled, true is returned. Otherwise, false is returned.
1349 * @param {Object} eventDescriptor The event descriptor of the keyframe.
1350 * @return {Boolean} True if the keyframe was handled, false if not.
1351 */\r
1352 processKeyFrame: function(eventDescriptor) {\r
1353 var me = this;\r
1354 // only fire keyframe event (and setup the eventDescriptor) once...\r
1355 if (!eventDescriptor.defer) {\r
1356 me.makeToken(eventDescriptor, 'done');\r
1357 me.fireEvent('keyframe', me, eventDescriptor);\r
1358 }\r
1359 return eventDescriptor.done;\r
1360 },\r
1361 /**
1362 * Called to inject the given event on the specified target.
1363 * @param {HTMLElement} target The target of the event.
1364 * @param {Object} event The event to inject. The properties of this object should be
1365 * those of standard DOM events but vary based on the `type` property. For details on
1366 * event types and their properties, see the class documentation.
1367 */\r
1368 injectEvent: function(target, event) {\r
1369 var me = this,\r
1370 type = event.type,\r
1371 options = Ext.apply({}, event, defaults[type]),\r
1372 handler;\r
1373 if (type === 'type') {\r
1374 handler = me._inputSpecialKeys[target.tagName];\r
1375 if (handler) {\r
1376 return me.injectTypeInputEvent(target, event, handler);\r
1377 }\r
1378 return me.injectTypeEvent(target, event);\r
1379 }\r
1380 if (type === 'focus' && target.focus) {\r
1381 target.focus();\r
1382 return true;\r
1383 }\r
1384 if (type === 'blur' && target.blur) {\r
1385 target.blur();\r
1386 return true;\r
1387 }\r
1388 if (type === 'scroll') {\r
1389 target.scrollLeft = event.pos[0];\r
1390 target.scrollTop = event.pos[1];\r
1391 return true;\r
1392 }\r
1393 if (type === 'mduclick') {\r
1394 return me.injectEvent(target, Ext.applyIf({\r
1395 type: 'mousedown'\r
1396 }, event)) && me.injectEvent(target, Ext.applyIf({\r
1397 type: 'mouseup'\r
1398 }, event)) && me.injectEvent(target, Ext.applyIf({\r
1399 type: 'click'\r
1400 }, event));\r
1401 }\r
1402 if (mouseEvents[type]) {\r
1403 return Player.injectMouseEvent(target, options, me.attachTo);\r
1404 }\r
1405 if (keyEvents[type]) {\r
1406 return Player.injectKeyEvent(target, options, me.attachTo);\r
1407 }\r
1408 if (uiEvents[type]) {\r
1409 return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);\r
1410 }\r
1411 return false;\r
1412 },\r
1413 injectTypeEvent: function(target, event) {\r
1414 var me = this,\r
1415 text = event.text,\r
1416 xlat = [],\r
1417 ch, chUp, i, n, sel, upper, isInput;\r
1418 if (text) {\r
1419 delete event.text;\r
1420 upper = text.toUpperCase();\r
1421 for (i = 0 , n = text.length; i < n; ++i) {\r
1422 ch = text.charCodeAt(i);\r
1423 chUp = upper.charCodeAt(i);\r
1424 xlat.push(Ext.applyIf({\r
1425 type: 'keydown',\r
1426 charCode: chUp,\r
1427 keyCode: chUp\r
1428 }, event), Ext.applyIf({\r
1429 type: 'keypress',\r
1430 charCode: ch,\r
1431 keyCode: ch\r
1432 }, event), Ext.applyIf({\r
1433 type: 'keyup',\r
1434 charCode: chUp,\r
1435 keyCode: chUp\r
1436 }, event));\r
1437 }\r
1438 } else {\r
1439 xlat.push(Ext.applyIf({\r
1440 type: 'keydown',\r
1441 charCode: event.keyCode\r
1442 }, event), Ext.applyIf({\r
1443 type: 'keyup',\r
1444 charCode: event.keyCode\r
1445 }, event));\r
1446 }\r
1447 for (i = 0 , n = xlat.length; i < n; ++i) {\r
1448 me.injectEvent(target, xlat[i]);\r
1449 }\r
1450 return true;\r
1451 },\r
1452 injectTypeInputEvent: function(target, event, handler) {\r
1453 var me = this,\r
1454 text = event.text,\r
1455 sel, n;\r
1456 if (handler) {\r
1457 sel = me.getTextSelection(target);\r
1458 if (text) {\r
1459 n = sel[0];\r
1460 target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);\r
1461 n += text.length;\r
1462 me.setTextSelection(target, n, n);\r
1463 } else {\r
1464 if (!(handler = handler[event.keyCode])) {\r
1465 // no handler for the special key for this element\r
1466 if ('caret' in event) {\r
1467 me.setTextSelection(target, event.caret, event.caret);\r
1468 } else if (event.selection) {\r
1469 me.setTextSelection(target, event.selection[0], event.selection[1]);\r
1470 }\r
1471 return me.injectTypeEvent(target, event);\r
1472 }\r
1473 handler.call(this, target, sel[0], sel[1]);\r
1474 return true;\r
1475 }\r
1476 }\r
1477 return true;\r
1478 },\r
1479 playEvent: function(eventDescriptor) {\r
1480 var me = this,\r
1481 target = me.getElementFromXPath(eventDescriptor.target),\r
1482 event;\r
1483 if (!target) {\r
1484 // not present (yet)... wait for element present...\r
1485 // TODO - need a timeout here\r
1486 return false;\r
1487 }\r
1488 if (!me.playEventHook(eventDescriptor, 'beforeplay')) {\r
1489 return false;\r
1490 }\r
1491 if (!eventDescriptor.injected) {\r
1492 eventDescriptor.injected = true;\r
1493 event = me.translateEvent(eventDescriptor, target);\r
1494 me.injectEvent(target, event);\r
1495 }\r
1496 return me.playEventHook(eventDescriptor, 'afterplay');\r
1497 },\r
1498 playEventHook: function(eventDescriptor, hookName) {\r
1499 var me = this,\r
1500 doneName = hookName + '.done',\r
1501 firedName = hookName + '.fired',\r
1502 hook = eventDescriptor[hookName];\r
1503 if (hook && !eventDescriptor[doneName]) {\r
1504 if (!eventDescriptor[firedName]) {\r
1505 eventDescriptor[firedName] = true;\r
1506 me.makeToken(eventDescriptor, doneName);\r
1507 if (me.eventScope && Ext.isString(hook)) {\r
1508 hook = me.eventScope[hook];\r
1509 }\r
1510 if (hook) {\r
1511 hook.call(me.eventScope || me, eventDescriptor);\r
1512 }\r
1513 }\r
1514 return false;\r
1515 }\r
1516 return true;\r
1517 },\r
1518 schedule: function() {\r
1519 var me = this;\r
1520 if (!me.timer) {\r
1521 me.timer = setTimeout(me.timerFn, 10);\r
1522 }\r
1523 },\r
1524 _translateAcross: [\r
1525 'type',\r
1526 'button',\r
1527 'charCode',\r
1528 'keyCode',\r
1529 'caret',\r
1530 'pos',\r
1531 'text',\r
1532 'selection'\r
1533 ],\r
1534 translateEvent: function(eventDescriptor, target) {\r
1535 var me = this,\r
1536 event = {},\r
1537 modKeys = eventDescriptor.modKeys || '',\r
1538 names = me._translateAcross,\r
1539 i = names.length,\r
1540 name, xy;\r
1541 while (i--) {\r
1542 name = names[i];\r
1543 if (name in eventDescriptor) {\r
1544 event[name] = eventDescriptor[name];\r
1545 }\r
1546 }\r
1547 event.altKey = modKeys.indexOf('A') > 0;\r
1548 event.ctrlKey = modKeys.indexOf('C') > 0;\r
1549 event.metaKey = modKeys.indexOf('M') > 0;\r
1550 event.shiftKey = modKeys.indexOf('S') > 0;\r
1551 if (target && 'x' in eventDescriptor) {\r
1552 xy = Ext.fly(target).getXY();\r
1553 xy[0] += eventDescriptor.x;\r
1554 xy[1] += eventDescriptor.y;\r
1555 } else if ('x' in eventDescriptor) {\r
1556 xy = [\r
1557 eventDescriptor.x,\r
1558 eventDescriptor.y\r
1559 ];\r
1560 } else if ('px' in eventDescriptor) {\r
1561 xy = [\r
1562 eventDescriptor.px,\r
1563 eventDescriptor.py\r
1564 ];\r
1565 }\r
1566 if (xy) {\r
1567 event.clientX = event.screenX = xy[0];\r
1568 event.clientY = event.screenY = xy[1];\r
1569 }\r
1570 if (eventDescriptor.key) {\r
1571 event.keyCode = me.specialKeysByName[eventDescriptor.key];\r
1572 }\r
1573 if (eventDescriptor.type === 'wheel') {\r
1574 if ('onwheel' in me.attachTo.document) {\r
1575 event.wheelX = eventDescriptor.dx;\r
1576 event.wheelY = eventDescriptor.dy;\r
1577 } else {\r
1578 event.type = 'mousewheel';\r
1579 event.wheelDeltaX = -40 * eventDescriptor.dx;\r
1580 event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;\r
1581 }\r
1582 }\r
1583 return event;\r
1584 },\r
1585 //---------------------------------\r
1586 // Driver overrides\r
1587 onStart: function() {\r
1588 var me = this;\r
1589 me.queueIndex = 0;\r
1590 me.schedule();\r
1591 },\r
1592 onStop: function() {\r
1593 var me = this;\r
1594 if (me.timer) {\r
1595 clearTimeout(me.timer);\r
1596 me.timer = null;\r
1597 }\r
1598 },\r
1599 //---------------------------------\r
1600 onTick: function() {\r
1601 var me = this;\r
1602 me.timer = null;\r
1603 if (me.processEvents()) {\r
1604 me.schedule();\r
1605 }\r
1606 },\r
1607 statics: {\r
1608 ieButtonCodeMap: {\r
1609 0: 1,\r
1610 1: 4,\r
1611 2: 2\r
1612 },\r
1613 /**
1614 * Injects a key event using the given event information to populate the event
1615 * object.
1616 *
1617 * **Note:** `keydown` causes Safari 2.x to crash.
1618 *
1619 * @param {HTMLElement} target The target of the given event.
1620 * @param {Object} options Object object containing all of the event injection
1621 * options.
1622 * @param {String} options.type The type of event to fire. This can be any one of
1623 * the following: `keyup`, `keydown` and `keypress`.
1624 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
1625 * DOM Level 3 specifies that all key events bubble by default.
1626 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
1627 * using `preventDefault`. DOM Level 3 specifies that all key events can be
1628 * cancelled.
1629 * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
1630 * pressed while the event is firing.
1631 * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
1632 * pressed while the event is firing.
1633 * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
1634 * pressed while the event is firing.
1635 * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
1636 * pressed while the event is firing.
1637 * @param {Number} [options.keyCode=0] The code for the key that is in use.
1638 * @param {Number} [options.charCode=0] The Unicode code for the character
1639 * associated with the key being used.
1640 * @param {Window} [view=window] The view containing the target. This is typically
1641 * the window object.
1642 * @private
1643 */\r
1644 injectKeyEvent: function(target, options, view) {\r
1645 var type = options.type,\r
1646 customEvent = null;\r
1647 if (type === 'textevent') {\r
1648 type = 'keypress';\r
1649 }\r
1650 view = view || window;\r
1651 //check for DOM-compliant browsers first\r
1652 if (doc.createEvent) {\r
1653 try {\r
1654 customEvent = doc.createEvent("KeyEvents");\r
1655 // Interesting problem: Firefox implemented a non-standard\r
1656 // version of initKeyEvent() based on DOM Level 2 specs.\r
1657 // Key event was removed from DOM Level 2 and re-introduced\r
1658 // in DOM Level 3 with a different interface. Firefox is the\r
1659 // only browser with any implementation of Key Events, so for\r
1660 // now, assume it's Firefox if the above line doesn't error.\r
1661 // @TODO: Decipher between Firefox's implementation and a correct one.\r
1662 customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);\r
1663 } catch (ex) {\r
1664 // If it got here, that means key events aren't officially supported. \r
1665 // Safari/WebKit is a real problem now. WebKit 522 won't let you\r
1666 // set keyCode, charCode, or other properties if you use a\r
1667 // UIEvent, so we first must try to create a generic event. The\r
1668 // fun part is that this will throw an error on Safari 2.x. The\r
1669 // end result is that we need another try...catch statement just to\r
1670 // deal with this mess.\r
1671 try {\r
1672 //try to create generic event - will fail in Safari 2.x\r
1673 customEvent = doc.createEvent("Events");\r
1674 } catch (uierror) {\r
1675 //the above failed, so create a UIEvent for Safari 2.x\r
1676 customEvent = doc.createEvent("UIEvents");\r
1677 } finally {\r
1678 customEvent.initEvent(type, options.bubbles, options.cancelable);\r
1679 customEvent.view = view;\r
1680 customEvent.altKey = options.altKey;\r
1681 customEvent.ctrlKey = options.ctrlKey;\r
1682 customEvent.shiftKey = options.shiftKey;\r
1683 customEvent.metaKey = options.metaKey;\r
1684 customEvent.keyCode = options.keyCode;\r
1685 customEvent.charCode = options.charCode;\r
1686 }\r
1687 }\r
1688 target.dispatchEvent(customEvent);\r
1689 } else if (doc.createEventObject) {\r
1690 //IE\r
1691 customEvent = doc.createEventObject();\r
1692 customEvent.bubbles = options.bubbles;\r
1693 customEvent.cancelable = options.cancelable;\r
1694 customEvent.view = view;\r
1695 customEvent.ctrlKey = options.ctrlKey;\r
1696 customEvent.altKey = options.altKey;\r
1697 customEvent.shiftKey = options.shiftKey;\r
1698 customEvent.metaKey = options.metaKey;\r
1699 // IE doesn't support charCode explicitly. CharCode should\r
1700 // take precedence over any keyCode value for accurate\r
1701 // representation.\r
1702 customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;\r
1703 target.fireEvent("on" + type, customEvent);\r
1704 } else {\r
1705 return false;\r
1706 }\r
1707 return true;\r
1708 },\r
1709 /**
1710 * Injects a mouse event using the given event information to populate the event
1711 * object.
1712 *
1713 * @param {HTMLElement} target The target of the given event.
1714 * @param {Object} options Object object containing all of the event injection
1715 * options.
1716 * @param {String} options.type The type of event to fire. This can be any one of
1717 * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
1718 * `mouseover` and `mousemove`.
1719 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
1720 * DOM Level 2 specifies that all mouse events bubble by default.
1721 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
1722 * using `preventDefault`. DOM Level 2 specifies that all mouse events except
1723 * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
1724 * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
1725 * pressed while the event is firing.
1726 * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
1727 * pressed while the event is firing.
1728 * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
1729 * pressed while the event is firing.
1730 * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
1731 * pressed while the event is firing.
1732 * @param {Number} [options.detail=1] The number of times the mouse button has
1733 * been used.
1734 * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
1735 * the event occurred.
1736 * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
1737 * the event occurred.
1738 * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
1739 * the event occurred.
1740 * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
1741 * the event occurred.
1742 * @param {Number} [options.button=0] The button being pressed while the event is
1743 * executing. The value should be 0 for the primary mouse button (typically the
1744 * left button), 1 for the tertiary mouse button (typically the middle button),
1745 * and 2 for the secondary mouse button (typically the right button).
1746 * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
1747 * is the element that the mouse has moved to. For `mouseover` events, this is
1748 * the element that the mouse has moved from. This argument is ignored for all
1749 * other events.
1750 * @param {Window} [view=window] The view containing the target. This is typically
1751 * the window object.
1752 * @private
1753 */\r
1754 injectMouseEvent: function(target, options, view) {\r
1755 var type = options.type,\r
1756 customEvent = null;\r
1757 view = view || window;\r
1758 //check for DOM-compliant browsers first\r
1759 if (doc.createEvent) {\r
1760 customEvent = doc.createEvent("MouseEvents");\r
1761 //Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()\r
1762 if (customEvent.initMouseEvent) {\r
1763 customEvent.initMouseEvent(type, options.bubbles, options.cancelable, view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget);\r
1764 } else {\r
1765 //Safari\r
1766 //the closest thing available in Safari 2.x is UIEvents\r
1767 customEvent = doc.createEvent("UIEvents");\r
1768 customEvent.initEvent(type, options.bubbles, options.cancelable);\r
1769 customEvent.view = view;\r
1770 customEvent.detail = options.detail;\r
1771 customEvent.screenX = options.screenX;\r
1772 customEvent.screenY = options.screenY;\r
1773 customEvent.clientX = options.clientX;\r
1774 customEvent.clientY = options.clientY;\r
1775 customEvent.ctrlKey = options.ctrlKey;\r
1776 customEvent.altKey = options.altKey;\r
1777 customEvent.metaKey = options.metaKey;\r
1778 customEvent.shiftKey = options.shiftKey;\r
1779 customEvent.button = options.button;\r
1780 customEvent.relatedTarget = options.relatedTarget;\r
1781 }\r
1782 /*
1783 * Check to see if relatedTarget has been assigned. Firefox
1784 * versions less than 2.0 don't allow it to be assigned via
1785 * initMouseEvent() and the property is readonly after event
1786 * creation, so in order to keep YAHOO.util.getRelatedTarget()
1787 * working, assign to the IE proprietary toElement property
1788 * for mouseout event and fromElement property for mouseover
1789 * event.
1790 */\r
1791 if (options.relatedTarget && !customEvent.relatedTarget) {\r
1792 if (type == "mouseout") {\r
1793 customEvent.toElement = options.relatedTarget;\r
1794 } else if (type == "mouseover") {\r
1795 customEvent.fromElement = options.relatedTarget;\r
1796 }\r
1797 }\r
1798 target.dispatchEvent(customEvent);\r
1799 } else if (doc.createEventObject) {\r
1800 //IE\r
1801 customEvent = doc.createEventObject();\r
1802 customEvent.bubbles = options.bubbles;\r
1803 customEvent.cancelable = options.cancelable;\r
1804 customEvent.view = view;\r
1805 customEvent.detail = options.detail;\r
1806 customEvent.screenX = options.screenX;\r
1807 customEvent.screenY = options.screenY;\r
1808 customEvent.clientX = options.clientX;\r
1809 customEvent.clientY = options.clientY;\r
1810 customEvent.ctrlKey = options.ctrlKey;\r
1811 customEvent.altKey = options.altKey;\r
1812 customEvent.metaKey = options.metaKey;\r
1813 customEvent.shiftKey = options.shiftKey;\r
1814 customEvent.button = Player.ieButtonCodeMap[options.button] || 0;\r
1815 /*
1816 * Have to use relatedTarget because IE won't allow assignment
1817 * to toElement or fromElement on generic events. This keeps
1818 * YAHOO.util.customEvent.getRelatedTarget() functional.
1819 */\r
1820 customEvent.relatedTarget = options.relatedTarget;\r
1821 target.fireEvent('on' + type, customEvent);\r
1822 } else {\r
1823 return false;\r
1824 }\r
1825 return true;\r
1826 },\r
1827 /**
1828 * Injects a UI event using the given event information to populate the event
1829 * object.
1830 *
1831 * @param {HTMLElement} target The target of the given event.
1832 * @param {Object} options
1833 * @param {String} options.type The type of event to fire. This can be any one of
1834 * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
1835 * `mouseover` and `mousemove`.
1836 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
1837 * DOM Level 2 specifies that all mouse events bubble by default.
1838 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
1839 * using `preventDefault`. DOM Level 2 specifies that all mouse events except
1840 * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
1841 * @param {Number} [options.detail=1] The number of times the mouse button has been
1842 * used.
1843 * @param {Window} [view=window] The view containing the target. This is typically
1844 * the window object.
1845 * @private
1846 */\r
1847 injectUIEvent: function(target, options, view) {\r
1848 var customEvent = null;\r
1849 view = view || window;\r
1850 //check for DOM-compliant browsers first\r
1851 if (doc.createEvent) {\r
1852 //just a generic UI Event object is needed\r
1853 customEvent = doc.createEvent("UIEvents");\r
1854 customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);\r
1855 target.dispatchEvent(customEvent);\r
1856 } else if (doc.createEventObject) {\r
1857 //IE\r
1858 customEvent = doc.createEventObject();\r
1859 customEvent.bubbles = options.bubbles;\r
1860 customEvent.cancelable = options.cancelable;\r
1861 customEvent.view = view;\r
1862 customEvent.detail = options.detail;\r
1863 target.fireEvent("on" + options.type, customEvent);\r
1864 } else {\r
1865 return false;\r
1866 }\r
1867 return true;\r
1868 }\r
1869 }\r
1870 };\r
1871});\r
1872// statics\r
1873\r
1874/**
1875 * @extends Ext.ux.event.Driver
1876 * Event recorder.
1877 */\r
1878Ext.define('Ext.ux.event.Recorder', function(Recorder) {\r
1879 function apply() {\r
1880 var a = arguments,\r
1881 n = a.length,\r
1882 obj = {\r
1883 kind: 'other'\r
1884 },\r
1885 i;\r
1886 for (i = 0; i < n; ++i) {\r
1887 Ext.apply(obj, arguments[i]);\r
1888 }\r
1889 if (obj.alt && !obj.event) {\r
1890 obj.event = obj.alt;\r
1891 }\r
1892 return obj;\r
1893 }\r
1894 function key(extra) {\r
1895 return apply({\r
1896 kind: 'keyboard',\r
1897 modKeys: true,\r
1898 key: true\r
1899 }, extra);\r
1900 }\r
1901 function mouse(extra) {\r
1902 return apply({\r
1903 kind: 'mouse',\r
1904 button: true,\r
1905 modKeys: true,\r
1906 xy: true\r
1907 }, extra);\r
1908 }\r
1909 var eventsToRecord = {\r
1910 keydown: key(),\r
1911 keypress: key(),\r
1912 keyup: key(),\r
1913 dragmove: mouse({\r
1914 alt: 'mousemove',\r
1915 pageCoords: true,\r
1916 whileDrag: true\r
1917 }),\r
1918 mousemove: mouse({\r
1919 pageCoords: true\r
1920 }),\r
1921 mouseover: mouse(),\r
1922 mouseout: mouse(),\r
1923 click: mouse(),\r
1924 wheel: mouse({\r
1925 wheel: true\r
1926 }),\r
1927 mousedown: mouse({\r
1928 press: true\r
1929 }),\r
1930 mouseup: mouse({\r
1931 release: true\r
1932 }),\r
1933 scroll: apply({\r
1934 listen: false\r
1935 }),\r
1936 focus: apply(),\r
1937 blur: apply()\r
1938 };\r
1939 for (var key in eventsToRecord) {\r
1940 if (!eventsToRecord[key].event) {\r
1941 eventsToRecord[key].event = key;\r
1942 }\r
1943 }\r
1944 eventsToRecord.wheel.event = null;\r
1945 // must detect later\r
1946 return {\r
1947 extend: 'Ext.ux.event.Driver',\r
1948 /**
1949 * @event add
1950 * Fires when an event is added to the recording.
1951 * @param {Ext.ux.event.Recorder} this
1952 * @param {Object} eventDescriptor The event descriptor.
1953 */\r
1954 /**
1955 * @event coalesce
1956 * Fires when an event is coalesced. This edits the tail of the recorded
1957 * event list.
1958 * @param {Ext.ux.event.Recorder} this
1959 * @param {Object} eventDescriptor The event descriptor that was coalesced.
1960 */\r
1961 eventsToRecord: eventsToRecord,\r
1962 ignoreIdRegEx: /ext-gen(?:\d+)/,\r
1963 inputRe: /^(input|textarea)$/i,\r
1964 constructor: function(config) {\r
1965 var me = this,\r
1966 events = config && config.eventsToRecord;\r
1967 if (events) {\r
1968 me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate\r
1969 events);\r
1970 // and merge\r
1971 delete config.eventsToRecord;\r
1972 }\r
1973 // don't smash\r
1974 me.callParent(arguments);\r
1975 me.clear();\r
1976 me.modKeys = [];\r
1977 me.attachTo = me.attachTo || window;\r
1978 },\r
1979 clear: function() {\r
1980 this.eventsRecorded = [];\r
1981 },\r
1982 listenToEvent: function(event) {\r
1983 var me = this,\r
1984 el = me.attachTo.document.body,\r
1985 fn = function() {\r
1986 return me.onEvent.apply(me, arguments);\r
1987 },\r
1988 cleaner = {};\r
1989 if (el.attachEvent && el.ownerDocument.documentMode < 10) {\r
1990 event = 'on' + event;\r
1991 el.attachEvent(event, fn);\r
1992 cleaner.destroy = function() {\r
1993 if (fn) {\r
1994 el.detachEvent(event, fn);\r
1995 fn = null;\r
1996 }\r
1997 };\r
1998 } else {\r
1999 el.addEventListener(event, fn, true);\r
2000 cleaner.destroy = function() {\r
2001 if (fn) {\r
2002 el.removeEventListener(event, fn, true);\r
2003 fn = null;\r
2004 }\r
2005 };\r
2006 }\r
2007 return cleaner;\r
2008 },\r
2009 coalesce: function(rec, ev) {\r
2010 var me = this,\r
2011 events = me.eventsRecorded,\r
2012 length = events.length,\r
2013 tail = length && events[length - 1],\r
2014 tail2 = (length > 1) && events[length - 2],\r
2015 tail3 = (length > 2) && events[length - 3];\r
2016 if (!tail) {\r
2017 return false;\r
2018 }\r
2019 if (rec.type === 'mousemove') {\r
2020 if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {\r
2021 rec.ts = tail.ts;\r
2022 events[length - 1] = rec;\r
2023 return true;\r
2024 }\r
2025 } else if (rec.type === 'click') {\r
2026 if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {\r
2027 if (rec.button == tail.button && rec.button == tail2.button && rec.target == tail.target && rec.target == tail2.target && me.samePt(rec, tail) && me.samePt(rec, tail2)) {\r
2028 events.pop();\r
2029 // remove mouseup\r
2030 tail2.type = 'mduclick';\r
2031 return true;\r
2032 }\r
2033 }\r
2034 } else if (rec.type === 'keyup') {\r
2035 // tail3 = { type: "type", text: "..." },\r
2036 // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },\r
2037 // tail = { type: "keypress", charCode: 97, keyCode: 97 },\r
2038 // rec = { type: "keyup", charCode: 65, keyCode: 65 },\r
2039 if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {\r
2040 if (rec.target === tail.target && rec.target === tail2.target) {\r
2041 events.pop();\r
2042 // remove keypress\r
2043 tail2.type = 'type';\r
2044 tail2.text = String.fromCharCode(tail.charCode);\r
2045 delete tail2.charCode;\r
2046 delete tail2.keyCode;\r
2047 if (tail3 && tail3.type === 'type') {\r
2048 if (tail3.text && tail3.target === tail2.target) {\r
2049 tail3.text += tail2.text;\r
2050 events.pop();\r
2051 }\r
2052 }\r
2053 return true;\r
2054 }\r
2055 }\r
2056 // tail = { type: "keydown", charCode: 40, keyCode: 40 },\r
2057 // rec = { type: "keyup", charCode: 40, keyCode: 40 },\r
2058 else if (me.completeKeyStroke(tail, rec)) {\r
2059 tail.type = 'type';\r
2060 me.completeSpecialKeyStroke(ev.target, tail, rec);\r
2061 return true;\r
2062 }\r
2063 // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },\r
2064 // tail = { type: "scroll", ... },\r
2065 // rec = { type: "keyup", charCode: 40, keyCode: 40 },\r
2066 else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {\r
2067 tail2.type = 'type';\r
2068 me.completeSpecialKeyStroke(ev.target, tail2, rec);\r
2069 // swap the order of type and scroll events\r
2070 events.pop();\r
2071 events.pop();\r
2072 events.push(tail, tail2);\r
2073 return true;\r
2074 }\r
2075 }\r
2076 return false;\r
2077 },\r
2078 completeKeyStroke: function(down, up) {\r
2079 if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {\r
2080 delete down.charCode;\r
2081 return true;\r
2082 }\r
2083 return false;\r
2084 },\r
2085 completeSpecialKeyStroke: function(target, down, up) {\r
2086 var key = this.specialKeysByCode[up.keyCode];\r
2087 if (key && this.inputRe.test(target.tagName)) {\r
2088 // home,end,arrow keys + shift get crazy, so encode selection/caret\r
2089 delete down.keyCode;\r
2090 down.key = key;\r
2091 down.selection = this.getTextSelection(target);\r
2092 if (down.selection[0] === down.selection[1]) {\r
2093 down.caret = down.selection[0];\r
2094 delete down.selection;\r
2095 }\r
2096 return true;\r
2097 }\r
2098 return false;\r
2099 },\r
2100 getElementXPath: function(el) {\r
2101 var me = this,\r
2102 good = false,\r
2103 xpath = [],\r
2104 count, sibling, t, tag;\r
2105 for (t = el; t; t = t.parentNode) {\r
2106 if (t == me.attachTo.document.body) {\r
2107 xpath.unshift('~');\r
2108 good = true;\r
2109 break;\r
2110 }\r
2111 if (t.id && !me.ignoreIdRegEx.test(t.id)) {\r
2112 xpath.unshift('#' + t.id);\r
2113 good = true;\r
2114 break;\r
2115 }\r
2116 for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {\r
2117 if (sibling.tagName == t.tagName) {\r
2118 ++count;\r
2119 }\r
2120 }\r
2121 tag = t.tagName.toLowerCase();\r
2122 if (count < 2) {\r
2123 xpath.unshift(tag);\r
2124 } else {\r
2125 xpath.unshift(tag + '[' + count + ']');\r
2126 }\r
2127 }\r
2128 return good ? xpath.join('/') : null;\r
2129 },\r
2130 getRecordedEvents: function() {\r
2131 return this.eventsRecorded;\r
2132 },\r
2133 onEvent: function(ev) {\r
2134 var me = this,\r
2135 e = new Ext.event.Event(ev),\r
2136 info = me.eventsToRecord[e.type],\r
2137 root, modKeys, elXY,\r
2138 rec = {\r
2139 type: e.type,\r
2140 ts: me.getTimestamp(),\r
2141 target: me.getElementXPath(e.target)\r
2142 },\r
2143 xy;\r
2144 if (!info || !rec.target) {\r
2145 return;\r
2146 }\r
2147 root = e.target.ownerDocument;\r
2148 root = root.defaultView || root.parentWindow;\r
2149 // Standards || IE\r
2150 if (root !== me.attachTo) {\r
2151 return;\r
2152 }\r
2153 if (me.eventsToRecord.scroll) {\r
2154 me.syncScroll(e.target);\r
2155 }\r
2156 if (info.xy) {\r
2157 xy = e.getXY();\r
2158 if (info.pageCoords || !rec.target) {\r
2159 rec.px = xy[0];\r
2160 rec.py = xy[1];\r
2161 } else {\r
2162 elXY = Ext.fly(e.getTarget()).getXY();\r
2163 xy[0] -= elXY[0];\r
2164 xy[1] -= elXY[1];\r
2165 rec.x = xy[0];\r
2166 rec.y = xy[1];\r
2167 }\r
2168 }\r
2169 if (info.button) {\r
2170 if ('buttons' in ev) {\r
2171 rec.button = ev.buttons;\r
2172 } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.\r
2173 {\r
2174 rec.button = ev.button;\r
2175 }\r
2176 if (!rec.button && info.whileDrag) {\r
2177 return;\r
2178 }\r
2179 }\r
2180 if (info.wheel) {\r
2181 rec.type = 'wheel';\r
2182 if (info.event === 'wheel') {\r
2183 // Current FireFox (technically IE9+ if we use addEventListener but\r
2184 // checking document.onwheel does not detect this)\r
2185 rec.dx = ev.deltaX;\r
2186 rec.dy = ev.deltaY;\r
2187 } else if (typeof ev.wheelDeltaX === 'number') {\r
2188 // new WebKit has both X & Y\r
2189 rec.dx = -1 / 40 * ev.wheelDeltaX;\r
2190 rec.dy = -1 / 40 * ev.wheelDeltaY;\r
2191 } else if (ev.wheelDelta) {\r
2192 // old WebKit and IE\r
2193 rec.dy = -1 / 40 * ev.wheelDelta;\r
2194 } else if (ev.detail) {\r
2195 // Old Gecko\r
2196 rec.dy = ev.detail;\r
2197 }\r
2198 }\r
2199 if (info.modKeys) {\r
2200 me.modKeys[0] = e.altKey ? 'A' : '';\r
2201 me.modKeys[1] = e.ctrlKey ? 'C' : '';\r
2202 me.modKeys[2] = e.metaKey ? 'M' : '';\r
2203 me.modKeys[3] = e.shiftKey ? 'S' : '';\r
2204 modKeys = me.modKeys.join('');\r
2205 if (modKeys) {\r
2206 rec.modKeys = modKeys;\r
2207 }\r
2208 }\r
2209 if (info.key) {\r
2210 rec.charCode = e.getCharCode();\r
2211 rec.keyCode = e.getKey();\r
2212 }\r
2213 if (me.coalesce(rec, e)) {\r
2214 me.fireEvent('coalesce', me, rec);\r
2215 } else {\r
2216 me.eventsRecorded.push(rec);\r
2217 me.fireEvent('add', me, rec);\r
2218 }\r
2219 },\r
2220 onStart: function() {\r
2221 var me = this,\r
2222 ddm = me.attachTo.Ext.dd.DragDropManager,\r
2223 evproto = me.attachTo.Ext.EventObjectImpl.prototype,\r
2224 special = [];\r
2225 // FireFox does not support the 'mousewheel' event but does support the\r
2226 // 'wheel' event instead.\r
2227 Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';\r
2228 me.listeners = [];\r
2229 Ext.Object.each(me.eventsToRecord, function(name, value) {\r
2230 if (value && value.listen !== false) {\r
2231 if (!value.event) {\r
2232 value.event = name;\r
2233 }\r
2234 if (value.alt && value.alt !== name) {\r
2235 // The 'drag' event is just mousemove while buttons are pressed,\r
2236 // so if there is a mousemove entry as well, ignore the drag\r
2237 if (!me.eventsToRecord[value.alt]) {\r
2238 special.push(value);\r
2239 }\r
2240 } else {\r
2241 me.listeners.push(me.listenToEvent(value.event));\r
2242 }\r
2243 }\r
2244 });\r
2245 Ext.each(special, function(info) {\r
2246 me.eventsToRecord[info.alt] = info;\r
2247 me.listeners.push(me.listenToEvent(info.alt));\r
2248 });\r
2249 me.ddmStopEvent = ddm.stopEvent;\r
2250 ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {\r
2251 me.onEvent(e);\r
2252 });\r
2253 me.evStopEvent = evproto.stopEvent;\r
2254 evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {\r
2255 me.onEvent(this);\r
2256 });\r
2257 },\r
2258 onStop: function() {\r
2259 var me = this;\r
2260 Ext.destroy(me.listeners);\r
2261 me.listeners = null;\r
2262 me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;\r
2263 me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;\r
2264 },\r
2265 samePt: function(pt1, pt2) {\r
2266 return pt1.x == pt2.x && pt1.y == pt2.y;\r
2267 },\r
2268 syncScroll: function(el) {\r
2269 var me = this,\r
2270 ts = me.getTimestamp(),\r
2271 oldX, oldY, x, y, scrolled, rec;\r
2272 for (var p = el; p; p = p.parentNode) {\r
2273 oldX = p.$lastScrollLeft;\r
2274 oldY = p.$lastScrollTop;\r
2275 x = p.scrollLeft;\r
2276 y = p.scrollTop;\r
2277 scrolled = false;\r
2278 if (oldX !== x) {\r
2279 if (x) {\r
2280 scrolled = true;\r
2281 }\r
2282 p.$lastScrollLeft = x;\r
2283 }\r
2284 if (oldY !== y) {\r
2285 if (y) {\r
2286 scrolled = true;\r
2287 }\r
2288 p.$lastScrollTop = y;\r
2289 }\r
2290 if (scrolled) {\r
2291 //console.log('scroll x:' + x + ' y:' + y, p);\r
2292 me.eventsRecorded.push(rec = {\r
2293 type: 'scroll',\r
2294 target: me.getElementXPath(p),\r
2295 ts: ts,\r
2296 pos: [\r
2297 x,\r
2298 y\r
2299 ]\r
2300 });\r
2301 me.fireEvent('add', me, rec);\r
2302 }\r
2303 if (p.tagName === 'BODY') {\r
2304 break;\r
2305 }\r
2306 }\r
2307 }\r
2308 };\r
2309});\r
2310\r
2311/**
2312 * This base class can be used by derived classes to dynamically require Google API's.
2313 */\r
2314Ext.define('Ext.ux.google.Api', {\r
2315 mixins: [\r
2316 'Ext.mixin.Mashup'\r
2317 ],\r
2318 requiredScripts: [\r
2319 '//www.google.com/jsapi'\r
2320 ],\r
2321 statics: {\r
2322 loadedModules: {}\r
2323 },\r
2324 /*
2325 * feeds: [ callback1, callback2, .... ] transitions to -> feeds : true (when complete)
2326 */\r
2327 onClassExtended: function(cls, data, hooks) {\r
2328 var onBeforeClassCreated = hooks.onBeforeCreated,\r
2329 Api = this;\r
2330 // the Ext.ux.google.Api class\r
2331 hooks.onBeforeCreated = function(cls, data) {\r
2332 var me = this,\r
2333 apis = [],\r
2334 requiresGoogle = Ext.Array.from(data.requiresGoogle),\r
2335 loadedModules = Api.loadedModules,\r
2336 remaining = 0,\r
2337 callback = function() {\r
2338 if (!--remaining) {\r
2339 onBeforeClassCreated.call(me, cls, data, hooks);\r
2340 }\r
2341 Ext.env.Ready.unblock();\r
2342 },\r
2343 api, i, length;\r
2344 /*
2345 * requiresGoogle: [
2346 * 'feeds',
2347 * { api: 'feeds', version: '1.x',
2348 * callback : fn, nocss : true } //optionals
2349 * ]
2350 */\r
2351 length = requiresGoogle.length;\r
2352 for (i = 0; i < length; ++i) {\r
2353 if (Ext.isString(api = requiresGoogle[i])) {\r
2354 apis.push({\r
2355 api: api\r
2356 });\r
2357 } else if (Ext.isObject(api)) {\r
2358 apis.push(Ext.apply({}, api));\r
2359 }\r
2360 }\r
2361 Ext.each(apis, function(api) {\r
2362 var name = api.api,\r
2363 version = String(api.version || '1.x'),\r
2364 module = loadedModules[name];\r
2365 if (!module) {\r
2366 ++remaining;\r
2367 Ext.env.Ready.block();\r
2368 loadedModules[name] = module = [\r
2369 callback\r
2370 ].concat(api.callback || []);\r
2371 delete api.api;\r
2372 delete api.version;\r
2373 //TODO: window.google assertion?\r
2374 google.load(name, version, Ext.applyIf({\r
2375 callback: function() {\r
2376 loadedModules[name] = true;\r
2377 for (var n = module.length; n-- > 0; ) {\r
2378 module[n]();\r
2379 }\r
2380 }\r
2381 }, //iterate callbacks in reverse\r
2382 api));\r
2383 } else if (module !== true) {\r
2384 module.push(callback);\r
2385 }\r
2386 });\r
2387 if (!remaining) {\r
2388 onBeforeClassCreated.call(me, cls, data, hooks);\r
2389 }\r
2390 };\r
2391 }\r
2392});\r
2393\r
2394/**
2395 * This class, when required, ensures that the Google RSS Feeds API is available.
2396 */\r
2397Ext.define('Ext.ux.google.Feeds', {\r
2398 extend: 'Ext.ux.google.Api',\r
2399 requiresGoogle: {\r
2400 api: 'feeds',\r
2401 nocss: true\r
2402 }\r
2403});\r
2404\r