",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/hammerjs/touchemulator/issues"
27 | },
28 | "homepage": "https://github.com/hammerjs/touchemulator"
29 | }
30 |
--------------------------------------------------------------------------------
/tests/manual/leaflet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/manual/events.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Check the console. No mouseevents should be fired, only touchevents. Click events are allowed.
14 |
15 |
16 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Hammer.js
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/tests/manual/googlemaps.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | To test it on the Google Maps view, you should open your
10 |
11 | Inspector and emulate a touch-device (by spoofing the user agent). .
12 |
13 | This is because the userAgent can't be overwritten and Google uses this to identify if there's any touch
14 | support.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/web-platform-tests/touch-events/multi-touch-interactions-manual.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 | Touch Events Multi-Touch Interaction Test
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
41 |
42 |
43 | Touch Events: Multi-Touch Interaction Test
44 |
45 | Touch this box with one finger, then another one...
46 |
47 |
48 | ...then drag to this box, then touch with a third finger, and lift all your fingers.
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/tests/web-platform-tests/resources/testharness.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
3 | }
4 |
5 | #log .warning,
6 | #log .warning a {
7 | color: black;
8 | background: yellow;
9 | }
10 |
11 | #log .error,
12 | #log .error a {
13 | color: white;
14 | background: red;
15 | }
16 |
17 | #log pre {
18 | border: 1px solid black;
19 | padding: 1em;
20 | }
21 |
22 | section#summary {
23 | margin-bottom:1em;
24 | }
25 |
26 | table#results {
27 | border-collapse:collapse;
28 | table-layout:fixed;
29 | width:100%;
30 | }
31 |
32 | table#results th:first-child,
33 | table#results td:first-child {
34 | width:4em;
35 | }
36 |
37 | table#results th:last-child,
38 | table#results td:last-child {
39 | width:50%;
40 | }
41 |
42 | table#results.assertions th:last-child,
43 | table#results.assertions td:last-child {
44 | width:35%;
45 | }
46 |
47 | table#results th {
48 | padding:0;
49 | padding-bottom:0.5em;
50 | border-bottom:medium solid black;
51 | }
52 |
53 | table#results td {
54 | padding:1em;
55 | padding-bottom:0.5em;
56 | border-bottom:thin solid black;
57 | }
58 |
59 | tr.pass > td:first-child {
60 | color:green;
61 | }
62 |
63 | tr.fail > td:first-child {
64 | color:red;
65 | }
66 |
67 | tr.timeout > td:first-child {
68 | color:red;
69 | }
70 |
71 | tr.notrun > td:first-child {
72 | color:blue;
73 | }
74 |
75 | .pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {
76 | font-variant:small-caps;
77 | }
78 |
79 | table#results span {
80 | display:block;
81 | }
82 |
83 | table#results span.expected {
84 | font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
85 | white-space:pre;
86 | }
87 |
88 | table#results span.actual {
89 | font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
90 | white-space:pre;
91 | }
92 |
93 | span.ok {
94 | color:green;
95 | }
96 |
97 | tr.error {
98 | color:red;
99 | }
100 |
101 | span.timeout {
102 | color:red;
103 | }
104 |
105 | span.ok, span.timeout, span.error {
106 | font-variant:small-caps;
107 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Touch Emulator
2 | ========
3 |
4 | Emulate multi-touch input on your desktop. Triggers touch events as
5 | [specified by W3C](http://www.w3.org/TR/touch-events). Press the `shift` key to pinch and rotate!
6 |
7 | - [Example with Hammer.js](http://rawgit.com/hammerjs/touchemulator/master/tests/manual/hammer.html)
8 | - [Example with Leaflet Maps](http://rawgit.com/hammerjs/touchemulator/master/tests/manual/leaflet.html)
9 | - [View webpage](http://hammerjs.github.io/touch-emulator)
10 |
11 | ## Install
12 | Download the script from this repo, via Bower:
13 |
14 | ```bash
15 | bower install hammer-touchemulator
16 | ```
17 |
18 | or NPM:
19 |
20 | ```bash
21 | npm install hammer-touchemulator
22 | ```
23 |
24 | ## How to use
25 | Include the javascript file, and call the `Emulator()` function before any other libraries that do something with the
26 | touch input. It will set some fake properties to spoof the touch detection of some libraries, and triggers `touchstart`, `touchmove` and `touchend` events on the mouse target.
27 |
28 | ````html
29 |
30 |
31 | ````
32 |
33 | ````js
34 | function log(ev) {
35 | console.log(ev);
36 | }
37 |
38 | document.body.addEventListener('touchstart', log, false);
39 | document.body.addEventListener('touchmove', log, false);
40 | document.body.addEventListener('touchend', log, false);
41 | ````
42 |
43 | Also, the script includes polyfills for `document.createTouch` and `document.createTouchList`.
44 |
45 | ## How it works
46 | It listens to the `mousedown`, `mousemove` and `mouseup` events, and translates them to touch events. If the mouseevent
47 | has the `shiftKey` property to `true`, it enables multi-touch.
48 |
49 | The script also prevents the following mouse events on the page:
50 | `mousedown`, `mouseenter`, `mouseleave`, `mousemove`, `mouseout`, `mouseover` and `mouseup`.
51 |
52 | ## Web platform tests
53 | The script has been tested with the [w3c web platform tests](/tests/web-platform-tests/touch-events) and passes all tests, except these;
54 | - *assert_true: event is a TouchEvent event expected true got false*
55 | - We trigger an event of the type `Event`
56 | - *assert_equals: touch list is of type TouchList expected "[object TouchList]" but got "[object Array]"*
57 | - *assert_equals: touch is of type Touch expected "[object Touch]" but got "[object Object]"*
58 |
59 | ## Bookmarklet
60 | ````js
61 | javascript:!function(a){var b=a.createElement("script");b.onload=function(){TouchEmulator()},b.src="//cdn.rawgit.com/hammerjs/touchemulator/0.0.2/touch-emulator.js",a.body.appendChild(b)}(document);
62 | ````
63 |
64 | ## Options
65 | #### TouchEmulator.template = Function(touch)
66 | Change the css properties of the rendered touches.
67 |
68 | #### TouchEmulator.multiTouchOffset = 75
69 | The distance between the two touch points when entering the *multi-touch zone*.
70 |
71 |
72 |
--------------------------------------------------------------------------------
/tests/manual/hammer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hammer.js
7 |
8 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/tests/web-platform-tests/touch-events/create-touch-touchlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 | Touch Events createTouch and createTouchList Interface Tests
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
173 |
183 |
184 |
185 | Touch Events: createTouch and createTouchList tests
186 | Please wait for test to complete...
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/tests/web-platform-tests/touch-events/multi-touch-interfaces-manual.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 | Touch Events Multi-Touch Interface Tests
20 |
21 |
22 |
23 |
24 |
25 |
26 |
252 |
266 |
267 |
268 | Touch Events: multi-touch interface tests
269 |
270 | Touch this box with one finger, then another one...
271 |
272 |
273 | ...then drag to this box and lift your fingers.
274 |
275 |
276 |
277 |
278 |
--------------------------------------------------------------------------------
/touch-emulator.js:
--------------------------------------------------------------------------------
1 | (function(window, document, exportName, undefined) {
2 | "use strict";
3 |
4 | var isMultiTouch = false;
5 | var multiTouchStartPos;
6 | var eventTarget;
7 | var touchElements = {};
8 |
9 | // polyfills
10 | if(!document.createTouch) {
11 | document.createTouch = function(view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
12 | // auto set
13 | if(clientX == undefined || clientY == undefined) {
14 | clientX = pageX - window.pageXOffset;
15 | clientY = pageY - window.pageYOffset;
16 | }
17 |
18 | return new Touch(target, identifier, {
19 | pageX: pageX,
20 | pageY: pageY,
21 | screenX: screenX,
22 | screenY: screenY,
23 | clientX: clientX,
24 | clientY: clientY
25 | });
26 | };
27 | }
28 |
29 | if(!document.createTouchList) {
30 | document.createTouchList = function() {
31 | var touchList = new TouchList();
32 | for (var i = 0; i < arguments.length; i++) {
33 | touchList[i] = arguments[i];
34 | }
35 | touchList.length = arguments.length;
36 | return touchList;
37 | };
38 | }
39 |
40 | /**
41 | * create an touch point
42 | * @constructor
43 | * @param target
44 | * @param identifier
45 | * @param pos
46 | * @param deltaX
47 | * @param deltaY
48 | * @returns {Object} touchPoint
49 | */
50 | function Touch(target, identifier, pos, deltaX, deltaY) {
51 | deltaX = deltaX || 0;
52 | deltaY = deltaY || 0;
53 |
54 | this.identifier = identifier;
55 | this.target = target;
56 | this.clientX = pos.clientX + deltaX;
57 | this.clientY = pos.clientY + deltaY;
58 | this.screenX = pos.screenX + deltaX;
59 | this.screenY = pos.screenY + deltaY;
60 | this.pageX = pos.pageX + deltaX;
61 | this.pageY = pos.pageY + deltaY;
62 | }
63 |
64 | /**
65 | * create empty touchlist with the methods
66 | * @constructor
67 | * @returns touchList
68 | */
69 | function TouchList() {
70 | var touchList = [];
71 |
72 | touchList.item = function(index) {
73 | return this[index] || null;
74 | };
75 |
76 | // specified by Mozilla
77 | touchList.identifiedTouch = function(id) {
78 | return this[id + 1] || null;
79 | };
80 |
81 | return touchList;
82 | }
83 |
84 |
85 | /**
86 | * Simple trick to fake touch event support
87 | * this is enough for most libraries like Modernizr and Hammer
88 | */
89 | function fakeTouchSupport() {
90 | var objs = [window, document.documentElement];
91 | var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];
92 |
93 | for(var o=0; o 2; // pointer events
110 | }
111 |
112 | /**
113 | * disable mouseevents on the page
114 | * @param ev
115 | */
116 | function preventMouseEvents(ev) {
117 | ev.preventDefault();
118 | ev.stopPropagation();
119 | }
120 |
121 | /**
122 | * only trigger touches when the left mousebutton has been pressed
123 | * @param touchType
124 | * @returns {Function}
125 | */
126 | function onMouse(touchType) {
127 | return function(ev) {
128 | // prevent mouse events
129 | preventMouseEvents(ev);
130 |
131 | if (ev.which !== 1) {
132 | return;
133 | }
134 |
135 | // The EventTarget on which the touch point started when it was first placed on the surface,
136 | // even if the touch point has since moved outside the interactive area of that element.
137 | // also, when the target doesnt exist anymore, we update it
138 | if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
139 | eventTarget = ev.target;
140 | }
141 |
142 | // shiftKey has been lost, so trigger a touchend
143 | if (isMultiTouch && !ev.shiftKey) {
144 | triggerTouch('touchend', ev);
145 | isMultiTouch = false;
146 | }
147 |
148 | triggerTouch(touchType, ev);
149 |
150 | // we're entering the multi-touch mode!
151 | if (!isMultiTouch && ev.shiftKey) {
152 | isMultiTouch = true;
153 | multiTouchStartPos = {
154 | pageX: ev.pageX,
155 | pageY: ev.pageY,
156 | clientX: ev.clientX,
157 | clientY: ev.clientY,
158 | screenX: ev.screenX,
159 | screenY: ev.screenY
160 | };
161 | triggerTouch('touchstart', ev);
162 | }
163 |
164 | // reset
165 | if (ev.type == 'mouseup') {
166 | multiTouchStartPos = null;
167 | isMultiTouch = false;
168 | eventTarget = null;
169 | }
170 | }
171 | }
172 |
173 | /**
174 | * trigger a touch event
175 | * @param eventName
176 | * @param mouseEv
177 | */
178 | function triggerTouch(eventName, mouseEv) {
179 | var touchEvent = document.createEvent('Event');
180 | touchEvent.initEvent(eventName, true, true);
181 |
182 | touchEvent.altKey = mouseEv.altKey;
183 | touchEvent.ctrlKey = mouseEv.ctrlKey;
184 | touchEvent.metaKey = mouseEv.metaKey;
185 | touchEvent.shiftKey = mouseEv.shiftKey;
186 |
187 | touchEvent.touches = getActiveTouches(mouseEv, eventName);
188 | touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
189 | touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);
190 |
191 | eventTarget.dispatchEvent(touchEvent);
192 | }
193 |
194 | /**
195 | * create a touchList based on the mouse event
196 | * @param mouseEv
197 | * @returns {TouchList}
198 | */
199 | function createTouchList(mouseEv) {
200 | var touchList = new TouchList();
201 |
202 | if (isMultiTouch) {
203 | var f = TouchEmulator.multiTouchOffset;
204 | var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
205 | var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;
206 |
207 | touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX*-1) - f, (deltaY*-1) + f));
208 | touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX+f, deltaY-f));
209 | } else {
210 | touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
211 | }
212 |
213 | return touchList;
214 | }
215 |
216 | /**
217 | * receive all active touches
218 | * @param mouseEv
219 | * @returns {TouchList}
220 | */
221 | function getActiveTouches(mouseEv, eventName) {
222 | // empty list
223 | if (mouseEv.type == 'mouseup') {
224 | return new TouchList();
225 | }
226 |
227 | var touchList = createTouchList(mouseEv);
228 | if(isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
229 | touchList.splice(1, 1);
230 | }
231 | return touchList;
232 | }
233 |
234 | /**
235 | * receive a filtered set of touches with only the changed pointers
236 | * @param mouseEv
237 | * @param eventName
238 | * @returns {TouchList}
239 | */
240 | function getChangedTouches(mouseEv, eventName) {
241 | var touchList = createTouchList(mouseEv);
242 |
243 | // we only want to return the added/removed item on multitouch
244 | // which is the second pointer, so remove the first pointer from the touchList
245 | //
246 | // but when the mouseEv.type is mouseup, we want to send all touches because then
247 | // no new input will be possible
248 | if(isMultiTouch && mouseEv.type != 'mouseup' &&
249 | (eventName == 'touchstart' || eventName == 'touchend')) {
250 | touchList.splice(0, 1);
251 | }
252 |
253 | return touchList;
254 | }
255 |
256 | /**
257 | * show the touchpoints on the screen
258 | */
259 | function showTouches(ev) {
260 | var touch, i, el, styles;
261 |
262 | // first all visible touches
263 | for(i = 0; i < ev.touches.length; i++) {
264 | touch = ev.touches[i];
265 | el = touchElements[touch.identifier];
266 | if(!el) {
267 | el = touchElements[touch.identifier] = document.createElement("div");
268 | document.body.appendChild(el);
269 | }
270 |
271 | styles = TouchEmulator.template(touch);
272 | for(var prop in styles) {
273 | el.style[prop] = styles[prop];
274 | }
275 | }
276 |
277 | // remove all ended touches
278 | if(ev.type == 'touchend' || ev.type == 'touchcancel') {
279 | for(i = 0; i < ev.changedTouches.length; i++) {
280 | touch = ev.changedTouches[i];
281 | el = touchElements[touch.identifier];
282 | if(el) {
283 | el.parentNode.removeChild(el);
284 | delete touchElements[touch.identifier];
285 | }
286 | }
287 | }
288 | }
289 |
290 | /**
291 | * TouchEmulator initializer
292 | */
293 | function TouchEmulator() {
294 | if (hasTouchSupport()) {
295 | return;
296 | }
297 |
298 | fakeTouchSupport();
299 |
300 | window.addEventListener("mousedown", onMouse('touchstart'), true);
301 | window.addEventListener("mousemove", onMouse('touchmove'), true);
302 | window.addEventListener("mouseup", onMouse('touchend'), true);
303 |
304 | window.addEventListener("mouseenter", preventMouseEvents, true);
305 | window.addEventListener("mouseleave", preventMouseEvents, true);
306 | window.addEventListener("mouseout", preventMouseEvents, true);
307 | window.addEventListener("mouseover", preventMouseEvents, true);
308 |
309 | // it uses itself!
310 | window.addEventListener("touchstart", showTouches, true);
311 | window.addEventListener("touchmove", showTouches, true);
312 | window.addEventListener("touchend", showTouches, true);
313 | window.addEventListener("touchcancel", showTouches, true);
314 | }
315 |
316 | // start distance when entering the multitouch mode
317 | TouchEmulator.multiTouchOffset = 75;
318 |
319 | /**
320 | * css template for the touch rendering
321 | * @param touch
322 | * @returns object
323 | */
324 | TouchEmulator.template = function(touch) {
325 | var size = 30;
326 | var transform = 'translate('+ (touch.clientX-(size/2)) +'px, '+ (touch.clientY-(size/2)) +'px)';
327 | return {
328 | position: 'fixed',
329 | left: 0,
330 | top: 0,
331 | background: '#fff',
332 | border: 'solid 1px #999',
333 | opacity: .6,
334 | borderRadius: '100%',
335 | height: size + 'px',
336 | width: size + 'px',
337 | padding: 0,
338 | margin: 0,
339 | display: 'block',
340 | overflow: 'hidden',
341 | pointerEvents: 'none',
342 | webkitUserSelect: 'none',
343 | mozUserSelect: 'none',
344 | userSelect: 'none',
345 | webkitTransform: transform,
346 | mozTransform: transform,
347 | transform: transform,
348 | zIndex: 100
349 | }
350 | };
351 |
352 | // export
353 | if (typeof define == "function" && define.amd) {
354 | define(function() {
355 | return TouchEmulator;
356 | });
357 | } else if (typeof module != "undefined" && module.exports) {
358 | module.exports = TouchEmulator;
359 | } else {
360 | window[exportName] = TouchEmulator;
361 | }
362 | })(window, document, "TouchEmulator");
363 |
--------------------------------------------------------------------------------
/tests/web-platform-tests/resources/testharnessreport.js:
--------------------------------------------------------------------------------
1 | /*global add_completion_callback, setup */
2 | /*
3 | * This file is intended for vendors to implement
4 | * code needed to integrate testharness.js tests with their own test systems.
5 | *
6 | * The default implementation extracts metadata from the tests and validates
7 | * it against the cached version that should be present in the test source
8 | * file. If the cache is not found or is out of sync, source code suitable for
9 | * caching the metadata is optionally generated.
10 | *
11 | * The cached metadata is present for extraction by test processing tools that
12 | * are unable to execute javascript.
13 | *
14 | * Metadata is attached to tests via the properties parameter in the test
15 | * constructor. See testharness.js for details.
16 | *
17 | * Typically test system integration will attach callbacks when each test has
18 | * run, using add_result_callback(callback(test)), or when the whole test file
19 | * has completed, using
20 | * add_completion_callback(callback(tests, harness_status)).
21 | *
22 | * For more documentation about the callback functions and the
23 | * parameters they are called with see testharness.js
24 | */
25 |
26 |
27 |
28 | var metadata_generator = {
29 |
30 | currentMetadata: {},
31 | cachedMetadata: false,
32 | metadataProperties: ['help', 'assert', 'author'],
33 |
34 | error: function(message) {
35 | var messageElement = document.createElement('p');
36 | messageElement.setAttribute('class', 'error');
37 | this.appendText(messageElement, message);
38 |
39 | var summary = document.getElementById('summary');
40 | if (summary) {
41 | summary.parentNode.insertBefore(messageElement, summary);
42 | }
43 | else {
44 | document.body.appendChild(messageElement);
45 | }
46 | },
47 |
48 | /**
49 | * Ensure property value has contact information
50 | */
51 | validateContact: function(test, propertyName) {
52 | var result = true;
53 | var value = test.properties[propertyName];
54 | var values = Array.isArray(value) ? value : [value];
55 | for (var index = 0; index < values.length; index++) {
56 | value = values[index];
57 | var re = /(\S+)(\s*)<(.*)>(.*)/;
58 | if (! re.test(value)) {
59 | re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/;
60 | if (! re.test(value)) {
61 | this.error('Metadata property "' + propertyName +
62 | '" for test: "' + test.name +
63 | '" must have name and contact information ' +
64 | '("name " or "name http(s)://")');
65 | result = false;
66 | }
67 | }
68 | }
69 | return result;
70 | },
71 |
72 | /**
73 | * Extract metadata from test object
74 | */
75 | extractFromTest: function(test) {
76 | var testMetadata = {};
77 | // filter out metadata from other properties in test
78 | for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
79 | metaIndex++) {
80 | var meta = this.metadataProperties[metaIndex];
81 | if (test.properties.hasOwnProperty(meta)) {
82 | if ('author' == meta) {
83 | this.validateContact(test, meta);
84 | }
85 | testMetadata[meta] = test.properties[meta];
86 | }
87 | }
88 | return testMetadata;
89 | },
90 |
91 | /**
92 | * Compare cached metadata to extracted metadata
93 | */
94 | validateCache: function() {
95 | for (var testName in this.currentMetadata) {
96 | if (! this.cachedMetadata.hasOwnProperty(testName)) {
97 | return false;
98 | }
99 | var testMetadata = this.currentMetadata[testName];
100 | var cachedTestMetadata = this.cachedMetadata[testName];
101 | delete this.cachedMetadata[testName];
102 |
103 | for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
104 | metaIndex++) {
105 | var meta = this.metadataProperties[metaIndex];
106 | if (cachedTestMetadata.hasOwnProperty(meta) &&
107 | testMetadata.hasOwnProperty(meta)) {
108 | if (Array.isArray(cachedTestMetadata[meta])) {
109 | if (! Array.isArray(testMetadata[meta])) {
110 | return false;
111 | }
112 | if (cachedTestMetadata[meta].length ==
113 | testMetadata[meta].length) {
114 | for (var index = 0;
115 | index < cachedTestMetadata[meta].length;
116 | index++) {
117 | if (cachedTestMetadata[meta][index] !=
118 | testMetadata[meta][index]) {
119 | return false;
120 | }
121 | }
122 | }
123 | else {
124 | return false;
125 | }
126 | }
127 | else {
128 | if (Array.isArray(testMetadata[meta])) {
129 | return false;
130 | }
131 | if (cachedTestMetadata[meta] != testMetadata[meta]) {
132 | return false;
133 | }
134 | }
135 | }
136 | else if (cachedTestMetadata.hasOwnProperty(meta) ||
137 | testMetadata.hasOwnProperty(meta)) {
138 | return false;
139 | }
140 | }
141 | }
142 | for (var testName in this.cachedMetadata) {
143 | return false;
144 | }
145 | return true;
146 | },
147 |
148 | appendText: function(elemement, text) {
149 | elemement.appendChild(document.createTextNode(text));
150 | },
151 |
152 | jsonifyArray: function(arrayValue, indent) {
153 | var output = '[';
154 |
155 | if (1 == arrayValue.length) {
156 | output += JSON.stringify(arrayValue[0]);
157 | }
158 | else {
159 | for (var index = 0; index < arrayValue.length; index++) {
160 | if (0 < index) {
161 | output += ',\n ' + indent;
162 | }
163 | output += JSON.stringify(arrayValue[index]);
164 | }
165 | }
166 | output += ']';
167 | return output;
168 | },
169 |
170 | jsonifyObject: function(objectValue, indent) {
171 | var output = '{';
172 | var value;
173 |
174 | var count = 0;
175 | for (var property in objectValue) {
176 | ++count;
177 | if (Array.isArray(objectValue[property]) ||
178 | ('object' == typeof(value))) {
179 | ++count;
180 | }
181 | }
182 | if (1 == count) {
183 | for (var property in objectValue) {
184 | output += ' "' + property + '": ' +
185 | JSON.stringify(objectValue[property]) +
186 | ' ';
187 | }
188 | }
189 | else {
190 | var first = true;
191 | for (var property in objectValue) {
192 | if (! first) {
193 | output += ',';
194 | }
195 | first = false;
196 | output += '\n ' + indent + '"' + property + '": ';
197 | value = objectValue[property];
198 | if (Array.isArray(value)) {
199 | output += this.jsonifyArray(value, indent +
200 | ' '.substr(0, 5 + property.length));
201 | }
202 | else if ('object' == typeof(value)) {
203 | output += this.jsonifyObject(value, indent + ' ');
204 | }
205 | else {
206 | output += JSON.stringify(value);
207 | }
208 | }
209 | if (1 < output.length) {
210 | output += '\n' + indent;
211 | }
212 | }
213 | output += '}';
214 | return output;
215 | },
216 |
217 | /**
218 | * Generate javascript source code for captured metadata
219 | * Metadata is in pretty-printed JSON format
220 | */
221 | generateSource: function() {
222 | var source =
223 | '\n';
226 | return source;
227 | },
228 |
229 | /**
230 | * Add element containing metadata source code
231 | */
232 | addSourceElement: function(event) {
233 | var sourceWrapper = document.createElement('div');
234 | sourceWrapper.setAttribute('id', 'metadata_source');
235 |
236 | var instructions = document.createElement('p');
237 | if (this.cachedMetadata) {
238 | this.appendText(instructions,
239 | 'Replace the existing
19 |
20 |
21 |
22 |
357 |
371 |
372 |
373 | Touch Events: single-touch tests
374 |
375 | Touch this box with one finger (or other pointing device)...
376 |
377 |
378 | ...then drag to this box and lift your finger.
379 |
380 |
381 |
382 |