terminus 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.rdoc +27 -89
  2. data/bin/terminus +7 -23
  3. data/lib/capybara/driver/terminus.rb +30 -11
  4. data/lib/terminus.rb +33 -22
  5. data/lib/terminus/application.rb +13 -7
  6. data/lib/terminus/browser.rb +110 -28
  7. data/lib/terminus/controller.rb +51 -20
  8. data/lib/terminus/host.rb +22 -0
  9. data/lib/terminus/node.rb +23 -4
  10. data/lib/terminus/proxy.rb +62 -0
  11. data/lib/terminus/proxy/driver_body.rb +63 -0
  12. data/lib/terminus/proxy/external.rb +25 -0
  13. data/lib/terminus/proxy/rewrite.rb +20 -0
  14. data/lib/terminus/public/icon.png +0 -0
  15. data/lib/terminus/public/loader.js +1 -0
  16. data/lib/terminus/public/style.css +43 -0
  17. data/lib/terminus/public/syn/browsers.js +150 -0
  18. data/lib/terminus/public/syn/drag/drag.js +322 -0
  19. data/lib/terminus/public/syn/key.js +905 -0
  20. data/lib/terminus/public/syn/mouse.js +284 -0
  21. data/lib/terminus/public/syn/synthetic.js +830 -0
  22. data/lib/{public → terminus/public}/terminus.js +109 -40
  23. data/lib/terminus/server.rb +5 -12
  24. data/lib/terminus/timeouts.rb +2 -2
  25. data/lib/{views/bookmarklet.erb → terminus/views/bootstrap.erb} +17 -12
  26. data/lib/terminus/views/index.erb +21 -0
  27. data/lib/terminus/views/infinite.html +16 -0
  28. data/spec/reports/chrome.txt +748 -0
  29. data/spec/reports/firefox.txt +748 -0
  30. data/spec/reports/opera.txt +748 -0
  31. data/spec/reports/safari.txt +748 -0
  32. data/spec/spec_helper.rb +18 -14
  33. data/spec/terminus_driver_spec.rb +7 -5
  34. data/spec/terminus_session_spec.rb +5 -18
  35. metadata +71 -57
  36. data/lib/public/loader.js +0 -1
  37. data/lib/public/style.css +0 -49
  38. data/lib/public/syn.js +0 -2355
  39. data/lib/views/index.erb +0 -32
@@ -0,0 +1,322 @@
1
+ (function() {
2
+
3
+ // check if elementFromPageExists
4
+ (function() {
5
+
6
+ // document body has to exists for this test
7
+ if (!document.body ) {
8
+ setTimeout(arguments.callee, 1)
9
+ return;
10
+ }
11
+ var div = document.createElement('div')
12
+ document.body.appendChild(div);
13
+ Syn.helpers.extend(div.style, {
14
+ width: "100px",
15
+ height: "10000px",
16
+ backgroundColor: "blue",
17
+ position: "absolute",
18
+ top: "10px",
19
+ left: "0px",
20
+ zIndex: 19999
21
+ });
22
+ document.body.scrollTop = 11;
23
+ if (!document.elementFromPoint ) {
24
+ return;
25
+ }
26
+ var el = document.elementFromPoint(3, 1)
27
+ if ( el == div ) {
28
+ Syn.support.elementFromClient = true;
29
+ }
30
+ else {
31
+ Syn.support.elementFromPage = true;
32
+ }
33
+ document.body.removeChild(div);
34
+ document.body.scrollTop = 0;
35
+ })();
36
+
37
+
38
+ //gets an element from a point
39
+ var elementFromPoint = function( point, element ) {
40
+ var clientX = point.clientX,
41
+ clientY = point.clientY,
42
+ win = Syn.helpers.getWindow(element),
43
+ el;
44
+
45
+
46
+
47
+ if ( Syn.support.elementFromPage ) {
48
+ var off = Syn.helpers.scrollOffset(win);
49
+ clientX = clientX + off.left; //convert to pageX
50
+ clientY = clientY + off.top; //convert to pageY
51
+ }
52
+ el = win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element;
53
+ if ( el === win.document.documentElement && (point.clientY < 0 || point.clientX < 0) ) {
54
+ return element;
55
+ } else {
56
+ return el;
57
+ }
58
+ },
59
+ //creates an event at a certain point
60
+ createEventAtPoint = function( event, point, element ) {
61
+ var el = elementFromPoint(point, element)
62
+ Syn.trigger(event, point, el || element)
63
+ return el;
64
+ },
65
+ // creates a mousemove event, but first triggering mouseout / mouseover if appropriate
66
+ mouseMove = function( point, element, last ) {
67
+ var el = elementFromPoint(point, element)
68
+ if ( last != el && el && last ) {
69
+ var options = Syn.helpers.extend({}, point);
70
+ options.relatedTarget = el;
71
+ Syn.trigger("mouseout", options, last);
72
+ options.relatedTarget = last;
73
+ Syn.trigger("mouseover", options, el);
74
+ }
75
+
76
+ Syn.trigger("mousemove", point, el || element)
77
+ return el;
78
+ },
79
+ // start and end are in clientX, clientY
80
+ startMove = function( start, end, duration, element, callback ) {
81
+ var startTime = new Date(),
82
+ distX = end.clientX - start.clientX,
83
+ distY = end.clientY - start.clientY,
84
+ win = Syn.helpers.getWindow(element),
85
+ current = elementFromPoint(start, element),
86
+ cursor = win.document.createElement('div'),
87
+ calls = 0;
88
+ move = function() {
89
+ //get what fraction we are at
90
+ var now = new Date(),
91
+ scrollOffset = Syn.helpers.scrollOffset(win),
92
+ fraction = (calls == 0 ? 0 : now - startTime) / duration,
93
+ options = {
94
+ clientX: distX * fraction + start.clientX,
95
+ clientY: distY * fraction + start.clientY
96
+ };
97
+ calls++;
98
+ if ( fraction < 1 ) {
99
+ Syn.helpers.extend(cursor.style, {
100
+ left: (options.clientX + scrollOffset.left + 2) + "px",
101
+ top: (options.clientY + scrollOffset.top + 2) + "px"
102
+ })
103
+ current = mouseMove(options, element, current)
104
+ setTimeout(arguments.callee, 15)
105
+ }
106
+ else {
107
+ current = mouseMove(end, element, current);
108
+ win.document.body.removeChild(cursor)
109
+ callback();
110
+ }
111
+ }
112
+ Syn.helpers.extend(cursor.style, {
113
+ height: "5px",
114
+ width: "5px",
115
+ backgroundColor: "red",
116
+ position: "absolute",
117
+ zIndex: 19999,
118
+ fontSize: "1px"
119
+ })
120
+ win.document.body.appendChild(cursor)
121
+ move();
122
+ },
123
+ startDrag = function( start, end, duration, element, callback ) {
124
+ createEventAtPoint("mousedown", start, element);
125
+ startMove(start, end, duration, element, function() {
126
+ createEventAtPoint("mouseup", end, element);
127
+ callback();
128
+ })
129
+ },
130
+ center = function( el ) {
131
+ var j = Syn.jquery()(el),
132
+ o = j.offset();
133
+ return {
134
+ pageX: o.left + (j.width() / 2),
135
+ pageY: o.top + (j.height() / 2)
136
+ }
137
+ },
138
+ convertOption = function( option, win, from ) {
139
+ var page = /(\d+)[x ](\d+)/,
140
+ client = /(\d+)X(\d+)/,
141
+ relative = /([+-]\d+)[xX ]([+-]\d+)/
142
+ //check relative "+22x-44"
143
+ if ( typeof option == 'string' && relative.test(option) && from ) {
144
+ var cent = center(from),
145
+ parts = option.match(relative);
146
+ option = {
147
+ pageX: cent.pageX + parseInt(parts[1]),
148
+ pageY: cent.pageY + parseInt(parts[2])
149
+ }
150
+ }
151
+ if ( typeof option == 'string' && page.test(option) ) {
152
+ var parts = option.match(page)
153
+ option = {
154
+ pageX: parseInt(parts[1]),
155
+ pageY: parseInt(parts[2])
156
+ }
157
+ }
158
+ if ( typeof option == 'string' && client.test(option) ) {
159
+ var parts = option.match(client)
160
+ option = {
161
+ clientX: parseInt(parts[1]),
162
+ clientY: parseInt(parts[2])
163
+ }
164
+ }
165
+ if ( typeof option == 'string' ) {
166
+ option = Syn.jquery()(option, win.document)[0];
167
+ }
168
+ if ( option.nodeName ) {
169
+ option = center(option)
170
+ }
171
+ if ( option.pageX ) {
172
+ var off = Syn.helpers.scrollOffset(win);
173
+ option = {
174
+ clientX: option.pageX - off.left,
175
+ clientY: option.pageY - off.top
176
+ }
177
+ }
178
+ return option;
179
+ },
180
+ // if the client chords are not going to be visible ... scroll the page so they will be ...
181
+ adjust = function(from, to, win){
182
+ if(from.clientY < 0){
183
+ var off = Syn.helpers.scrollOffset(win);
184
+ var dimensions = Syn.helpers.scrollDimensions(win),
185
+ top = off.top + (from.clientY) - 100,
186
+ diff = top - off.top
187
+
188
+ // first, lets see if we can scroll 100 px
189
+ if( top > 0){
190
+
191
+ } else {
192
+ top =0;
193
+ diff = -off.top;
194
+ }
195
+ console.log("moving", from.clientY,from.clientY - diff, off.top, top )
196
+ from.clientY = from.clientY - diff;
197
+ to.clientY = to.clientY - diff;
198
+ Syn.helpers.scrollOffset(win,{top: top, left: off.left});
199
+
200
+ //throw "out of bounds!"
201
+ }
202
+ }
203
+ /**
204
+ * @add Syn prototype
205
+ */
206
+ Syn.helpers.extend(Syn.init.prototype, {
207
+ /**
208
+ * @function move
209
+ * Moves the cursor from one point to another.
210
+ *
211
+ * ### Quick Example
212
+ *
213
+ * The following moves the cursor from (0,0) in
214
+ * the window to (100,100) in 1 second.
215
+ *
216
+ * Syn.move(
217
+ * {
218
+ * from: {clientX: 0, clientY: 0},
219
+ * to: {clientX: 100, clientY: 100},
220
+ * duration: 1000
221
+ * },
222
+ * document.document)
223
+ *
224
+ * ## Options
225
+ *
226
+ * There are many ways to configure the endpoints of the move.
227
+ *
228
+ * ### PageX and PageY
229
+ *
230
+ * If you pass pageX or pageY, these will get converted
231
+ * to client coordinates.
232
+ *
233
+ * Syn.move(
234
+ * {
235
+ * from: {pageX: 0, pageY: 0},
236
+ * to: {pageX: 100, pageY: 100}
237
+ * },
238
+ * document.document)
239
+ *
240
+ * ### String Coordinates
241
+ *
242
+ * You can set the pageX and pageY as strings like:
243
+ *
244
+ * Syn.move(
245
+ * {
246
+ * from: "0x0",
247
+ * to: "100x100"
248
+ * },
249
+ * document.document)
250
+ *
251
+ * ### Element Coordinates
252
+ *
253
+ * If jQuery is present, you can pass an element as the from or to option
254
+ * and the coordinate will be set as the center of the element.
255
+
256
+ * Syn.move(
257
+ * {
258
+ * from: $(".recipe")[0],
259
+ * to: $("#trash")[0]
260
+ * },
261
+ * document.document)
262
+ *
263
+ * ### Query Strings
264
+ *
265
+ * If jQuery is present, you can pass a query string as the from or to option.
266
+ *
267
+ * Syn.move(
268
+ * {
269
+ * from: ".recipe",
270
+ * to: "#trash"
271
+ * },
272
+ * document.document)
273
+ *
274
+ * ### No From
275
+ *
276
+ * If you don't provide a from, the element argument passed to Syn is used.
277
+ *
278
+ * Syn.move(
279
+ * { to: "#trash" },
280
+ * 'myrecipe')
281
+ *
282
+ * ### Relative
283
+ *
284
+ * You can move the drag relative to the center of the from element.
285
+ *
286
+ * Syn.move("+20 +30", "myrecipe");
287
+ *
288
+ * @param {Object} options options to configure the drag
289
+ * @param {HTMLElement} from the element to move
290
+ * @param {Function} callback a callback that happens after the drag motion has completed
291
+ */
292
+ _move: function( options, from, callback ) {
293
+ //need to convert if elements
294
+ var win = Syn.helpers.getWindow(from),
295
+ fro = convertOption(options.from || from, win, from),
296
+ to = convertOption(options.to || options, win, from);
297
+
298
+ options.adjust !== false && adjust(fro, to, win);
299
+ startMove(fro, to, options.duration || 500, from, callback);
300
+
301
+ },
302
+ /**
303
+ * @function drag
304
+ * Creates a mousedown and drags from one point to another.
305
+ * Check out [Syn.prototype.move move] for API details.
306
+ *
307
+ * @param {Object} options
308
+ * @param {Object} from
309
+ * @param {Object} callback
310
+ */
311
+ _drag: function( options, from, callback ) {
312
+ //need to convert if elements
313
+ var win = Syn.helpers.getWindow(from),
314
+ fro = convertOption(options.from || from, win, from),
315
+ to = convertOption(options.to || options, win, from);
316
+
317
+ options.adjust !== false && adjust(fro, to, win);
318
+ startDrag(fro, to, options.duration || 500, from, callback);
319
+
320
+ }
321
+ })
322
+ }());
@@ -0,0 +1,905 @@
1
+ steal.then(function(){
2
+ var h = Syn.helpers,
3
+ S = Syn,
4
+
5
+ // gets the selection of an input or textarea
6
+ getSelection = function( el ) {
7
+ // use selectionStart if we can
8
+ if ( el.selectionStart !== undefined ) {
9
+ // this is for opera, so we don't have to focus to type how we think we would
10
+ if ( document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0 ) {
11
+ return {
12
+ start: el.value.length,
13
+ end: el.value.length
14
+ };
15
+ }
16
+ return {
17
+ start: el.selectionStart,
18
+ end: el.selectionEnd
19
+ }
20
+ } else {
21
+ //check if we aren't focused
22
+ try {
23
+ //try 2 different methods that work differently (IE breaks depending on type)
24
+ if ( el.nodeName.toLowerCase() == 'input' ) {
25
+ var real = h.getWindow(el).document.selection.createRange(),
26
+ r = el.createTextRange();
27
+ r.setEndPoint("EndToStart", real);
28
+
29
+ var start = r.text.length
30
+ return {
31
+ start: start,
32
+ end: start + real.text.length
33
+ }
34
+ }
35
+ else {
36
+ var real = h.getWindow(el).document.selection.createRange(),
37
+ r = real.duplicate(),
38
+ r2 = real.duplicate(),
39
+ r3 = real.duplicate();
40
+ r2.collapse();
41
+ r3.collapse(false);
42
+ r2.moveStart('character', -1)
43
+ r3.moveStart('character', -1)
44
+ //select all of our element
45
+ r.moveToElementText(el)
46
+ //now move our endpoint to the end of our real range
47
+ r.setEndPoint('EndToEnd', real);
48
+ var start = r.text.length - real.text.length,
49
+ end = r.text.length;
50
+ if ( start != 0 && r2.text == "" ) {
51
+ start += 2;
52
+ }
53
+ if ( end != 0 && r3.text == "" ) {
54
+ end += 2;
55
+ }
56
+ //if we aren't at the start, but previous is empty, we are at start of newline
57
+ return {
58
+ start: start,
59
+ end: end
60
+ }
61
+ }
62
+ } catch (e) {
63
+ return {
64
+ start: el.value.length,
65
+ end: el.value.length
66
+ };
67
+ }
68
+ }
69
+ },
70
+ // gets all focusable elements
71
+ getFocusable = function( el ) {
72
+ var document = h.getWindow(el).document,
73
+ res = [];
74
+
75
+ var els = document.getElementsByTagName('*'),
76
+ len = els.length;
77
+
78
+ for ( var i = 0; i < len; i++ ) {
79
+ Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i])
80
+ }
81
+ return res;
82
+
83
+
84
+ };
85
+
86
+ /**
87
+ * @add Syn static
88
+ */
89
+ h.extend(Syn, {
90
+ /**
91
+ * @attribute
92
+ * A list of the keys and their keycodes codes you can type.
93
+ * You can add type keys with
94
+ * @codestart
95
+ * Syn('key','delete','title');
96
+ *
97
+ * //or
98
+ *
99
+ * Syn('type','One Two Three[left][left][delete]','title')
100
+ * @codeend
101
+ *
102
+ * The following are a list of keys you can type:
103
+ * @codestart text
104
+ * \b - backspace
105
+ * \t - tab
106
+ * \r - enter
107
+ * ' ' - space
108
+ * a-Z 0-9 - normal characters
109
+ * /!@#$*,.? - All other typeable characters
110
+ * page-up - scrolls up
111
+ * page-down - scrolls down
112
+ * end - scrolls to bottom
113
+ * home - scrolls to top
114
+ * insert - changes how keys are entered
115
+ * delete - deletes the next character
116
+ * left - moves cursor left
117
+ * right - moves cursor right
118
+ * up - moves the cursor up
119
+ * down - moves the cursor down
120
+ * f1-12 - function buttons
121
+ * shift, ctrl, alt - special keys
122
+ * pause-break - the pause button
123
+ * scroll-lock - locks scrolling
124
+ * caps - makes caps
125
+ * escape - escape button
126
+ * num-lock - allows numbers on keypad
127
+ * print - screen capture
128
+ * @codeend
129
+ */
130
+ keycodes: {
131
+ //backspace
132
+ '\b': '8',
133
+
134
+ //tab
135
+ '\t': '9',
136
+
137
+ //enter
138
+ '\r': '13',
139
+
140
+ //special
141
+ 'shift': '16',
142
+ 'ctrl': '17',
143
+ 'alt': '18',
144
+
145
+ //weird
146
+ 'pause-break': '19',
147
+ 'caps': '20',
148
+ 'escape': '27',
149
+ 'num-lock': '144',
150
+ 'scroll-lock': '145',
151
+ 'print': '44',
152
+
153
+ //navigation
154
+ 'page-up': '33',
155
+ 'page-down': '34',
156
+ 'end': '35',
157
+ 'home': '36',
158
+ 'left': '37',
159
+ 'up': '38',
160
+ 'right': '39',
161
+ 'down': '40',
162
+ 'insert': '45',
163
+ 'delete': '46',
164
+
165
+ //normal characters
166
+ ' ': '32',
167
+ '0': '48',
168
+ '1': '49',
169
+ '2': '50',
170
+ '3': '51',
171
+ '4': '52',
172
+ '5': '53',
173
+ '6': '54',
174
+ '7': '55',
175
+ '8': '56',
176
+ '9': '57',
177
+ 'a': '65',
178
+ 'b': '66',
179
+ 'c': '67',
180
+ 'd': '68',
181
+ 'e': '69',
182
+ 'f': '70',
183
+ 'g': '71',
184
+ 'h': '72',
185
+ 'i': '73',
186
+ 'j': '74',
187
+ 'k': '75',
188
+ 'l': '76',
189
+ 'm': '77',
190
+ 'n': '78',
191
+ 'o': '79',
192
+ 'p': '80',
193
+ 'q': '81',
194
+ 'r': '82',
195
+ 's': '83',
196
+ 't': '84',
197
+ 'u': '85',
198
+ 'v': '86',
199
+ 'w': '87',
200
+ 'x': '88',
201
+ 'y': '89',
202
+ 'z': '90',
203
+ //normal-characters, numpad
204
+ 'num0': '96',
205
+ 'num1': '97',
206
+ 'num2': '98',
207
+ 'num3': '99',
208
+ 'num4': '100',
209
+ 'num5': '101',
210
+ 'num6': '102',
211
+ 'num7': '103',
212
+ 'num8': '104',
213
+ 'num9': '105',
214
+ '*': '106',
215
+ '+': '107',
216
+ '-': '109',
217
+ '.': '110',
218
+ //normal-characters, others
219
+ '/': '111',
220
+ ';': '186',
221
+ '=': '187',
222
+ ',': '188',
223
+ '-': '189',
224
+ '.': '190',
225
+ '/': '191',
226
+ '`': '192',
227
+ '[': '219',
228
+ '\\': '220',
229
+ ']': '221',
230
+ "'": '222',
231
+
232
+ //ignore these, you shouldn't use them
233
+ 'left window key': '91',
234
+ 'right window key': '92',
235
+ 'select key': '93',
236
+
237
+
238
+ 'f1': '112',
239
+ 'f2': '113',
240
+ 'f3': '114',
241
+ 'f4': '115',
242
+ 'f5': '116',
243
+ 'f6': '117',
244
+ 'f7': '118',
245
+ 'f8': '119',
246
+ 'f9': '120',
247
+ 'f10': '121',
248
+ 'f11': '122',
249
+ 'f12': '123'
250
+ },
251
+
252
+ // what we can type in
253
+ typeable: /input|textarea/i,
254
+
255
+ // selects text on an element
256
+ selectText: function( el, start, end ) {
257
+ if ( el.setSelectionRange ) {
258
+ if (!end ) {
259
+ el.focus();
260
+ el.setSelectionRange(start, start);
261
+ } else {
262
+ el.selectionStart = start;
263
+ el.selectionEnd = end;
264
+ }
265
+ } else if ( el.createTextRange ) {
266
+ //el.focus();
267
+ var r = el.createTextRange();
268
+ r.moveStart('character', start);
269
+ end = end || start;
270
+ r.moveEnd('character', end - el.value.length);
271
+
272
+ r.select();
273
+ }
274
+ },
275
+ getText: function( el ) {
276
+ //first check if the el has anything selected ..
277
+ if ( Syn.typeable.test(el.nodeName) ) {
278
+ var sel = getSelection(el);
279
+ return el.value.substring(sel.start, sel.end)
280
+ }
281
+ //otherwise get from page
282
+ var win = Syn.helpers.getWindow(el);
283
+ if ( win.getSelection ) {
284
+ return win.getSelection().toString();
285
+ }
286
+ else if ( win.document.getSelection ) {
287
+ return win.document.getSelection().toString()
288
+ }
289
+ else {
290
+ return win.document.selection.createRange().text;
291
+ }
292
+ },
293
+ getSelection: getSelection
294
+ });
295
+
296
+ h.extend(Syn.key, {
297
+ // retrieves a description of what events for this character should look like
298
+ data: function( key ) {
299
+ //check if it is described directly
300
+ if ( S.key.browser[key] ) {
301
+ return S.key.browser[key];
302
+ }
303
+ for ( var kind in S.key.kinds ) {
304
+ if ( h.inArray(key, S.key.kinds[kind]) > -1 ) {
305
+ return S.key.browser[kind]
306
+ }
307
+ }
308
+ return S.key.browser.character
309
+ },
310
+
311
+ //returns the special key if special
312
+ isSpecial: function( keyCode ) {
313
+ var specials = S.key.kinds.special;
314
+ for ( var i = 0; i < specials.length; i++ ) {
315
+ if ( Syn.keycodes[specials[i]] == keyCode ) {
316
+ return specials[i];
317
+ }
318
+ }
319
+ },
320
+ /**
321
+ * @hide
322
+ * gets the options for a key and event type ...
323
+ * @param {Object} key
324
+ * @param {Object} event
325
+ */
326
+ options: function( key, event ) {
327
+ var keyData = Syn.key.data(key);
328
+
329
+ if (!keyData[event] ) {
330
+ //we shouldn't be creating this event
331
+ return null;
332
+ }
333
+
334
+ var charCode = keyData[event][0],
335
+ keyCode = keyData[event][1],
336
+ result = {};
337
+
338
+ if ( keyCode == 'key' ) {
339
+ result.keyCode = Syn.keycodes[key]
340
+ } else if ( keyCode == 'char' ) {
341
+ result.keyCode = key.charCodeAt(0)
342
+ } else {
343
+ result.keyCode = keyCode;
344
+ }
345
+
346
+ if ( charCode == 'char' ) {
347
+ result.charCode = key.charCodeAt(0)
348
+ } else if ( charCode !== null ) {
349
+ result.charCode = charCode;
350
+ }
351
+
352
+
353
+ return result
354
+ },
355
+ //types of event keys
356
+ kinds: {
357
+ special: ["shift", 'ctrl', 'alt', 'caps'],
358
+ specialChars: ["\b"],
359
+ navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'],
360
+ 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12']
361
+ },
362
+ //returns the default function
363
+ getDefault: function( key ) {
364
+ //check if it is described directly
365
+ if ( Syn.key.defaults[key] ) {
366
+ return Syn.key.defaults[key];
367
+ }
368
+ for ( var kind in Syn.key.kinds ) {
369
+ if ( h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind] ) {
370
+ return Syn.key.defaults[kind];
371
+ }
372
+ }
373
+ return Syn.key.defaults.character
374
+ },
375
+ // default behavior when typing
376
+ defaults: {
377
+ 'character': function( options, scope, key, force, sel ) {
378
+ if (/num\d+/.test(key) ) {
379
+ key = key.match(/\d+/)[0]
380
+ }
381
+
382
+ if ( force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName)) ) {
383
+ var current = this.value,
384
+ before = current.substr(0, sel.start),
385
+ after = current.substr(sel.end),
386
+ character = key;
387
+
388
+ this.value = before + character + after;
389
+ //handle IE inserting \r\n
390
+ var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length;
391
+ Syn.selectText(this, before.length + charLength)
392
+ }
393
+ },
394
+ 'c': function( options, scope, key, force, sel ) {
395
+ if ( Syn.key.ctrlKey ) {
396
+ Syn.key.clipboard = Syn.getText(this)
397
+ } else {
398
+ Syn.key.defaults.character.apply(this, arguments);
399
+ }
400
+ },
401
+ 'v': function( options, scope, key, force, sel ) {
402
+ if ( Syn.key.ctrlKey ) {
403
+ Syn.key.defaults.character.call(this, options, scope, Syn.key.clipboard, true, sel);
404
+ } else {
405
+ Syn.key.defaults.character.apply(this, arguments);
406
+ }
407
+ },
408
+ 'a': function( options, scope, key, force, sel ) {
409
+ if ( Syn.key.ctrlKey ) {
410
+ Syn.selectText(this, 0, this.value.length)
411
+ } else {
412
+ Syn.key.defaults.character.apply(this, arguments);
413
+ }
414
+ },
415
+ 'home': function() {
416
+ Syn.onParents(this, function( el ) {
417
+ if ( el.scrollHeight != el.clientHeight ) {
418
+ el.scrollTop = 0;
419
+ return false;
420
+ }
421
+ })
422
+ },
423
+ 'end': function() {
424
+ Syn.onParents(this, function( el ) {
425
+ if ( el.scrollHeight != el.clientHeight ) {
426
+ el.scrollTop = el.scrollHeight;
427
+ return false;
428
+ }
429
+ })
430
+ },
431
+ 'page-down': function() {
432
+ //find the first parent we can scroll
433
+ Syn.onParents(this, function( el ) {
434
+ if ( el.scrollHeight != el.clientHeight ) {
435
+ var ch = el.clientHeight
436
+ el.scrollTop += ch;
437
+ return false;
438
+ }
439
+ })
440
+ },
441
+ 'page-up': function() {
442
+ Syn.onParents(this, function( el ) {
443
+ if ( el.scrollHeight != el.clientHeight ) {
444
+ var ch = el.clientHeight
445
+ el.scrollTop -= ch;
446
+ return false;
447
+ }
448
+ })
449
+ },
450
+ '\b': function( options, scope, key, force, sel ) {
451
+ //this assumes we are deleting from the end
452
+ if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) {
453
+ var current = this.value,
454
+ before = current.substr(0, sel.start),
455
+ after = current.substr(sel.end);
456
+
457
+ if ( sel.start == sel.end && sel.start > 0 ) {
458
+ //remove a character
459
+ this.value = before.substring(0, before.length - 1) + after
460
+ Syn.selectText(this, sel.start - 1)
461
+ } else {
462
+ this.value = before + after;
463
+ Syn.selectText(this, sel.start)
464
+ }
465
+
466
+ //set back the selection
467
+ }
468
+ },
469
+ 'delete': function( options, scope, key, force, sel ) {
470
+ if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) {
471
+ var current = this.value,
472
+ before = current.substr(0, sel.start),
473
+ after = current.substr(sel.end);
474
+ if ( sel.start == sel.end && sel.start <= this.value.length - 1 ) {
475
+ this.value = before + after.substring(1)
476
+ } else {
477
+ this.value = before + after;
478
+
479
+ }
480
+ Syn.selectText(this, sel.start)
481
+ }
482
+ },
483
+ '\r': function( options, scope, key, force, sel ) {
484
+
485
+ var nodeName = this.nodeName.toLowerCase()
486
+ // submit a form
487
+ if (!S.support.keypressSubmits && nodeName == 'input' ) {
488
+ var form = Syn.closest(this, "form");
489
+ if ( form ) {
490
+ Syn.trigger("submit", {}, form);
491
+ }
492
+
493
+ }
494
+ //newline in textarea
495
+ if (!S.support.keyCharacters && nodeName == 'textarea' ) {
496
+ Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel)
497
+ }
498
+ // 'click' hyperlinks
499
+ if (!S.support.keypressOnAnchorClicks && nodeName == 'a' ) {
500
+ Syn.trigger("click", {}, this);
501
+ }
502
+ },
503
+ //
504
+ // Gets all focusable elements. If the element (this)
505
+ // doesn't have a tabindex, finds the next element after.
506
+ // If the element (this) has a tabindex finds the element
507
+ // with the next higher tabindex OR the element with the same
508
+ // tabindex after it in the document.
509
+ // @return the next element
510
+ //
511
+ '\t': function( options, scope ) {
512
+ // focusable elements
513
+ var focusEls = getFocusable(this),
514
+ // the current element's tabindex
515
+ tabIndex = Syn.tabIndex(this),
516
+ // will be set to our guess for the next element
517
+ current = null,
518
+ // the next index we care about
519
+ currentIndex = 1000000000,
520
+ // set to true once we found 'this' element
521
+ found = false,
522
+ i = 0,
523
+ el,
524
+ //the tabindex of the tabable element we are looking at
525
+ elIndex, firstNotIndexed, prev;
526
+ orders = [];
527
+ for (; i < focusEls.length; i++ ) {
528
+ orders.push([focusEls[i], i]);
529
+ }
530
+ var sort = function( order1, order2 ) {
531
+ var el1 = order1[0],
532
+ el2 = order2[0],
533
+ tab1 = Syn.tabIndex(el1) || 0,
534
+ tab2 = Syn.tabIndex(el2) || 0;
535
+ if ( tab1 == tab2 ) {
536
+ return order1[1] - order2[1]
537
+ } else {
538
+ if ( tab1 == 0 ) {
539
+ return 1;
540
+ } else if ( tab2 == 0 ) {
541
+ return -1;
542
+ } else {
543
+ return tab1 - tab2;
544
+ }
545
+ }
546
+ }
547
+ orders.sort(sort);
548
+ //now find current
549
+ for ( i = 0; i < orders.length; i++ ) {
550
+ el = orders[i][0];
551
+ if ( this == el ) {
552
+ if (!Syn.key.shiftKey ) {
553
+ current = orders[i + 1][0];
554
+ if (!current ) {
555
+ current = orders[0][0]
556
+ }
557
+ } else {
558
+ current = orders[i - 1][0];
559
+ if (!current ) {
560
+ current = orders[focusEls.length - 1][0]
561
+ }
562
+ }
563
+
564
+ }
565
+ }
566
+
567
+ //restart if we didn't find anything
568
+ if (!current ) {
569
+ current = firstNotIndexed;
570
+ }
571
+ current && current.focus();
572
+ return current;
573
+ },
574
+ 'left': function( options, scope, key, force, sel ) {
575
+ if ( Syn.typeable.test(this.nodeName) ) {
576
+ if ( Syn.key.shiftKey ) {
577
+ Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end)
578
+ } else {
579
+ Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1)
580
+ }
581
+ }
582
+ },
583
+ 'right': function( options, scope, key, force, sel ) {
584
+ if ( Syn.typeable.test(this.nodeName) ) {
585
+ if ( Syn.key.shiftKey ) {
586
+ Syn.selectText(this, sel.start, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1)
587
+ } else {
588
+ Syn.selectText(this, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1)
589
+ }
590
+ }
591
+ },
592
+ 'up': function() {
593
+ if (/select/i.test(this.nodeName) ) {
594
+
595
+ this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0;
596
+ //set this to change on blur?
597
+ }
598
+ },
599
+ 'down': function() {
600
+ if (/select/i.test(this.nodeName) ) {
601
+ Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex)
602
+ this.selectedIndex = this.selectedIndex + 1;
603
+ //set this to change on blur?
604
+ }
605
+ },
606
+ 'shift': function() {
607
+ return null;
608
+ }
609
+ }
610
+ });
611
+
612
+
613
+ h.extend(Syn.create, {
614
+ keydown: {
615
+ setup: function( type, options, element ) {
616
+ if ( h.inArray(options, Syn.key.kinds.special) != -1 ) {
617
+ Syn.key[options + "Key"] = element;
618
+ }
619
+ }
620
+ },
621
+ keypress: {
622
+ setup: function( type, options, element ) {
623
+ // if this browsers supports writing keys on events
624
+ // but doesn't write them if the element isn't focused
625
+ // focus on the element (ignored if already focused)
626
+ if ( S.support.keyCharacters && !S.support.keysOnNotFocused ) {
627
+ element.focus()
628
+ }
629
+ }
630
+ },
631
+ keyup: {
632
+ setup: function( type, options, element ) {
633
+ if ( h.inArray(options, Syn.key.kinds.special) != -1 ) {
634
+ Syn.key[options + "Key"] = null;
635
+ }
636
+ }
637
+ },
638
+ key: {
639
+ // return the options for a key event
640
+ options: function( type, options, element ) {
641
+ //check if options is character or has character
642
+ options = typeof options != "object" ? {
643
+ character: options
644
+ } : options;
645
+
646
+ //don't change the orignial
647
+ options = h.extend({}, options)
648
+ if ( options.character ) {
649
+ h.extend(options, S.key.options(options.character, type));
650
+ delete options.character;
651
+ }
652
+
653
+ options = h.extend({
654
+ ctrlKey: !! Syn.key.ctrlKey,
655
+ altKey: !! Syn.key.altKey,
656
+ shiftKey: !! Syn.key.shiftKey,
657
+ metaKey: !! Syn.key.metaKey
658
+ }, options)
659
+
660
+ return options;
661
+ },
662
+ // creates a key event
663
+ event: function( type, options, element ) { //Everyone Else
664
+ var doc = h.getWindow(element).document || document;
665
+ if ( doc.createEvent ) {
666
+ var event;
667
+
668
+ try {
669
+
670
+ event = doc.createEvent("KeyEvents");
671
+ event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
672
+ }
673
+ catch (e) {
674
+ event = h.createBasicStandardEvent(type, options, doc);
675
+ }
676
+ event.synthetic = true;
677
+ return event;
678
+ }
679
+ else {
680
+ var event;
681
+ try {
682
+ event = h.createEventObject.apply(this, arguments);
683
+ h.extend(event, options)
684
+ }
685
+ catch (e) {}
686
+
687
+ return event;
688
+ }
689
+ }
690
+ }
691
+ });
692
+
693
+ var convert = {
694
+ "enter": "\r",
695
+ "backspace": "\b",
696
+ "tab": "\t",
697
+ "space": " "
698
+ }
699
+
700
+ /**
701
+ * @add Syn prototype
702
+ */
703
+ h.extend(Syn.init.prototype, {
704
+ /**
705
+ * @function key
706
+ * Types a single key. The key should be
707
+ * a string that matches a
708
+ * [Syn.static.keycodes].
709
+ *
710
+ * The following sends a carridge return
711
+ * to the 'name' element.
712
+ * @codestart
713
+ * Syn.key('\r','name')
714
+ * @codeend
715
+ * For each character, a keydown, keypress, and keyup is triggered if
716
+ * appropriate.
717
+ * @param {String} options
718
+ * @param {HTMLElement} [element]
719
+ * @param {Function} [callback]
720
+ * @return {HTMLElement} the element currently focused.
721
+ */
722
+ _key: function( options, element, callback ) {
723
+ //first check if it is a special up
724
+ if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), Syn.key.kinds.special) != -1 ) {
725
+ Syn.trigger('keyup', options.replace("-up", ""), element)
726
+ callback(true, element);
727
+ return;
728
+ }
729
+
730
+
731
+ var caret = Syn.typeable.test(element.nodeName) && getSelection(element),
732
+ key = convert[options] || options,
733
+ // should we run default events
734
+ runDefaults = Syn.trigger('keydown', key, element),
735
+
736
+ // a function that gets the default behavior for a key
737
+ getDefault = Syn.key.getDefault,
738
+
739
+ // how this browser handles preventing default events
740
+ prevent = Syn.key.browser.prevent,
741
+
742
+ // the result of the default event
743
+ defaultResult,
744
+
745
+ // options for keypress
746
+ keypressOptions = Syn.key.options(key, 'keypress')
747
+
748
+
749
+ if ( runDefaults ) {
750
+ //if the browser doesn't create keypresses for this key, run default
751
+ if (!keypressOptions ) {
752
+ defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret)
753
+ } else {
754
+ //do keypress
755
+ runDefaults = Syn.trigger('keypress', keypressOptions, element)
756
+ if ( runDefaults ) {
757
+ defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret)
758
+ }
759
+ }
760
+ } else {
761
+ //canceled ... possibly don't run keypress
762
+ if ( keypressOptions && h.inArray('keypress', prevent.keydown) == -1 ) {
763
+ Syn.trigger('keypress', keypressOptions, element)
764
+ }
765
+ }
766
+ if ( defaultResult && defaultResult.nodeName ) {
767
+ element = defaultResult
768
+ }
769
+
770
+ if ( defaultResult !== null ) {
771
+ setTimeout(function() {
772
+ Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element)
773
+ callback(runDefaults, element)
774
+ }, 1)
775
+ } else {
776
+ callback(runDefaults, element)
777
+ }
778
+
779
+
780
+ //do mouseup
781
+ return element;
782
+ // is there a keypress? .. if not , run default
783
+ // yes -> did we prevent it?, if not run ...
784
+ },
785
+ /**
786
+ * @function type
787
+ * Types sequence of [Syn.key key actions]. Each
788
+ * character is typed, one at a type.
789
+ * Multi-character keys like 'left' should be
790
+ * enclosed in square brackents.
791
+ *
792
+ * The following types 'JavaScript MVC' then deletes the space.
793
+ * @codestart
794
+ * Syn.type('JavaScript MVC[left][left][left]\b','name')
795
+ * @codeend
796
+ *
797
+ * Type is able to handle (and move with) tabs (\t).
798
+ * The following simulates tabing and entering values in a form and
799
+ * eventually submitting the form.
800
+ * @codestart
801
+ * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r")
802
+ * @codeend
803
+ * @param {String} options the text to type
804
+ * @param {HTMLElement} [element] an element or an id of an element
805
+ * @param {Function} [callback] a function to callback
806
+ */
807
+ _type: function( options, element, callback ) {
808
+ //break it up into parts ...
809
+ //go through each type and run
810
+ var parts = options.match(/(\[[^\]]+\])|([^\[])/g),
811
+ self = this,
812
+ runNextPart = function( runDefaults, el ) {
813
+ var part = parts.shift();
814
+ if (!part ) {
815
+ callback(runDefaults, el);
816
+ return;
817
+ }
818
+ el = el || element;
819
+ if ( part.length > 1 ) {
820
+ part = part.substr(1, part.length - 2)
821
+ }
822
+ self._key(part, el, runNextPart)
823
+ }
824
+
825
+ runNextPart();
826
+
827
+ }
828
+ });
829
+
830
+
831
+ //do support code
832
+ (function() {
833
+ if (!document.body ) {
834
+ setTimeout(arguments.callee, 1)
835
+ return;
836
+ }
837
+
838
+ var div = document.createElement("div"),
839
+ checkbox, submit, form, input, submitted = false,
840
+ anchor, textarea, inputter;
841
+
842
+ div.innerHTML = "<form id='outer'>" +
843
+ "<input name='checkbox' type='checkbox'/>" +
844
+ "<input name='radio' type='radio' />" +
845
+ "<input type='submit' name='submitter'/>" +
846
+ "<input type='input' name='inputter'/>" +
847
+ "<input name='one'>" +
848
+ "<input name='two'/>" +
849
+ "<a href='#abc'></a>" +
850
+ "<textarea>1\n2</textarea>" +
851
+ "</form>";
852
+
853
+ document.documentElement.appendChild(div);
854
+ form = div.firstChild;
855
+ checkbox = form.childNodes[0];
856
+ submit = form.childNodes[2];
857
+ anchor = form.getElementsByTagName("a")[0];
858
+ textarea = form.getElementsByTagName("textarea")[0];
859
+ inputter = form.childNodes[3];
860
+
861
+ form.onsubmit = function( ev ) {
862
+ if ( ev.preventDefault ) ev.preventDefault();
863
+ S.support.keypressSubmits = true;
864
+ ev.returnValue = false;
865
+ return false;
866
+ };
867
+ // Firefox 4 won't write key events if the element isn't focused
868
+ inputter.focus();
869
+ Syn.trigger("keypress", "\r", inputter);
870
+
871
+
872
+ Syn.trigger("keypress", "a", inputter);
873
+ S.support.keyCharacters = inputter.value == "a";
874
+
875
+
876
+ inputter.value = "a";
877
+ Syn.trigger("keypress", "\b", inputter);
878
+ S.support.backspaceWorks = inputter.value == "";
879
+
880
+
881
+
882
+ inputter.onchange = function() {
883
+ S.support.focusChanges = true;
884
+ }
885
+ inputter.focus();
886
+ Syn.trigger("keypress", "a", inputter);
887
+ form.childNodes[5].focus(); // this will throw a change event
888
+ Syn.trigger("keypress", "b", inputter);
889
+ S.support.keysOnNotFocused = inputter.value == "ab";
890
+
891
+ //test keypress \r on anchor submits
892
+ S.bind(anchor, "click", function( ev ) {
893
+ if ( ev.preventDefault ) ev.preventDefault();
894
+ S.support.keypressOnAnchorClicks = true;
895
+ ev.returnValue = false;
896
+ return false;
897
+ })
898
+ Syn.trigger("keypress", "\r", anchor);
899
+
900
+ S.support.textareaCarriage = textarea.value.length == 4
901
+ document.documentElement.removeChild(div);
902
+
903
+ S.support.ready++;
904
+ })();
905
+ })